Make tests more resilient for React 18 upgrade (#12861)

* Make tests more resilient for React 18 upgrade

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-08-06 18:22:02 +01:00 committed by GitHub
parent 4e4c5c7768
commit 8285283cc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 313 additions and 290 deletions

View file

@ -1380,7 +1380,6 @@ describe("<MatrixChat />", () => {
it("while we are checking the sync store", async () => {
const rendered = getComponent({});
await flushPromises();
expect(rendered.getByTestId("spinner")).toBeInTheDocument();
// now a third session starts

View file

@ -128,7 +128,7 @@ describe("<UserMenu>", () => {
const spy = jest.spyOn(defaultDispatcher, "dispatch");
screen.getByRole("button", { name: /User menu/i }).click();
screen.getByRole("menuitem", { name: /Sign out/i }).click();
(await screen.findByRole("menuitem", { name: /Sign out/i })).click();
await waitFor(() => {
expect(spy).toHaveBeenCalledWith({ action: "logout" });
});
@ -152,7 +152,7 @@ describe("<UserMenu>", () => {
const spy = jest.spyOn(defaultDispatcher, "dispatch");
screen.getByRole("button", { name: /User menu/i }).click();
screen.getByRole("menuitem", { name: /Sign out/i }).click();
(await screen.findByRole("menuitem", { name: /Sign out/i })).click();
await waitFor(() => {
expect(spy).toHaveBeenCalledWith({ action: "logout" });
});
@ -178,7 +178,7 @@ describe("<UserMenu>", () => {
const spy = jest.spyOn(Modal, "createDialog");
screen.getByRole("button", { name: /User menu/i }).click();
screen.getByRole("menuitem", { name: /Sign out/i }).click();
(await screen.findByRole("menuitem", { name: /Sign out/i })).click();
await waitFor(() => {
expect(spy).toHaveBeenCalledWith(LogoutDialog);

View file

@ -129,11 +129,11 @@ describe("AccessSecretStorageDialog", () => {
expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);
await submitDialog();
expect(
screen.getByText(
await expect(
screen.findByText(
"👎 Unable to access secret storage. Please verify that you entered the correct Security Phrase.",
),
).toBeInTheDocument();
).resolves.toBeInTheDocument();
expect(screen.getByPlaceholderText("Security Phrase")).toHaveFocus();
});

View file

@ -429,7 +429,7 @@ describe("InviteDialog", () => {
describe("when clicking »Start DM anyway«", () => {
beforeEach(async () => {
await userEvent.click(screen.getByRole("button", { name: "Start DM anyway", exact: true }));
await userEvent.click(screen.getByRole("button", { name: "Start DM anyway" }));
});
it("should start the DM", () => {

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { render, RenderResult } from "@testing-library/react";
import { render, RenderResult, waitForElementToBeRemoved } from "@testing-library/react";
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
@ -39,6 +39,7 @@ describe("<MessageEditHistory />", () => {
async function renderComponent(): Promise<RenderResult> {
const result = render(<MessageEditHistoryDialog mxEvent={event} onFinished={jest.fn()} />);
await waitForElementToBeRemoved(() => result.queryByRole("progressbar"));
await flushPromises();
return result;
}

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import {
EventTimeline,
EventType,
@ -129,7 +129,7 @@ describe("<RoomSettingsDialog />", () => {
expect(screen.getByTestId("settings-tab-ROOM_PEOPLE_TAB")).toBeInTheDocument();
});
it("re-renders on room join rule changes", () => {
it("re-renders on room join rule changes", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(setting) => setting === "feature_ask_to_join",
);
@ -142,7 +142,9 @@ describe("<RoomSettingsDialog />", () => {
room.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
null,
);
expect(screen.queryByTestId("settings-tab-ROOM_PEOPLE_TAB")).not.toBeInTheDocument();
await waitFor(() =>
expect(screen.queryByTestId("settings-tab-ROOM_PEOPLE_TAB")).not.toBeInTheDocument(),
);
});
});

View file

@ -57,14 +57,9 @@ jest.mock("../../../../src/settings/SettingsStore", () => ({
settingIsOveriddenAtConfigLevel: jest.fn(),
}));
jest.mock("../../../../src/SdkConfig", () => ({
get: jest.fn(),
}));
describe("<UserSettingsDialog />", () => {
const userId = "@alice:server.org";
const mockSettingsStore = mocked(SettingsStore);
const mockSdkConfig = mocked(SdkConfig);
let mockClient!: MockedObject<MatrixClient>;
let sdkContext: SdkContextClass;
@ -89,7 +84,8 @@ describe("<UserSettingsDialog />", () => {
mockSettingsStore.getValue.mockReturnValue(false);
mockSettingsStore.getValueAt.mockReturnValue(false);
mockSettingsStore.getFeatureSettingNames.mockReturnValue([]);
mockSdkConfig.get.mockReturnValue({ brand: "Test" });
SdkConfig.reset();
SdkConfig.put({ brand: "Test" });
});
const getActiveTabLabel = (container: Element) =>
@ -115,6 +111,9 @@ describe("<UserSettingsDialog />", () => {
});
it("renders tabs correctly", () => {
SdkConfig.add({
show_labs_settings: true,
});
const { container } = render(getComponent());
expect(container.querySelectorAll(".mx_TabbedView_tabLabel")).toMatchSnapshot();
});
@ -181,7 +180,7 @@ describe("<UserSettingsDialog />", () => {
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Voice & Video");
});
it("renders with secutity tab selected", () => {
it("renders with security tab selected", () => {
const { container } = render(getComponent({ initialTabId: UserTab.Security }));
expect(getActiveTabLabel(container)).toEqual("Security & Privacy");
@ -189,18 +188,8 @@ describe("<UserSettingsDialog />", () => {
});
it("renders with labs tab selected", () => {
// @ts-ignore I give up trying to get the types right here
// why do we have functions that return different things depending on what they're passed?
mockSdkConfig.get.mockImplementation((x) => {
const mockConfig = { show_labs_settings: true, brand: "Test" };
switch (x) {
case "show_labs_settings":
case "brand":
// @ts-ignore
return mockConfig[x];
default:
return mockConfig;
}
SdkConfig.add({
show_labs_settings: true,
});
const { container } = render(getComponent({ initialTabId: UserTab.Labs }));
@ -223,8 +212,9 @@ describe("<UserSettingsDialog />", () => {
});
it("renders labs tab when show_labs_settings is enabled in config", () => {
// @ts-ignore simplified test stub
mockSdkConfig.get.mockImplementation((configName) => configName === "show_labs_settings");
SdkConfig.add({
show_labs_settings: true,
});
const { getByTestId } = render(getComponent());
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
});
@ -238,7 +228,7 @@ describe("<UserSettingsDialog />", () => {
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
});
it("watches settings", () => {
it("watches settings", async () => {
const watchSettingCallbacks: Record<string, CallbackFn> = {};
mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => {
@ -247,7 +237,7 @@ describe("<UserSettingsDialog />", () => {
});
mockSettingsStore.getValue.mockReturnValue(false);
const { queryByTestId, unmount } = render(getComponent());
const { queryByTestId, findByTestId, unmount } = render(getComponent());
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeFalsy();
expect(mockSettingsStore.watchSetting).toHaveBeenCalledWith("feature_mjolnir", null, expect.anything());
@ -257,7 +247,7 @@ describe("<UserSettingsDialog />", () => {
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", "", SettingLevel.ACCOUNT, true, true);
// tab is rendered now
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
await expect(findByTestId(`settings-tab-${UserTab.Mjolnir}`)).resolves.toBeTruthy();
unmount();

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { act, fireEvent, render, screen } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import Field from "../../../../src/components/views/elements/Field";
@ -63,12 +63,10 @@ describe("Field", () => {
);
// When invalid
await act(async () => {
fireEvent.focus(screen.getByRole("textbox"));
});
// Expect 'alert' role
expect(screen.queryByRole("alert")).toBeInTheDocument();
await expect(screen.findByRole("alert")).resolves.toBeInTheDocument();
// Close the feedback is Escape is pressed
fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" });
@ -85,12 +83,10 @@ describe("Field", () => {
);
// When valid
await act(async () => {
fireEvent.focus(screen.getByRole("textbox"));
});
// Expect 'status' role
expect(screen.queryByRole("status")).toBeInTheDocument();
await expect(screen.findByRole("status")).resolves.toBeInTheDocument();
// Close the feedback is Escape is pressed
fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" });
@ -108,12 +104,10 @@ describe("Field", () => {
);
// When valid or invalid and 'tooltipContent' set
await act(async () => {
fireEvent.focus(screen.getByRole("textbox"));
});
// Expect 'tooltip' role
expect(screen.queryByRole("tooltip")).toBeInTheDocument();
await expect(screen.findByRole("tooltip")).resolves.toBeInTheDocument();
// Close the feedback is Escape is pressed
fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" });

View file

@ -32,10 +32,10 @@ describe("<SearchWarning />", () => {
});
it("renders with a logo by default", () => {
const { asFragment, queryByRole } = render(
const { asFragment, getByRole } = render(
<SearchWarning isRoomEncrypted={true} kind={WarningKind.Search} />,
);
expect(queryByRole("img")).toBeInTheDocument();
expect(getByRole("img")).toHaveAttribute("src", "https://logo");
expect(asFragment()).toMatchSnapshot();
});

View file

@ -22,7 +22,10 @@ import PlatformPeg from "../../../../src/PlatformPeg";
describe("<SpellCheckLanguagesDropdown />", () => {
it("renders as expected", async () => {
const platform: any = { getAvailableSpellCheckLanguages: jest.fn().mockResolvedValue(["en", "de", "qq"]) };
const platform: any = {
getAvailableSpellCheckLanguages: jest.fn().mockResolvedValue(["en", "de", "qq"]),
supportsSetting: jest.fn(),
};
PlatformPeg.set(platform);
const { asFragment } = render(

View file

@ -211,7 +211,7 @@ describe("<MImageBody/>", () => {
it("should generate a thumbnail if one isn't included for animated media", async () => {
Object.defineProperty(global.Image.prototype, "src", {
set(src) {
window.setTimeout(() => this.onload());
window.setTimeout(() => this.onload?.());
},
});
Object.defineProperty(global.Image.prototype, "height", {

View file

@ -19,6 +19,7 @@ import { fireEvent, render, waitFor } from "@testing-library/react";
import { LocationAssetType, ClientEvent, RoomMember, SyncState } from "matrix-js-sdk/src/matrix";
import * as maplibregl from "maplibre-gl";
import { logger } from "matrix-js-sdk/src/logger";
import { sleep } from "matrix-js-sdk/src/utils";
import MLocationBody from "../../../../src/components/views/messages/MLocationBody";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
@ -64,9 +65,11 @@ describe("MLocationBody", () => {
});
const component = getComponent();
sleep(10).then(() => {
// simulate error initialising map in maplibregl
// @ts-ignore
mockMap.emit("error", { status: 404 });
});
return component;
};
@ -100,9 +103,10 @@ describe("MLocationBody", () => {
expect(component.container.querySelector(".mx_EventTile_body")).toMatchSnapshot();
});
it("displays correct fallback content when map_style_url is misconfigured", () => {
it("displays correct fallback content when map_style_url is misconfigured", async () => {
const component = getMapErrorComponent();
expect(component.container.querySelector(".mx_EventTile_body")).toMatchSnapshot();
await waitFor(() => expect(component.container.querySelector(".mx_EventTile_body")).toBeTruthy());
await waitFor(() => expect(component.container.querySelector(".mx_EventTile_body")).toMatchSnapshot());
});
it("should clear the error on reconnect", () => {

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { render } from "@testing-library/react";
import { render, waitFor } from "@testing-library/react";
import { EventTimeline, MatrixEvent, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
@ -129,13 +129,12 @@ describe("<MPollEndBody />", () => {
describe("when poll start event does not exist in current timeline", () => {
it("fetches the related poll start event and displays a poll tile", async () => {
await setupRoomWithEventsTimeline(pollEndEvent);
const { container, getByTestId } = getComponent();
const { container, getByTestId, getByRole } = getComponent();
// while fetching event, only icon is shown
expect(container).toMatchSnapshot();
// flush the fetch event promise
await flushPromises();
await waitFor(() => expect(getByRole("progressbar")).toBeInTheDocument());
expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId());

View file

@ -24,6 +24,7 @@ import {
getMockClientWithEventEmitter,
makePollEndEvent,
makePollStartEvent,
mockClientMethodsRooms,
mockClientMethodsUser,
mockIntlDateTimeFormat,
setupRoomWithPollEvents,
@ -41,7 +42,7 @@ describe("<PollHistory />", () => {
const roomId = "!room:domain.org";
const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
getRoom: jest.fn(),
...mockClientMethodsRooms([]),
relations: jest.fn(),
decryptEventIfNeeded: jest.fn(),
getOrCreateFilter: jest.fn(),
@ -117,7 +118,7 @@ describe("<PollHistory />", () => {
expect(getByText("Loading polls")).toBeInTheDocument();
// flush filter creation request
await flushPromises();
await act(flushPromises);
expect(liveTimeline.getPaginationToken).toHaveBeenCalledWith(EventTimeline.BACKWARDS);
expect(mockClient.paginateEventTimeline).toHaveBeenCalledWith(liveTimeline, { backwards: true });
@ -147,7 +148,7 @@ describe("<PollHistory />", () => {
);
// flush filter creation request
await flushPromises();
await act(flushPromises);
// once per page
expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(3);
@ -182,7 +183,7 @@ describe("<PollHistory />", () => {
it("renders a no polls message when there are no active polls in the room", async () => {
const { getByText } = getComponent();
await flushPromises();
await act(flushPromises);
expect(getByText("There are no active polls in this room")).toBeTruthy();
});

View file

@ -163,7 +163,7 @@ describe("<PollListItemEnded />", () => {
await setupRoomWithPollEvents([pollStartEvent], responses, [pollEndEvent], mockClient, room);
const poll = room.polls.get(pollId)!;
const { getByText, queryByText } = getComponent({ event: pollStartEvent, poll });
const { getByText, queryByText, findByText } = getComponent({ event: pollStartEvent, poll });
// fetch relations
await flushPromises();
@ -174,7 +174,7 @@ describe("<PollListItemEnded />", () => {
]);
// updated with more responses
expect(getByText("Final result based on 3 votes")).toBeInTheDocument();
await expect(findByText("Final result based on 3 votes")).resolves.toBeInTheDocument();
expect(getByText("Nissan Silvia S15")).toBeInTheDocument();
expect(queryByText("Mitsubishi Lancer Evolution IX")).not.toBeInTheDocument();
});

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { render } from "@testing-library/react";
import { render, waitFor } from "@testing-library/react";
import {
MatrixEvent,
MsgType,
@ -79,21 +79,23 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
});
it("thread notification does change the thread button", () => {
it("thread notification does change the thread button", async () => {
const { container } = getComponent(room);
expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeFalsy();
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 1);
await waitFor(() => {
expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeTruthy();
expect(isIndicatorOfType(container, "notification")).toBe(true);
});
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 1);
expect(isIndicatorOfType(container, "highlight")).toBe(true);
await waitFor(() => expect(isIndicatorOfType(container, "highlight")).toBe(true));
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 0);
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 0);
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
await waitFor(() => expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull());
});
it("thread activity does change the thread button", async () => {
@ -122,7 +124,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
},
});
room.addReceipt(receipt);
expect(isIndicatorOfType(container, "activity")).toBe(true);
await waitFor(() => expect(isIndicatorOfType(container, "activity")).toBe(true));
// Sending the last event should clear the notification.
let event = mkEvent({
@ -140,7 +142,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
},
});
room.addLiveEvents([event]);
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
await waitFor(() => expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull());
// Mark it as unread again.
event = mkEvent({
@ -158,7 +160,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
},
});
room.addLiveEvents([event]);
expect(isIndicatorOfType(container, "activity")).toBe(true);
await waitFor(() => expect(isIndicatorOfType(container, "activity")).toBe(true));
// Sending a read receipt on an earlier event shouldn't do anything.
receipt = new MatrixEvent({
@ -173,7 +175,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
},
});
room.addReceipt(receipt);
expect(isIndicatorOfType(container, "activity")).toBe(true);
await waitFor(() => expect(isIndicatorOfType(container, "activity")).toBe(true));
// Sending a receipt on the latest event should clear the notification.
receipt = new MatrixEvent({
@ -188,6 +190,6 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
},
});
room.addReceipt(receipt);
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
await waitFor(() => expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull());
});
});

View file

@ -87,9 +87,7 @@ describe("<PinnedMessagesCard />", () => {
};
const mountPins = async (room: Room): Promise<RenderResult> => {
let pins!: RenderResult;
await act(async () => {
pins = render(
const pins = render(
<MatrixClientContext.Provider value={cli}>
<PinnedMessagesCard
room={room}
@ -99,8 +97,7 @@ describe("<PinnedMessagesCard />", () => {
</MatrixClientContext.Provider>,
);
// Wait a tick for state updates
await sleep(0);
});
await act(() => sleep(0));
return pins;
};
@ -313,7 +310,6 @@ describe("<PinnedMessagesCard />", () => {
it("should show spinner whilst loading", async () => {
const room = mkRoom([], [pin1]);
mountPins(room);
const spinner = await screen.findByTestId("spinner");
await waitForElementToBeRemoved(spinner);
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
});
});

View file

@ -287,10 +287,10 @@ describe("<UserInfo />", () => {
expect(spy).not.toHaveBeenCalled();
});
it("renders close button correctly when encryption panel with a pending verification request", () => {
it("renders close button correctly when encryption panel with a pending verification request", async () => {
renderComponent({ phase: RightPanelPhases.EncryptionPanel, verificationRequest });
screen.getByTestId("base-card-close-button").focus();
expect(screen.getByRole("tooltip")).toHaveTextContent("Cancel");
await expect(screen.findByRole("tooltip", { name: "Cancel" })).resolves.toBeInTheDocument();
});
});

View file

@ -16,7 +16,15 @@ limitations under the License.
*/
import React from "react";
import { act, fireEvent, render, RenderResult, screen } from "@testing-library/react";
import {
act,
fireEvent,
render,
RenderResult,
screen,
waitFor,
waitForElementToBeRemoved,
} from "@testing-library/react";
import { Room, MatrixClient, RoomState, RoomMember, User, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { mocked, MockedObject } from "jest-mock";
@ -30,6 +38,7 @@ import {
filterConsole,
flushPromises,
getMockClientWithEventEmitter,
mockClientMethodsRooms,
mockClientMethodsUser,
} from "../../../test-utils";
import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
@ -358,6 +367,7 @@ describe("MemberList", () => {
mocked(shouldShowComponent).mockReturnValue(true);
client = getMockClientWithEventEmitter({
...mockClientMethodsUser(),
...mockClientMethodsRooms(),
getRoom: jest.fn(),
hasLazyLoadMembersEnabled: jest.fn(),
});
@ -372,7 +382,7 @@ describe("MemberList", () => {
const renderComponent = () => {
const context = new TestSdkContext();
context.client = client;
render(
return render(
<SDKContext.Provider value={context}>
<MemberList
searchQuery=""
@ -407,7 +417,10 @@ describe("MemberList", () => {
await flushPromises();
// button rendered but disabled
expect(screen.getByText("Invite to this room")).toHaveAttribute("aria-disabled", "true");
expect(screen.getByRole("button", { name: "Invite to this room" })).toHaveAttribute(
"aria-disabled",
"true",
);
});
it("renders enabled invite button when current user is a member and has rights to invite", async () => {
@ -425,10 +438,17 @@ describe("MemberList", () => {
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
jest.spyOn(room, "canInvite").mockReturnValue(true);
renderComponent();
await flushPromises();
const { getByRole } = renderComponent();
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
fireEvent.click(screen.getByText("Invite to this room"));
await waitFor(() =>
expect(getByRole("button", { name: "Invite to this room" })).not.toHaveAttribute(
"aria-disabled",
"true",
),
);
fireEvent.click(getByRole("button", { name: "Invite to this room" }));
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: "view_invite",

View file

@ -345,6 +345,7 @@ describe("RoomHeader", () => {
jest.spyOn(CallStore.instance, "getCall").mockReturnValue({
widget,
on: () => {},
off: () => {},
} as unknown as Call);
jest.spyOn(WidgetStore.instance, "getApps").mockReturnValue([widget]);
const { container } = render(<RoomHeader room={room} />, getWrapper());
@ -363,6 +364,7 @@ describe("RoomHeader", () => {
jest.spyOn(CallStore.instance, "getCall").mockReturnValue({
widget,
on: () => {},
off: () => {},
} as unknown as Call);
jest.spyOn(WidgetStore.instance, "getApps").mockReturnValue([widget]);

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from "react";
import { MockedObject } from "jest-mock";
import { Room } from "matrix-js-sdk/src/matrix";
import { fireEvent, render, screen } from "@testing-library/react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { VideoRoomChatButton } from "../../../../../src/components/views/rooms/RoomHeader/VideoRoomChatButton";
import { SDKContext, SdkContextClass } from "../../../../../src/contexts/SDKContext";
@ -94,7 +94,7 @@ describe("<VideoRoomChatButton />", () => {
expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy();
});
it("adds unread marker when room notification state changes to unread", () => {
it("adds unread marker when room notification state changes to unread", async () => {
const room = makeRoom();
// start in read state
const notificationState = mockRoomNotificationState(room, NotificationLevel.None);
@ -108,10 +108,10 @@ describe("<VideoRoomChatButton />", () => {
notificationState.emit(NotificationStateEvents.Update);
// unread marker
expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy();
await waitFor(() => expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy());
});
it("clears unread marker when room notification state changes to read", () => {
it("clears unread marker when room notification state changes to read", async () => {
const room = makeRoom();
// start in unread state
const notificationState = mockRoomNotificationState(room, NotificationLevel.Highlight);
@ -125,6 +125,6 @@ describe("<VideoRoomChatButton />", () => {
notificationState.emit(NotificationStateEvents.Update);
// unread marker cleared
expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeFalsy();
await waitFor(() => expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeFalsy());
});
});

View file

@ -15,10 +15,9 @@ limitations under the License.
*/
import React, { ComponentProps } from "react";
import { render, fireEvent, RenderResult, waitFor } from "@testing-library/react";
import { render, fireEvent, RenderResult, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
import { Room, RoomMember, MatrixError, IContent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { sleep } from "matrix-js-sdk/src/utils";
import { withClientContextRenderOptions, stubClient } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
@ -374,8 +373,7 @@ describe("<RoomPreviewBar />", () => {
const onJoinClick = jest.fn();
const onRejectClick = jest.fn();
const component = getComponent({ ...props, onJoinClick, onRejectClick });
await sleep(0);
expect(getPrimaryActionButton(component)).toBeTruthy();
await waitFor(() => expect(getPrimaryActionButton(component)).toBeTruthy());
if (expectSecondaryButton) expect(getSecondaryActionButton(component)).toBeFalsy();
fireEvent.click(getPrimaryActionButton(component)!);
expect(onJoinClick).toHaveBeenCalled();
@ -388,7 +386,7 @@ describe("<RoomPreviewBar />", () => {
it("renders error message", async () => {
const component = getComponent({ inviterName, invitedEmail });
await sleep(0);
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot();
});
@ -405,7 +403,7 @@ describe("<RoomPreviewBar />", () => {
it("renders invite message with invited email", async () => {
const component = getComponent({ inviterName, invitedEmail });
await sleep(0);
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot();
});
@ -421,7 +419,7 @@ describe("<RoomPreviewBar />", () => {
it("renders invite message with invited email", async () => {
const component = getComponent({ inviterName, invitedEmail });
await sleep(0);
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot();
});
@ -439,7 +437,7 @@ describe("<RoomPreviewBar />", () => {
it("renders email mismatch message when invite email mxid doesnt match", async () => {
MatrixClientPeg.safeGet().lookupThreePid = jest.fn().mockReturnValue({ mxid: "not userid" });
const component = getComponent({ inviterName, invitedEmail });
await sleep(0);
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot();
expect(MatrixClientPeg.safeGet().lookupThreePid).toHaveBeenCalledWith(
@ -453,7 +451,7 @@ describe("<RoomPreviewBar />", () => {
it("renders invite message when invite email mxid match", async () => {
MatrixClientPeg.safeGet().lookupThreePid = jest.fn().mockReturnValue({ mxid: userId });
const component = getComponent({ inviterName, invitedEmail });
await sleep(0);
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot();
await testJoinButton({ inviterName, invitedEmail }, false)();

View file

@ -30,7 +30,16 @@ import {
ThreepidMedium,
} from "matrix-js-sdk/src/matrix";
import { randomString } from "matrix-js-sdk/src/randomstring";
import { act, fireEvent, getByTestId, render, screen, waitFor, within } from "@testing-library/react";
import {
act,
fireEvent,
getByTestId,
render,
screen,
waitFor,
waitForElementToBeRemoved,
within,
} from "@testing-library/react";
import { mocked } from "jest-mock";
import userEvent from "@testing-library/user-event";
@ -244,7 +253,7 @@ describe("<Notifications />", () => {
// get component, wait for async data and force a render
const getComponentAndWait = async () => {
const component = getComponent();
await flushPromises();
await waitForElementToBeRemoved(() => component.queryAllByRole("progressbar"));
return component;
};
@ -527,7 +536,9 @@ describe("<Notifications />", () => {
// oneToOneRule is set to 'on'
// and is kind: 'underride'
const offToggle = screen.getByTestId(section + oneToOneRule.rule_id).querySelector('input[type="radio"]')!;
await act(() => {
fireEvent.click(offToggle);
});
await flushPromises();
@ -552,7 +563,9 @@ describe("<Notifications />", () => {
// oneToOneRule is set to 'on'
// and is kind: 'underride'
const offToggle = screen.getByTestId(section + oneToOneRule.rule_id).querySelector('input[type="radio"]')!;
await act(() => {
fireEvent.click(offToggle);
});
await flushPromises();
@ -576,7 +589,7 @@ describe("<Notifications />", () => {
await flushPromises();
// no error after after successful change
// no error after successful change
expect(
within(oneToOneRuleElement).queryByText(
"An error occurred when updating your notification preferences. Please try to toggle your option again.",
@ -716,7 +729,9 @@ describe("<Notifications />", () => {
mockClient.setPushRuleActions.mockRejectedValue("oups");
const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]')!;
await act(() => {
fireEvent.click(offToggle);
});
await flushPromises();
@ -814,7 +829,9 @@ describe("<Notifications />", () => {
mockClient.setPushRuleEnabled.mockRejectedValueOnce("oups");
await act(() => {
fireEvent.click(within(screen.getByTestId(section + keywordsRuleId)).getByLabelText("Off"));
});
await flushPromises();

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render, screen, within } from "@testing-library/react";
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { logger } from "matrix-js-sdk/src/logger";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
@ -99,6 +99,6 @@ describe("SetIntegrationManager", () => {
expect(logger.error).toHaveBeenCalledWith("Error changing integration manager provisioning");
expect(logger.error).toHaveBeenCalledWith("oups");
expect(within(integrationSection).getByRole("switch")).not.toBeChecked();
await waitFor(() => expect(within(integrationSection).getByRole("switch")).not.toBeChecked());
});
});

View file

@ -108,7 +108,7 @@ describe("<DeviceDetailHeading />", () => {
});
it("toggles out of editing mode when device name is saved successfully", async () => {
const { getByTestId } = render(getComponent());
const { getByTestId, findByTestId } = render(getComponent());
// start editing
fireEvent.click(getByTestId("device-heading-rename-cta"));
@ -118,12 +118,12 @@ describe("<DeviceDetailHeading />", () => {
await flushPromisesWithFakeTimers();
// read mode displayed
expect(getByTestId("device-detail-heading")).toBeTruthy();
await expect(findByTestId("device-detail-heading")).resolves.toBeTruthy();
});
it("displays error when device name fails to save", async () => {
const saveDeviceName = jest.fn().mockRejectedValueOnce("oups").mockResolvedValue({});
const { getByTestId, queryByText, container } = render(getComponent({ saveDeviceName }));
const { getByTestId, queryByText, findByText, container } = render(getComponent({ saveDeviceName }));
// start editing
fireEvent.click(getByTestId("device-heading-rename-cta"));
@ -136,7 +136,7 @@ describe("<DeviceDetailHeading />", () => {
await flushPromisesWithFakeTimers();
// error message displayed
expect(queryByText("Failed to set session name")).toBeTruthy();
await expect(findByText("Failed to set session name")).resolves.toBeTruthy();
// spinner removed
expect(container.getElementsByClassName("mx_Spinner").length).toBeFalsy();

View file

@ -120,8 +120,7 @@ describe("<FilteredDeviceList />", () => {
});
describe("filtering", () => {
const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) =>
await act(async () => {
const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) => {
const dropdown = container.querySelector('[aria-label="Filter devices"]');
fireEvent.click(dropdown as Element);
@ -129,7 +128,7 @@ describe("<FilteredDeviceList />", () => {
await flushPromises();
fireEvent.click(container.querySelector(`#device-list-filter__${option}`) as Element);
});
};
it("does not display filter description when filter is falsy", () => {
const { container } = render(getComponent({ filter: undefined }));

View file

@ -104,6 +104,7 @@ describe("<EmailAddress/>", () => {
"https://fake-url/",
),
);
await expect(screen.findByText("Complete")).resolves.not.toHaveAttribute("aria-disabled", "true");
fireEvent.click(screen.getByText("Complete"));
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.
@ -120,6 +121,7 @@ describe("<EmailAddress/>", () => {
it("Shows error dialog when share completion fails (UserFriendlyError)", async () => {
const fakeErrorText = "Fake UserFriendlyError error in test" as TranslationKey;
mockClient.bindThreePid.mockRejectedValue(new UserFriendlyError(fakeErrorText));
await expect(screen.findByText("Complete")).resolves.not.toHaveAttribute("aria-disabled", "true");
fireEvent.click(screen.getByText("Complete"));
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.
@ -132,6 +134,7 @@ describe("<EmailAddress/>", () => {
it("Shows error dialog when share completion fails (generic error)", async () => {
const fakeErrorText = "Fake plain error in test";
mockClient.bindThreePid.mockRejectedValue(new Error(fakeErrorText));
await expect(screen.findByText("Complete")).resolves.not.toHaveAttribute("aria-disabled", "true");
fireEvent.click(screen.getByText("Complete"));
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.

View file

@ -15,7 +15,16 @@ limitations under the License.
*/
import React from "react";
import { act, fireEvent, render, RenderResult, screen } from "@testing-library/react";
import {
act,
fireEvent,
render,
RenderResult,
screen,
waitFor,
waitForElementToBeRemoved,
within,
} from "@testing-library/react";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { logger } from "matrix-js-sdk/src/logger";
import { CryptoApi, DeviceVerificationStatus, VerificationRequest } from "matrix-js-sdk/src/crypto-api";
@ -146,7 +155,7 @@ describe("<SessionManagerTab />", () => {
// open device detail
const tile = getByTestId(`device-tile-${deviceId}`);
const label = isOpen ? "Hide details" : "Show details";
const toggle = tile.querySelector(`[aria-label="${label}"]`) as Element;
const toggle = within(tile).getByLabelText(label);
fireEvent.click(toggle);
};
@ -165,16 +174,14 @@ describe("<SessionManagerTab />", () => {
return getByTestId(`device-tile-${deviceId}`);
};
const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) =>
await act(async () => {
const dropdown = container.querySelector('[aria-label="Filter devices"]');
const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) => {
const dropdown = within(container).getByLabelText("Filter devices");
fireEvent.click(dropdown as Element);
// tick to let dropdown render
await flushPromises();
fireEvent.click(dropdown);
screen.getByRole("listbox");
fireEvent.click(container.querySelector(`#device-list-filter__${option}`) as Element);
});
fireEvent.click(screen.getByTestId(`filter-option-${option}`) as Element);
};
const isDeviceSelected = (
getByTestId: ReturnType<typeof render>["getByTestId"],
@ -920,37 +927,31 @@ describe("<SessionManagerTab />", () => {
it("deletes a device when interactive auth is not required", async () => {
mockClient.deleteMultipleDevices.mockResolvedValue({});
mockClient.getDevices
.mockResolvedValueOnce({
mockClient.getDevices.mockResolvedValue({
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
})
});
const { getByTestId, findByTestId } = render(getComponent());
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
await toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
const signOutButton = await within(
await findByTestId(`device-detail-${alicesMobileDevice.device_id}`),
).findByTestId("device-detail-sign-out-cta");
// pretend it was really deleted on refresh
.mockResolvedValueOnce({
mockClient.getDevices.mockResolvedValueOnce({
devices: [alicesDevice, alicesOlderMobileDevice],
});
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`);
const signOutButton = deviceDetails.querySelector(
'[data-testid="device-detail-sign-out-cta"]',
) as Element;
fireEvent.click(signOutButton);
await confirmSignout(getByTestId);
// sign out button is disabled with spinner
expect(
(deviceDetails.querySelector('[data-testid="device-detail-sign-out-cta"]') as Element).getAttribute(
"aria-disabled",
),
).toEqual("true");
const prom = waitFor(() => expect(signOutButton).toHaveAttribute("aria-disabled", "true"));
fireEvent.click(signOutButton);
await confirmSignout(getByTestId);
await prom;
// delete called
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
[alicesMobileDevice.device_id],
@ -1008,9 +1009,7 @@ describe("<SessionManagerTab />", () => {
const { getByTestId, getByLabelText } = render(getComponent());
await act(async () => {
await flushPromises();
});
await act(flushPromises);
// reset mock count after initial load
mockClient.getDevices.mockClear();
@ -1570,9 +1569,7 @@ describe("<SessionManagerTab />", () => {
});
const { getByTestId, container } = render(getComponent());
await act(async () => {
await flushPromises();
});
await act(flushPromises);
// filter for inactive sessions
await setFilter(container, DeviceSecurityVariation.Inactive);
@ -1765,6 +1762,7 @@ describe("<SessionManagerTab />", () => {
await flushPromises();
fireEvent.click(getByText("Show QR code"));
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
await expect(findByTestId("login-with-qr")).resolves.toBeTruthy();
});

View file

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { act } from "@testing-library/react";
import { renderHook } from "@testing-library/react-hooks/dom";
import { waitFor } from "@testing-library/react";
import { IPushRules, MatrixClient, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
import { useNotificationSettings } from "../../src/hooks/useNotificationSettings";
@ -68,17 +68,13 @@ describe("useNotificationSettings", () => {
});
it("correctly parses model", async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook(() => useNotificationSettings(cli));
const { result } = renderHook(() => useNotificationSettings(cli));
expect(result.current.model).toEqual(null);
await waitForNextUpdate();
expect(result.current.model).toEqual(expectedModel);
await waitFor(() => expect(result.current.model).toEqual(expectedModel));
expect(result.current.hasPendingChanges).toBeFalsy();
});
});
it("correctly generates change calls", async () => {
await act(async () => {
const addPushRule = jest.fn(cli.addPushRule);
cli.addPushRule = addPushRule;
const deletePushRule = jest.fn(cli.deletePushRule);
@ -88,14 +84,12 @@ describe("useNotificationSettings", () => {
const setPushRuleActions = jest.fn(cli.setPushRuleActions);
cli.setPushRuleActions = setPushRuleActions;
const { result, waitForNextUpdate } = renderHook(() => useNotificationSettings(cli));
const { result } = renderHook(() => useNotificationSettings(cli));
expect(result.current.model).toEqual(null);
await waitForNextUpdate();
expect(result.current.model).toEqual(expectedModel);
await waitFor(() => expect(result.current.model).toEqual(expectedModel));
expect(result.current.hasPendingChanges).toBeFalsy();
await result.current.reconcile(DefaultNotificationSettings);
await waitForNextUpdate();
expect(result.current.hasPendingChanges).toBeFalsy();
await waitFor(() => expect(result.current.hasPendingChanges).toBeFalsy());
expect(addPushRule).toHaveBeenCalledTimes(0);
expect(deletePushRule).toHaveBeenCalledTimes(9);
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justjann3");
@ -117,12 +111,7 @@ describe("useNotificationSettings", () => {
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.Message, true);
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.EncryptedDM, true);
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.DM, true);
expect(setPushRuleEnabled).toHaveBeenCalledWith(
"global",
PushRuleKind.Override,
RuleId.SuppressNotices,
false,
);
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.SuppressNotices, false);
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.InviteToSelf, true);
expect(setPushRuleActions).toHaveBeenCalledTimes(6);
expect(setPushRuleActions).toHaveBeenCalledWith(
@ -162,5 +151,4 @@ describe("useNotificationSettings", () => {
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
);
});
});
});

View file

@ -81,9 +81,11 @@ describe("useUserOnboardingTasks", () => {
});
const { result, rerender } = renderHook(() => useUserOnboardingTasks(context.result.current));
expect(result.current[4].id).toBe("permission-notifications");
await waitFor(() => expect(result.current[4].completed).toBe(false));
expect(result.current[4].completed).toBe(false);
result.current[4].action!.onClick!({ type: "click" } as any);
await waitFor(() => {
rerender();
await waitFor(() => expect(result.current[4].completed).toBe(true));
expect(result.current[4].completed).toBe(true);
});
});
});

View file

@ -32,6 +32,8 @@ import {
TranslatedString,
UserFriendlyError,
TranslationKey,
IVariables,
Tags,
} from "../src/languageHandler";
import { stubClient } from "./test-utils";
import { setupLanguageMock } from "./setup/setupLanguage";
@ -214,13 +216,7 @@ describe("languageHandler JSX", function () {
const plurals = "common|and_n_others";
const variableSub = "slash_command|ignore_dialog_description";
type TestCase = [
string,
TranslationKey,
Record<string, unknown>,
Record<string, unknown> | undefined,
TranslatedString,
];
type TestCase = [string, TranslationKey, IVariables, Tags | undefined, TranslatedString];
const testCasesEn: TestCase[] = [
// description of the test case, translationString, variables, tags, expected result
["translates a basic string", basicString, {}, undefined, "Rooms"],

View file

@ -118,6 +118,9 @@ describe("ProxiedApiModule", () => {
describe("openDialog", () => {
it("should open dialog with a custom title and default options", async () => {
class MyDialogContent extends DialogContent {
public constructor(props: DialogProps) {
super(props);
}
trySubmit = async () => ({ result: true });
render = () => <p>This is my example content.</p>;
}
@ -147,6 +150,9 @@ describe("ProxiedApiModule", () => {
it("should open dialog with custom options", async () => {
class MyDialogContent extends DialogContent {
public constructor(props: DialogProps) {
super(props);
}
trySubmit = async () => ({ result: true });
render = () => <p>This is my example content.</p>;
}
@ -178,6 +184,9 @@ describe("ProxiedApiModule", () => {
it("should update the options from the opened dialog", async () => {
class MyDialogContent extends DialogContent {
public constructor(props: DialogProps) {
super(props);
}
trySubmit = async () => ({ result: true });
render = () => {
const onClick = () => {
@ -231,6 +240,9 @@ describe("ProxiedApiModule", () => {
it("should cancel the dialog from within the dialog", async () => {
class MyDialogContent extends DialogContent {
public constructor(props: DialogProps) {
super(props);
}
trySubmit = async () => ({ result: true });
render = () => (
<button type="button" onClick={this.props.cancel}>

View file

@ -181,4 +181,5 @@ export const mockClientMethodsCrypto = (): Partial<
export const mockClientMethodsRooms = (rooms: Room[] = []): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
getRooms: jest.fn().mockReturnValue(rooms),
getRoom: jest.fn((roomId) => rooms.find((r) => r.roomId === roomId) ?? null),
isRoomEncrypted: jest.fn(),
});

View file

@ -109,11 +109,9 @@ describe("VoiceBroadcastPreRecordingPip", () => {
describe("and double clicking »Go live«", () => {
beforeEach(async () => {
await act(async () => {
await userEvent.click(screen.getByText("Go live"));
await userEvent.click(screen.getByText("Go live"));
});
});
it("should call start once", () => {
expect(preRecording.start).toHaveBeenCalledTimes(1);

View file

@ -117,11 +117,9 @@ describe("VoiceBroadcastRecordingPip", () => {
describe("and selecting another input device", () => {
beforeEach(async () => {
await act(async () => {
await userEvent.click(screen.getByLabelText("Change input device"));
await userEvent.click(screen.getByText("Device 1"));
});
});
it("should select the device and pause and resume the broadcast", () => {
expect(MediaDeviceHandler.instance.setDevice).toHaveBeenCalledWith(
@ -199,8 +197,8 @@ describe("VoiceBroadcastRecordingPip", () => {
client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error);
});
it("should render a paused recording", () => {
expect(screen.getByLabelText("resume voice broadcast")).toBeInTheDocument();
it("should render a paused recording", async () => {
await expect(screen.findByLabelText("resume voice broadcast")).resolves.toBeInTheDocument();
});
});
});