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 () => { it("while we are checking the sync store", async () => {
const rendered = getComponent({}); const rendered = getComponent({});
await flushPromises();
expect(rendered.getByTestId("spinner")).toBeInTheDocument(); expect(rendered.getByTestId("spinner")).toBeInTheDocument();
// now a third session starts // now a third session starts

View file

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

View file

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

View file

@ -429,7 +429,7 @@ describe("InviteDialog", () => {
describe("when clicking »Start DM anyway«", () => { describe("when clicking »Start DM anyway«", () => {
beforeEach(async () => { 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", () => { it("should start the DM", () => {

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,10 @@ import PlatformPeg from "../../../../src/PlatformPeg";
describe("<SpellCheckLanguagesDropdown />", () => { describe("<SpellCheckLanguagesDropdown />", () => {
it("renders as expected", async () => { 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); PlatformPeg.set(platform);
const { asFragment } = render( 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 () => { it("should generate a thumbnail if one isn't included for animated media", async () => {
Object.defineProperty(global.Image.prototype, "src", { Object.defineProperty(global.Image.prototype, "src", {
set(src) { set(src) {
window.setTimeout(() => this.onload()); window.setTimeout(() => this.onload?.());
}, },
}); });
Object.defineProperty(global.Image.prototype, "height", { 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 { LocationAssetType, ClientEvent, RoomMember, SyncState } from "matrix-js-sdk/src/matrix";
import * as maplibregl from "maplibre-gl"; import * as maplibregl from "maplibre-gl";
import { logger } from "matrix-js-sdk/src/logger"; 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 MLocationBody from "../../../../src/components/views/messages/MLocationBody";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
@ -64,9 +65,11 @@ describe("MLocationBody", () => {
}); });
const component = getComponent(); const component = getComponent();
sleep(10).then(() => {
// simulate error initialising map in maplibregl // simulate error initialising map in maplibregl
// @ts-ignore // @ts-ignore
mockMap.emit("error", { status: 404 }); mockMap.emit("error", { status: 404 });
});
return component; return component;
}; };
@ -100,9 +103,10 @@ describe("MLocationBody", () => {
expect(component.container.querySelector(".mx_EventTile_body")).toMatchSnapshot(); 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(); 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", () => { it("should clear the error on reconnect", () => {

View file

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

View file

@ -24,6 +24,7 @@ import {
getMockClientWithEventEmitter, getMockClientWithEventEmitter,
makePollEndEvent, makePollEndEvent,
makePollStartEvent, makePollStartEvent,
mockClientMethodsRooms,
mockClientMethodsUser, mockClientMethodsUser,
mockIntlDateTimeFormat, mockIntlDateTimeFormat,
setupRoomWithPollEvents, setupRoomWithPollEvents,
@ -41,7 +42,7 @@ describe("<PollHistory />", () => {
const roomId = "!room:domain.org"; const roomId = "!room:domain.org";
const mockClient = getMockClientWithEventEmitter({ const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId), ...mockClientMethodsUser(userId),
getRoom: jest.fn(), ...mockClientMethodsRooms([]),
relations: jest.fn(), relations: jest.fn(),
decryptEventIfNeeded: jest.fn(), decryptEventIfNeeded: jest.fn(),
getOrCreateFilter: jest.fn(), getOrCreateFilter: jest.fn(),
@ -117,7 +118,7 @@ describe("<PollHistory />", () => {
expect(getByText("Loading polls")).toBeInTheDocument(); expect(getByText("Loading polls")).toBeInTheDocument();
// flush filter creation request // flush filter creation request
await flushPromises(); await act(flushPromises);
expect(liveTimeline.getPaginationToken).toHaveBeenCalledWith(EventTimeline.BACKWARDS); expect(liveTimeline.getPaginationToken).toHaveBeenCalledWith(EventTimeline.BACKWARDS);
expect(mockClient.paginateEventTimeline).toHaveBeenCalledWith(liveTimeline, { backwards: true }); expect(mockClient.paginateEventTimeline).toHaveBeenCalledWith(liveTimeline, { backwards: true });
@ -147,7 +148,7 @@ describe("<PollHistory />", () => {
); );
// flush filter creation request // flush filter creation request
await flushPromises(); await act(flushPromises);
// once per page // once per page
expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(3); 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 () => { it("renders a no polls message when there are no active polls in the room", async () => {
const { getByText } = getComponent(); const { getByText } = getComponent();
await flushPromises(); await act(flushPromises);
expect(getByText("There are no active polls in this room")).toBeTruthy(); 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); await setupRoomWithPollEvents([pollStartEvent], responses, [pollEndEvent], mockClient, room);
const poll = room.polls.get(pollId)!; const poll = room.polls.get(pollId)!;
const { getByText, queryByText } = getComponent({ event: pollStartEvent, poll }); const { getByText, queryByText, findByText } = getComponent({ event: pollStartEvent, poll });
// fetch relations // fetch relations
await flushPromises(); await flushPromises();
@ -174,7 +174,7 @@ describe("<PollListItemEnded />", () => {
]); ]);
// updated with more responses // 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(getByText("Nissan Silvia S15")).toBeInTheDocument();
expect(queryByText("Mitsubishi Lancer Evolution IX")).not.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. limitations under the License.
*/ */
import { render } from "@testing-library/react"; import { render, waitFor } from "@testing-library/react";
import { import {
MatrixEvent, MatrixEvent,
MsgType, MsgType,
@ -79,21 +79,23 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull(); 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); const { container } = getComponent(room);
expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeFalsy(); expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeFalsy();
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 1); room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 1);
await waitFor(() => {
expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeTruthy(); expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeTruthy();
expect(isIndicatorOfType(container, "notification")).toBe(true); expect(isIndicatorOfType(container, "notification")).toBe(true);
});
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 1); 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.Total, 0);
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 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 () => { it("thread activity does change the thread button", async () => {
@ -122,7 +124,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
}, },
}); });
room.addReceipt(receipt); 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. // Sending the last event should clear the notification.
let event = mkEvent({ let event = mkEvent({
@ -140,7 +142,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
}, },
}); });
room.addLiveEvents([event]); 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. // Mark it as unread again.
event = mkEvent({ event = mkEvent({
@ -158,7 +160,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
}, },
}); });
room.addLiveEvents([event]); 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. // Sending a read receipt on an earlier event shouldn't do anything.
receipt = new MatrixEvent({ receipt = new MatrixEvent({
@ -173,7 +175,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
}, },
}); });
room.addReceipt(receipt); 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. // Sending a receipt on the latest event should clear the notification.
receipt = new MatrixEvent({ receipt = new MatrixEvent({
@ -188,6 +190,6 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
}, },
}); });
room.addReceipt(receipt); 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> => { const mountPins = async (room: Room): Promise<RenderResult> => {
let pins!: RenderResult; const pins = render(
await act(async () => {
pins = render(
<MatrixClientContext.Provider value={cli}> <MatrixClientContext.Provider value={cli}>
<PinnedMessagesCard <PinnedMessagesCard
room={room} room={room}
@ -99,8 +97,7 @@ describe("<PinnedMessagesCard />", () => {
</MatrixClientContext.Provider>, </MatrixClientContext.Provider>,
); );
// Wait a tick for state updates // Wait a tick for state updates
await sleep(0); await act(() => sleep(0));
});
return pins; return pins;
}; };
@ -313,7 +310,6 @@ describe("<PinnedMessagesCard />", () => {
it("should show spinner whilst loading", async () => { it("should show spinner whilst loading", async () => {
const room = mkRoom([], [pin1]); const room = mkRoom([], [pin1]);
mountPins(room); mountPins(room);
const spinner = await screen.findByTestId("spinner"); await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
await waitForElementToBeRemoved(spinner);
}); });
}); });

View file

@ -287,10 +287,10 @@ describe("<UserInfo />", () => {
expect(spy).not.toHaveBeenCalled(); 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 }); renderComponent({ phase: RightPanelPhases.EncryptionPanel, verificationRequest });
screen.getByTestId("base-card-close-button").focus(); 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 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 { Room, MatrixClient, RoomState, RoomMember, User, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { mocked, MockedObject } from "jest-mock"; import { mocked, MockedObject } from "jest-mock";
@ -30,6 +38,7 @@ import {
filterConsole, filterConsole,
flushPromises, flushPromises,
getMockClientWithEventEmitter, getMockClientWithEventEmitter,
mockClientMethodsRooms,
mockClientMethodsUser, mockClientMethodsUser,
} from "../../../test-utils"; } from "../../../test-utils";
import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents"; import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
@ -358,6 +367,7 @@ describe("MemberList", () => {
mocked(shouldShowComponent).mockReturnValue(true); mocked(shouldShowComponent).mockReturnValue(true);
client = getMockClientWithEventEmitter({ client = getMockClientWithEventEmitter({
...mockClientMethodsUser(), ...mockClientMethodsUser(),
...mockClientMethodsRooms(),
getRoom: jest.fn(), getRoom: jest.fn(),
hasLazyLoadMembersEnabled: jest.fn(), hasLazyLoadMembersEnabled: jest.fn(),
}); });
@ -372,7 +382,7 @@ describe("MemberList", () => {
const renderComponent = () => { const renderComponent = () => {
const context = new TestSdkContext(); const context = new TestSdkContext();
context.client = client; context.client = client;
render( return render(
<SDKContext.Provider value={context}> <SDKContext.Provider value={context}>
<MemberList <MemberList
searchQuery="" searchQuery=""
@ -407,7 +417,10 @@ describe("MemberList", () => {
await flushPromises(); await flushPromises();
// button rendered but disabled // 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 () => { 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, "getMyMembership").mockReturnValue(KnownMembership.Join);
jest.spyOn(room, "canInvite").mockReturnValue(true); jest.spyOn(room, "canInvite").mockReturnValue(true);
renderComponent(); const { getByRole } = renderComponent();
await flushPromises(); 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({ expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: "view_invite", action: "view_invite",

View file

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

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from "react"; import React from "react";
import { MockedObject } from "jest-mock"; import { MockedObject } from "jest-mock";
import { Room } from "matrix-js-sdk/src/matrix"; 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 { VideoRoomChatButton } from "../../../../../src/components/views/rooms/RoomHeader/VideoRoomChatButton";
import { SDKContext, SdkContextClass } from "../../../../../src/contexts/SDKContext"; import { SDKContext, SdkContextClass } from "../../../../../src/contexts/SDKContext";
@ -94,7 +94,7 @@ describe("<VideoRoomChatButton />", () => {
expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy(); 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(); const room = makeRoom();
// start in read state // start in read state
const notificationState = mockRoomNotificationState(room, NotificationLevel.None); const notificationState = mockRoomNotificationState(room, NotificationLevel.None);
@ -108,10 +108,10 @@ describe("<VideoRoomChatButton />", () => {
notificationState.emit(NotificationStateEvents.Update); notificationState.emit(NotificationStateEvents.Update);
// unread marker // 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(); const room = makeRoom();
// start in unread state // start in unread state
const notificationState = mockRoomNotificationState(room, NotificationLevel.Highlight); const notificationState = mockRoomNotificationState(room, NotificationLevel.Highlight);
@ -125,6 +125,6 @@ describe("<VideoRoomChatButton />", () => {
notificationState.emit(NotificationStateEvents.Update); notificationState.emit(NotificationStateEvents.Update);
// unread marker cleared // 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 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 { Room, RoomMember, MatrixError, IContent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { sleep } from "matrix-js-sdk/src/utils";
import { withClientContextRenderOptions, stubClient } from "../../../test-utils"; import { withClientContextRenderOptions, stubClient } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
@ -374,8 +373,7 @@ describe("<RoomPreviewBar />", () => {
const onJoinClick = jest.fn(); const onJoinClick = jest.fn();
const onRejectClick = jest.fn(); const onRejectClick = jest.fn();
const component = getComponent({ ...props, onJoinClick, onRejectClick }); const component = getComponent({ ...props, onJoinClick, onRejectClick });
await sleep(0); await waitFor(() => expect(getPrimaryActionButton(component)).toBeTruthy());
expect(getPrimaryActionButton(component)).toBeTruthy();
if (expectSecondaryButton) expect(getSecondaryActionButton(component)).toBeFalsy(); if (expectSecondaryButton) expect(getSecondaryActionButton(component)).toBeFalsy();
fireEvent.click(getPrimaryActionButton(component)!); fireEvent.click(getPrimaryActionButton(component)!);
expect(onJoinClick).toHaveBeenCalled(); expect(onJoinClick).toHaveBeenCalled();
@ -388,7 +386,7 @@ describe("<RoomPreviewBar />", () => {
it("renders error message", async () => { it("renders error message", async () => {
const component = getComponent({ inviterName, invitedEmail }); const component = getComponent({ inviterName, invitedEmail });
await sleep(0); await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot(); expect(getMessage(component)).toMatchSnapshot();
}); });
@ -405,7 +403,7 @@ describe("<RoomPreviewBar />", () => {
it("renders invite message with invited email", async () => { it("renders invite message with invited email", async () => {
const component = getComponent({ inviterName, invitedEmail }); const component = getComponent({ inviterName, invitedEmail });
await sleep(0); await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot(); expect(getMessage(component)).toMatchSnapshot();
}); });
@ -421,7 +419,7 @@ describe("<RoomPreviewBar />", () => {
it("renders invite message with invited email", async () => { it("renders invite message with invited email", async () => {
const component = getComponent({ inviterName, invitedEmail }); const component = getComponent({ inviterName, invitedEmail });
await sleep(0); await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot(); expect(getMessage(component)).toMatchSnapshot();
}); });
@ -439,7 +437,7 @@ describe("<RoomPreviewBar />", () => {
it("renders email mismatch message when invite email mxid doesnt match", async () => { it("renders email mismatch message when invite email mxid doesnt match", async () => {
MatrixClientPeg.safeGet().lookupThreePid = jest.fn().mockReturnValue({ mxid: "not userid" }); MatrixClientPeg.safeGet().lookupThreePid = jest.fn().mockReturnValue({ mxid: "not userid" });
const component = getComponent({ inviterName, invitedEmail }); const component = getComponent({ inviterName, invitedEmail });
await sleep(0); await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot(); expect(getMessage(component)).toMatchSnapshot();
expect(MatrixClientPeg.safeGet().lookupThreePid).toHaveBeenCalledWith( expect(MatrixClientPeg.safeGet().lookupThreePid).toHaveBeenCalledWith(
@ -453,7 +451,7 @@ describe("<RoomPreviewBar />", () => {
it("renders invite message when invite email mxid match", async () => { it("renders invite message when invite email mxid match", async () => {
MatrixClientPeg.safeGet().lookupThreePid = jest.fn().mockReturnValue({ mxid: userId }); MatrixClientPeg.safeGet().lookupThreePid = jest.fn().mockReturnValue({ mxid: userId });
const component = getComponent({ inviterName, invitedEmail }); const component = getComponent({ inviterName, invitedEmail });
await sleep(0); await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
expect(getMessage(component)).toMatchSnapshot(); expect(getMessage(component)).toMatchSnapshot();
await testJoinButton({ inviterName, invitedEmail }, false)(); await testJoinButton({ inviterName, invitedEmail }, false)();

View file

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

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from "react"; 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 { logger } from "matrix-js-sdk/src/logger";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; 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("Error changing integration manager provisioning");
expect(logger.error).toHaveBeenCalledWith("oups"); 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 () => { it("toggles out of editing mode when device name is saved successfully", async () => {
const { getByTestId } = render(getComponent()); const { getByTestId, findByTestId } = render(getComponent());
// start editing // start editing
fireEvent.click(getByTestId("device-heading-rename-cta")); fireEvent.click(getByTestId("device-heading-rename-cta"));
@ -118,12 +118,12 @@ describe("<DeviceDetailHeading />", () => {
await flushPromisesWithFakeTimers(); await flushPromisesWithFakeTimers();
// read mode displayed // 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 () => { it("displays error when device name fails to save", async () => {
const saveDeviceName = jest.fn().mockRejectedValueOnce("oups").mockResolvedValue({}); const saveDeviceName = jest.fn().mockRejectedValueOnce("oups").mockResolvedValue({});
const { getByTestId, queryByText, container } = render(getComponent({ saveDeviceName })); const { getByTestId, queryByText, findByText, container } = render(getComponent({ saveDeviceName }));
// start editing // start editing
fireEvent.click(getByTestId("device-heading-rename-cta")); fireEvent.click(getByTestId("device-heading-rename-cta"));
@ -136,7 +136,7 @@ describe("<DeviceDetailHeading />", () => {
await flushPromisesWithFakeTimers(); await flushPromisesWithFakeTimers();
// error message displayed // error message displayed
expect(queryByText("Failed to set session name")).toBeTruthy(); await expect(findByText("Failed to set session name")).resolves.toBeTruthy();
// spinner removed // spinner removed
expect(container.getElementsByClassName("mx_Spinner").length).toBeFalsy(); expect(container.getElementsByClassName("mx_Spinner").length).toBeFalsy();

View file

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

View file

@ -104,6 +104,7 @@ describe("<EmailAddress/>", () => {
"https://fake-url/", "https://fake-url/",
), ),
); );
await expect(screen.findByText("Complete")).resolves.not.toHaveAttribute("aria-disabled", "true");
fireEvent.click(screen.getByText("Complete")); fireEvent.click(screen.getByText("Complete"));
// Expect error dialog/modal to be shown. We have to wait for the UI to transition. // 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 () => { it("Shows error dialog when share completion fails (UserFriendlyError)", async () => {
const fakeErrorText = "Fake UserFriendlyError error in test" as TranslationKey; const fakeErrorText = "Fake UserFriendlyError error in test" as TranslationKey;
mockClient.bindThreePid.mockRejectedValue(new UserFriendlyError(fakeErrorText)); mockClient.bindThreePid.mockRejectedValue(new UserFriendlyError(fakeErrorText));
await expect(screen.findByText("Complete")).resolves.not.toHaveAttribute("aria-disabled", "true");
fireEvent.click(screen.getByText("Complete")); fireEvent.click(screen.getByText("Complete"));
// Expect error dialog/modal to be shown. We have to wait for the UI to transition. // 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 () => { it("Shows error dialog when share completion fails (generic error)", async () => {
const fakeErrorText = "Fake plain error in test"; const fakeErrorText = "Fake plain error in test";
mockClient.bindThreePid.mockRejectedValue(new Error(fakeErrorText)); mockClient.bindThreePid.mockRejectedValue(new Error(fakeErrorText));
await expect(screen.findByText("Complete")).resolves.not.toHaveAttribute("aria-disabled", "true");
fireEvent.click(screen.getByText("Complete")); fireEvent.click(screen.getByText("Complete"));
// Expect error dialog/modal to be shown. We have to wait for the UI to transition. // 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 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 { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { CryptoApi, DeviceVerificationStatus, VerificationRequest } from "matrix-js-sdk/src/crypto-api"; import { CryptoApi, DeviceVerificationStatus, VerificationRequest } from "matrix-js-sdk/src/crypto-api";
@ -146,7 +155,7 @@ describe("<SessionManagerTab />", () => {
// open device detail // open device detail
const tile = getByTestId(`device-tile-${deviceId}`); const tile = getByTestId(`device-tile-${deviceId}`);
const label = isOpen ? "Hide details" : "Show details"; 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); fireEvent.click(toggle);
}; };
@ -165,16 +174,14 @@ describe("<SessionManagerTab />", () => {
return getByTestId(`device-tile-${deviceId}`); return getByTestId(`device-tile-${deviceId}`);
}; };
const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) => const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) => {
await act(async () => { const dropdown = within(container).getByLabelText("Filter devices");
const dropdown = container.querySelector('[aria-label="Filter devices"]');
fireEvent.click(dropdown as Element); fireEvent.click(dropdown);
// tick to let dropdown render screen.getByRole("listbox");
await flushPromises();
fireEvent.click(container.querySelector(`#device-list-filter__${option}`) as Element); fireEvent.click(screen.getByTestId(`filter-option-${option}`) as Element);
}); };
const isDeviceSelected = ( const isDeviceSelected = (
getByTestId: ReturnType<typeof render>["getByTestId"], getByTestId: ReturnType<typeof render>["getByTestId"],
@ -920,37 +927,31 @@ describe("<SessionManagerTab />", () => {
it("deletes a device when interactive auth is not required", async () => { it("deletes a device when interactive auth is not required", async () => {
mockClient.deleteMultipleDevices.mockResolvedValue({}); mockClient.deleteMultipleDevices.mockResolvedValue({});
mockClient.getDevices mockClient.getDevices.mockResolvedValue({
.mockResolvedValueOnce({
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice], 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 // pretend it was really deleted on refresh
.mockResolvedValueOnce({ mockClient.getDevices.mockResolvedValueOnce({
devices: [alicesDevice, alicesOlderMobileDevice], 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 // sign out button is disabled with spinner
expect( const prom = waitFor(() => expect(signOutButton).toHaveAttribute("aria-disabled", "true"));
(deviceDetails.querySelector('[data-testid="device-detail-sign-out-cta"]') as Element).getAttribute(
"aria-disabled", fireEvent.click(signOutButton);
), await confirmSignout(getByTestId);
).toEqual("true"); await prom;
// delete called // delete called
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith( expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
[alicesMobileDevice.device_id], [alicesMobileDevice.device_id],
@ -1008,9 +1009,7 @@ describe("<SessionManagerTab />", () => {
const { getByTestId, getByLabelText } = render(getComponent()); const { getByTestId, getByLabelText } = render(getComponent());
await act(async () => { await act(flushPromises);
await flushPromises();
});
// reset mock count after initial load // reset mock count after initial load
mockClient.getDevices.mockClear(); mockClient.getDevices.mockClear();
@ -1570,9 +1569,7 @@ describe("<SessionManagerTab />", () => {
}); });
const { getByTestId, container } = render(getComponent()); const { getByTestId, container } = render(getComponent());
await act(async () => { await act(flushPromises);
await flushPromises();
});
// filter for inactive sessions // filter for inactive sessions
await setFilter(container, DeviceSecurityVariation.Inactive); await setFilter(container, DeviceSecurityVariation.Inactive);
@ -1765,6 +1762,7 @@ describe("<SessionManagerTab />", () => {
await flushPromises(); await flushPromises();
fireEvent.click(getByText("Show QR code")); fireEvent.click(getByText("Show QR code"));
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
await expect(findByTestId("login-with-qr")).resolves.toBeTruthy(); 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. limitations under the License.
*/ */
import { act } from "@testing-library/react";
import { renderHook } from "@testing-library/react-hooks/dom"; 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 { IPushRules, MatrixClient, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
import { useNotificationSettings } from "../../src/hooks/useNotificationSettings"; import { useNotificationSettings } from "../../src/hooks/useNotificationSettings";
@ -68,17 +68,13 @@ describe("useNotificationSettings", () => {
}); });
it("correctly parses model", async () => { it("correctly parses model", async () => {
await act(async () => { const { result } = renderHook(() => useNotificationSettings(cli));
const { result, waitForNextUpdate } = renderHook(() => useNotificationSettings(cli));
expect(result.current.model).toEqual(null); expect(result.current.model).toEqual(null);
await waitForNextUpdate(); await waitFor(() => expect(result.current.model).toEqual(expectedModel));
expect(result.current.model).toEqual(expectedModel);
expect(result.current.hasPendingChanges).toBeFalsy(); expect(result.current.hasPendingChanges).toBeFalsy();
}); });
});
it("correctly generates change calls", async () => { it("correctly generates change calls", async () => {
await act(async () => {
const addPushRule = jest.fn(cli.addPushRule); const addPushRule = jest.fn(cli.addPushRule);
cli.addPushRule = addPushRule; cli.addPushRule = addPushRule;
const deletePushRule = jest.fn(cli.deletePushRule); const deletePushRule = jest.fn(cli.deletePushRule);
@ -88,14 +84,12 @@ describe("useNotificationSettings", () => {
const setPushRuleActions = jest.fn(cli.setPushRuleActions); const setPushRuleActions = jest.fn(cli.setPushRuleActions);
cli.setPushRuleActions = setPushRuleActions; cli.setPushRuleActions = setPushRuleActions;
const { result, waitForNextUpdate } = renderHook(() => useNotificationSettings(cli)); const { result } = renderHook(() => useNotificationSettings(cli));
expect(result.current.model).toEqual(null); expect(result.current.model).toEqual(null);
await waitForNextUpdate(); await waitFor(() => expect(result.current.model).toEqual(expectedModel));
expect(result.current.model).toEqual(expectedModel);
expect(result.current.hasPendingChanges).toBeFalsy(); expect(result.current.hasPendingChanges).toBeFalsy();
await result.current.reconcile(DefaultNotificationSettings); await result.current.reconcile(DefaultNotificationSettings);
await waitForNextUpdate(); await waitFor(() => expect(result.current.hasPendingChanges).toBeFalsy());
expect(result.current.hasPendingChanges).toBeFalsy();
expect(addPushRule).toHaveBeenCalledTimes(0); expect(addPushRule).toHaveBeenCalledTimes(0);
expect(deletePushRule).toHaveBeenCalledTimes(9); expect(deletePushRule).toHaveBeenCalledTimes(9);
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justjann3"); 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.Message, true);
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.EncryptedDM, true); expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.EncryptedDM, true);
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.DM, true); expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.DM, true);
expect(setPushRuleEnabled).toHaveBeenCalledWith( expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.SuppressNotices, false);
"global",
PushRuleKind.Override,
RuleId.SuppressNotices,
false,
);
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.InviteToSelf, true); expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.InviteToSelf, true);
expect(setPushRuleActions).toHaveBeenCalledTimes(6); expect(setPushRuleActions).toHaveBeenCalledTimes(6);
expect(setPushRuleActions).toHaveBeenCalledWith( expect(setPushRuleActions).toHaveBeenCalledWith(
@ -163,4 +152,3 @@ describe("useNotificationSettings", () => {
); );
}); });
}); });
});

View file

@ -81,9 +81,11 @@ describe("useUserOnboardingTasks", () => {
}); });
const { result, rerender } = renderHook(() => useUserOnboardingTasks(context.result.current)); const { result, rerender } = renderHook(() => useUserOnboardingTasks(context.result.current));
expect(result.current[4].id).toBe("permission-notifications"); 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); result.current[4].action!.onClick!({ type: "click" } as any);
await waitFor(() => {
rerender(); 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, TranslatedString,
UserFriendlyError, UserFriendlyError,
TranslationKey, TranslationKey,
IVariables,
Tags,
} from "../src/languageHandler"; } from "../src/languageHandler";
import { stubClient } from "./test-utils"; import { stubClient } from "./test-utils";
import { setupLanguageMock } from "./setup/setupLanguage"; import { setupLanguageMock } from "./setup/setupLanguage";
@ -214,13 +216,7 @@ describe("languageHandler JSX", function () {
const plurals = "common|and_n_others"; const plurals = "common|and_n_others";
const variableSub = "slash_command|ignore_dialog_description"; const variableSub = "slash_command|ignore_dialog_description";
type TestCase = [ type TestCase = [string, TranslationKey, IVariables, Tags | undefined, TranslatedString];
string,
TranslationKey,
Record<string, unknown>,
Record<string, unknown> | undefined,
TranslatedString,
];
const testCasesEn: TestCase[] = [ const testCasesEn: TestCase[] = [
// description of the test case, translationString, variables, tags, expected result // description of the test case, translationString, variables, tags, expected result
["translates a basic string", basicString, {}, undefined, "Rooms"], ["translates a basic string", basicString, {}, undefined, "Rooms"],

View file

@ -118,6 +118,9 @@ describe("ProxiedApiModule", () => {
describe("openDialog", () => { describe("openDialog", () => {
it("should open dialog with a custom title and default options", async () => { it("should open dialog with a custom title and default options", async () => {
class MyDialogContent extends DialogContent { class MyDialogContent extends DialogContent {
public constructor(props: DialogProps) {
super(props);
}
trySubmit = async () => ({ result: true }); trySubmit = async () => ({ result: true });
render = () => <p>This is my example content.</p>; render = () => <p>This is my example content.</p>;
} }
@ -147,6 +150,9 @@ describe("ProxiedApiModule", () => {
it("should open dialog with custom options", async () => { it("should open dialog with custom options", async () => {
class MyDialogContent extends DialogContent { class MyDialogContent extends DialogContent {
public constructor(props: DialogProps) {
super(props);
}
trySubmit = async () => ({ result: true }); trySubmit = async () => ({ result: true });
render = () => <p>This is my example content.</p>; render = () => <p>This is my example content.</p>;
} }
@ -178,6 +184,9 @@ describe("ProxiedApiModule", () => {
it("should update the options from the opened dialog", async () => { it("should update the options from the opened dialog", async () => {
class MyDialogContent extends DialogContent { class MyDialogContent extends DialogContent {
public constructor(props: DialogProps) {
super(props);
}
trySubmit = async () => ({ result: true }); trySubmit = async () => ({ result: true });
render = () => { render = () => {
const onClick = () => { const onClick = () => {
@ -231,6 +240,9 @@ describe("ProxiedApiModule", () => {
it("should cancel the dialog from within the dialog", async () => { it("should cancel the dialog from within the dialog", async () => {
class MyDialogContent extends DialogContent { class MyDialogContent extends DialogContent {
public constructor(props: DialogProps) {
super(props);
}
trySubmit = async () => ({ result: true }); trySubmit = async () => ({ result: true });
render = () => ( render = () => (
<button type="button" onClick={this.props.cancel}> <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>> => ({ export const mockClientMethodsRooms = (rooms: Room[] = []): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
getRooms: jest.fn().mockReturnValue(rooms), getRooms: jest.fn().mockReturnValue(rooms),
getRoom: jest.fn((roomId) => rooms.find((r) => r.roomId === roomId) ?? null), 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«", () => { describe("and double clicking »Go live«", () => {
beforeEach(async () => { beforeEach(async () => {
await act(async () => {
await userEvent.click(screen.getByText("Go live")); await userEvent.click(screen.getByText("Go live"));
await userEvent.click(screen.getByText("Go live")); await userEvent.click(screen.getByText("Go live"));
}); });
});
it("should call start once", () => { it("should call start once", () => {
expect(preRecording.start).toHaveBeenCalledTimes(1); expect(preRecording.start).toHaveBeenCalledTimes(1);

View file

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