From b41cf5c5179d3ea9cb49db02ca2ac64de87feb34 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 5 Aug 2024 11:06:43 +0100 Subject: [PATCH 1/6] Make tests more resilient for React 18 upgrade Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../components/structures/MatrixChat-test.tsx | 1 - test/components/structures/UserMenu-test.tsx | 6 +- .../AccessSecretStorageDialog-test.tsx | 14 +- .../views/dialogs/InviteDialog-test.tsx | 2 +- .../dialogs/MessageEditHistoryDialog-test.tsx | 3 +- .../views/dialogs/RoomSettingsDialog-test.tsx | 8 +- .../views/dialogs/SpotlightDialog-test.tsx | 192 +++++++++--------- .../views/dialogs/UserSettingsDialog-test.tsx | 38 ++-- test/components/views/elements/Field-test.tsx | 20 +- .../views/elements/SearchWarning-test.tsx | 4 +- .../SpellCheckLanguagesDropdown-test.tsx | 5 +- .../views/messages/MImageBody-test.tsx | 2 +- .../views/messages/MLocationBody-test.tsx | 14 +- .../views/messages/MPollEndBody-test.tsx | 8 +- .../polls/pollHistory/PollHistory-test.tsx | 9 +- .../pollHistory/PollListItemEnded-test.tsx | 4 +- .../LegacyRoomHeaderButtons-test.tsx | 24 ++- .../right_panel/PinnedMessagesCard-test.tsx | 33 ++- .../views/right_panel/UserInfo-test.tsx | 4 +- .../views/rooms/MemberList-test.tsx | 32 ++- .../views/rooms/RoomHeader-test.tsx | 2 + .../RoomHeader/VideoRoomChatButton-test.tsx | 10 +- .../views/rooms/RoomPreviewBar-test.tsx | 16 +- .../views/settings/Notifications-test.tsx | 23 ++- .../settings/SetIntegrationManager-test.tsx | 4 +- .../devices/DeviceDetailHeading-test.tsx | 6 +- .../devices/FilteredDeviceList-test.tsx | 15 +- .../discovery/EmailAddresses-test.tsx | 5 +- .../tabs/user/SessionManagerTab-test.tsx | 82 ++++---- test/hooks/useNotificationSettings-test.tsx | 168 +++++++-------- test/hooks/useUserOnboardingTasks-test.tsx | 8 +- test/languageHandler-test.tsx | 10 +- test/modules/ProxiedModuleApi-test.tsx | 12 ++ test/test-utils/client.ts | 1 + .../VoiceBroadcastPreRecordingPip-test.tsx | 6 +- .../VoiceBroadcastRecordingPip-test.tsx | 12 +- 36 files changed, 417 insertions(+), 386 deletions(-) diff --git a/test/components/structures/MatrixChat-test.tsx b/test/components/structures/MatrixChat-test.tsx index d2d4178d1f8..bb6afa6da9d 100644 --- a/test/components/structures/MatrixChat-test.tsx +++ b/test/components/structures/MatrixChat-test.tsx @@ -1380,7 +1380,6 @@ describe("", () => { it("while we are checking the sync store", async () => { const rendered = getComponent({}); - await flushPromises(); expect(rendered.getByTestId("spinner")).toBeInTheDocument(); // now a third session starts diff --git a/test/components/structures/UserMenu-test.tsx b/test/components/structures/UserMenu-test.tsx index 24b75a87d15..6fea2453b0b 100644 --- a/test/components/structures/UserMenu-test.tsx +++ b/test/components/structures/UserMenu-test.tsx @@ -128,7 +128,7 @@ describe("", () => { const spy = jest.spyOn(defaultDispatcher, "dispatch"); screen.getByRole("button", { name: /User menu/i }).click(); - screen.getByRole("menuitem", { name: /Sign out/i }).click(); + await waitFor(() => screen.getByRole("menuitem", { name: /Sign out/i }).click()); await waitFor(() => { expect(spy).toHaveBeenCalledWith({ action: "logout" }); }); @@ -152,7 +152,7 @@ describe("", () => { const spy = jest.spyOn(defaultDispatcher, "dispatch"); screen.getByRole("button", { name: /User menu/i }).click(); - screen.getByRole("menuitem", { name: /Sign out/i }).click(); + await waitFor(() => screen.getByRole("menuitem", { name: /Sign out/i }).click()); await waitFor(() => { expect(spy).toHaveBeenCalledWith({ action: "logout" }); }); @@ -178,7 +178,7 @@ describe("", () => { const spy = jest.spyOn(Modal, "createDialog"); screen.getByRole("button", { name: /User menu/i }).click(); - screen.getByRole("menuitem", { name: /Sign out/i }).click(); + await waitFor(() => screen.getByRole("menuitem", { name: /Sign out/i }).click()); await waitFor(() => { expect(spy).toHaveBeenCalledWith(LogoutDialog); diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx index 00b7242d960..3ebd2487fbd 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx @@ -16,7 +16,7 @@ limitations under the License. import React, { ComponentProps } from "react"; import { SecretStorage, MatrixClient } from "matrix-js-sdk/src/matrix"; -import { act, fireEvent, render, screen } from "@testing-library/react"; +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Mocked } from "jest-mock"; @@ -129,11 +129,13 @@ describe("AccessSecretStorageDialog", () => { expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey); await submitDialog(); - expect( - screen.getByText( - "👎 Unable to access secret storage. Please verify that you entered the correct Security Phrase.", - ), - ).toBeInTheDocument(); + await waitFor(() => + expect( + screen.getByText( + "👎 Unable to access secret storage. Please verify that you entered the correct Security Phrase.", + ), + ).toBeInTheDocument(), + ); expect(screen.getByPlaceholderText("Security Phrase")).toHaveFocus(); }); diff --git a/test/components/views/dialogs/InviteDialog-test.tsx b/test/components/views/dialogs/InviteDialog-test.tsx index 4e1dca41936..e55dbf050ab 100644 --- a/test/components/views/dialogs/InviteDialog-test.tsx +++ b/test/components/views/dialogs/InviteDialog-test.tsx @@ -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", () => { diff --git a/test/components/views/dialogs/MessageEditHistoryDialog-test.tsx b/test/components/views/dialogs/MessageEditHistoryDialog-test.tsx index 2bd388103d2..f306c856f6b 100644 --- a/test/components/views/dialogs/MessageEditHistoryDialog-test.tsx +++ b/test/components/views/dialogs/MessageEditHistoryDialog-test.tsx @@ -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("", () => { async function renderComponent(): Promise { const result = render(); + await waitForElementToBeRemoved(() => result.queryByRole("progressbar")); await flushPromises(); return result; } diff --git a/test/components/views/dialogs/RoomSettingsDialog-test.tsx b/test/components/views/dialogs/RoomSettingsDialog-test.tsx index d028d43a939..4d53e714c7a 100644 --- a/test/components/views/dialogs/RoomSettingsDialog-test.tsx +++ b/test/components/views/dialogs/RoomSettingsDialog-test.tsx @@ -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("", () => { 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("", () => { 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(), + ); }); }); diff --git a/test/components/views/dialogs/SpotlightDialog-test.tsx b/test/components/views/dialogs/SpotlightDialog-test.tsx index f8fe3c00a7d..00584f3b4f9 100644 --- a/test/components/views/dialogs/SpotlightDialog-test.tsx +++ b/test/components/views/dialogs/SpotlightDialog-test.tsx @@ -27,12 +27,12 @@ import { } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import sanitizeHtml from "sanitize-html"; -import { fireEvent, render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import SpotlightDialog from "../../../../src/components/views/dialogs/spotlight/SpotlightDialog"; import { Filter } from "../../../../src/components/views/dialogs/spotlight/Filter"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; -import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../src/models/LocalRoom"; +import { LOCAL_ROOM_ID_PREFIX, LocalRoom } from "../../../../src/models/LocalRoom"; import { DirectoryMember, startDmOnFirstMessage } from "../../../../src/utils/direct-messages"; import DMRoomMap from "../../../../src/utils/DMRoomMap"; import { flushPromisesWithFakeTimers, mkRoom, stubClient } from "../../../test-utils"; @@ -157,6 +157,9 @@ describe("Spotlight Dialog", () => { let mockedClient: MatrixClient; beforeEach(() => { + SdkConfig.reset(); + localStorage.clear(); + SettingsStore.reset(); mockedClient = mockClient({ rooms: [testPublicRoom], users: [testPerson] }); testRoom = mkRoom(mockedClient, "!test23:example.com"); mocked(testRoom.getMyMembership).mockReturnValue(KnownMembership.Join); @@ -201,10 +204,12 @@ describe("Spotlight Dialog", () => { expect(filterChip).toBeInTheDocument(); expect(filterChip.innerHTML).toContain("Public rooms"); - const content = document.querySelector("#mx_SpotlightDialog_content")!; - const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBe(1); - expect(options[0].innerHTML).toContain(testPublicRoom.name); + await waitFor(() => { + const content = document.querySelector("#mx_SpotlightDialog_content")!; + const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBe(1); + expect(options[0].innerHTML).toContain(testPublicRoom.name); + }); }); it("with people filter", async () => { @@ -223,22 +228,18 @@ describe("Spotlight Dialog", () => { expect(filterChip).toBeInTheDocument(); expect(filterChip.innerHTML).toContain("People"); - const content = document.querySelector("#mx_SpotlightDialog_content")!; - const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBeGreaterThanOrEqual(1); - expect(options[0]!.innerHTML).toContain(testPerson.display_name); + await waitFor(() => { + const content = document.querySelector("#mx_SpotlightDialog_content")!; + const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBeGreaterThanOrEqual(1); + expect(options[0]!.innerHTML).toContain(testPerson.display_name); + }); }); }); describe("when MSC3946 dynamic room predecessors is enabled", () => { - beforeEach(() => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, excludeDefault) => { - if (settingName === "feature_dynamic_room_predecessors") { - return true; - } else { - return []; // SpotlightSearch.recentSearches - } - }); + beforeEach(async () => { + await SettingsStore.setValue("feature_dynamic_room_predecessors", null, SettingLevel.DEVICE, true); }); afterEach(() => { @@ -269,10 +270,12 @@ describe("Spotlight Dialog", () => { expect(filterChip).toBeInTheDocument(); expect(filterChip.innerHTML).toContain("Public rooms"); - const content = document.querySelector("#mx_SpotlightDialog_content")!; - const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBe(1); - expect(options[0]!.innerHTML).toContain(testPublicRoom.name); + await waitFor(() => { + const content = document.querySelector("#mx_SpotlightDialog_content")!; + const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBe(1); + expect(options[0]!.innerHTML).toContain(testPublicRoom.name); + }); // assert that getVisibleRooms is called without MSC3946 dynamic room predecessors expect(mockedClient.getVisibleRooms).toHaveBeenCalledWith(false); @@ -292,10 +295,12 @@ describe("Spotlight Dialog", () => { expect(filterChip).toBeInTheDocument(); expect(filterChip.innerHTML).toContain("People"); - const content = document.querySelector("#mx_SpotlightDialog_content")!; - const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBeGreaterThanOrEqual(1); - expect(options[0]!.innerHTML).toContain(testPerson.display_name); + await waitFor(() => { + const content = document.querySelector("#mx_SpotlightDialog_content")!; + const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBeGreaterThanOrEqual(1); + expect(options[0]!.innerHTML).toContain(testPerson.display_name); + }); }); }); @@ -380,11 +385,13 @@ describe("Spotlight Dialog", () => { jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - const content = document.querySelector("#mx_SpotlightDialog_content")!; - const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBeGreaterThanOrEqual(2); - expect(options[0]).toHaveTextContent("User Alpha"); - expect(options[1]).toHaveTextContent("User Beta"); + await waitFor(() => { + const content = document.querySelector("#mx_SpotlightDialog_content")!; + const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBeGreaterThanOrEqual(2); + expect(options[0]).toHaveTextContent("User Alpha"); + expect(options[1]).toHaveTextContent("User Beta"); + }); }); it("should not filter out users sent by the server even if a local suggestion gets filtered out", async () => { @@ -405,11 +412,13 @@ describe("Spotlight Dialog", () => { jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - const content = document.querySelector("#mx_SpotlightDialog_content")!; - const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBeGreaterThanOrEqual(2); - expect(options[0]).toHaveTextContent(testPerson.display_name!); - expect(options[1]).toHaveTextContent("User Beta"); + await waitFor(() => { + const content = document.querySelector("#mx_SpotlightDialog_content")!; + const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBeGreaterThanOrEqual(2); + expect(options[0]).toHaveTextContent(testPerson.display_name!); + expect(options[1]).toHaveTextContent("User Beta"); + }); }); it("show non-matching query members with DMs if they are present in the server search results", async () => { @@ -427,11 +436,13 @@ describe("Spotlight Dialog", () => { jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - const content = document.querySelector("#mx_SpotlightDialog_content")!; - const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBeGreaterThanOrEqual(2); - expect(options[0]).toHaveTextContent(testDMUserId); - expect(options[1]).toHaveTextContent("Bob Wonder"); + await waitFor(() => { + const content = document.querySelector("#mx_SpotlightDialog_content")!; + const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBeGreaterThanOrEqual(2); + expect(options[0]).toHaveTextContent(testDMUserId); + expect(options[1]).toHaveTextContent("Bob Wonder"); + }); }); it("don't sort the order of users sent by the server", async () => { @@ -449,11 +460,13 @@ describe("Spotlight Dialog", () => { jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - const content = document.querySelector("#mx_SpotlightDialog_content")!; - const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBeGreaterThanOrEqual(2); - expect(options[0]).toHaveTextContent("User Beta"); - expect(options[1]).toHaveTextContent("User Alpha"); + await waitFor(() => { + const content = document.querySelector("#mx_SpotlightDialog_content")!; + const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBeGreaterThanOrEqual(2); + expect(options[0]).toHaveTextContent("User Beta"); + expect(options[1]).toHaveTextContent("User Alpha"); + }); }); it("should start a DM when clicking a person", async () => { @@ -468,12 +481,13 @@ describe("Spotlight Dialog", () => { jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - const options = document.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBeGreaterThanOrEqual(1); - expect(options[0]!.innerHTML).toContain(testPerson.display_name); - - fireEvent.click(options[0]!); - expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockedClient, [new DirectoryMember(testPerson)]); + await waitFor(() => { + const options = document.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBeGreaterThanOrEqual(1); + expect(options[0]!.innerHTML).toContain(testPerson.display_name); + fireEvent.click(options[0]!); + expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockedClient, [new DirectoryMember(testPerson)]); + }); }); it("should pass via of the server being explored when joining room from directory", async () => { @@ -489,20 +503,22 @@ describe("Spotlight Dialog", () => { jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - const content = document.querySelector("#mx_SpotlightDialog_content")!; - const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); - expect(options.length).toBe(1); - expect(options[0].innerHTML).toContain(testPublicRoom.name); - - fireEvent.click(options[0].querySelector("[role='button']")!); - expect(defaultDispatcher.dispatch).toHaveBeenCalledTimes(1); - expect(defaultDispatcher.dispatch).toHaveBeenCalledWith( - expect.objectContaining({ - action: "view_room", - room_id: testPublicRoom.room_id, - via_servers: ["example.tld"], - }), - ); + await waitFor(() => { + const content = document.querySelector("#mx_SpotlightDialog_content")!; + const options = content.querySelectorAll("li.mx_SpotlightDialog_option"); + expect(options.length).toBe(1); + expect(options[0].innerHTML).toContain(testPublicRoom.name); + + fireEvent.click(options[0].querySelector("[role='button']")!); + expect(defaultDispatcher.dispatch).toHaveBeenCalledTimes(1); + expect(defaultDispatcher.dispatch).toHaveBeenCalledWith( + expect.objectContaining({ + action: "view_room", + room_id: testPublicRoom.room_id, + via_servers: ["example.tld"], + }), + ); + }); }); describe("nsfw public rooms filter", () => { @@ -533,13 +549,9 @@ describe("Spotlight Dialog", () => { guest_can_join: false, }; - beforeEach(() => { + beforeEach(async () => { mockedClient = mockClient({ rooms: [nsfwNameRoom, nsfwTopicRoom, potatoRoom], users: [testPerson] }); - SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, false); - }); - - afterAll(() => { - SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, false); + await SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, false); }); it("does not display rooms with nsfw keywords in results when showNsfwPublicRooms is falsy", async () => { @@ -549,22 +561,26 @@ describe("Spotlight Dialog", () => { jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - expect(screen.getByText(potatoRoom.name!)).toBeInTheDocument(); - expect(screen.queryByText(nsfwTopicRoom.name!)).not.toBeInTheDocument(); - expect(screen.queryByText(nsfwTopicRoom.name!)).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(potatoRoom.name!)).toBeInTheDocument(); + expect(screen.queryByText(nsfwTopicRoom.name!)).not.toBeInTheDocument(); + expect(screen.queryByText(nsfwTopicRoom.name!)).not.toBeInTheDocument(); + }); }); it("displays rooms with nsfw keywords in results when showNsfwPublicRooms is truthy", async () => { - SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, true); + await SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, true); render( null} />); // search is debounced jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - expect(screen.getByText(nsfwTopicRoom.name!)).toBeInTheDocument(); - expect(screen.getByText(nsfwNameRoom.name!)).toBeInTheDocument(); - expect(screen.getByText(potatoRoom.name!)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(nsfwTopicRoom.name!)).toBeInTheDocument(); + expect(screen.getByText(nsfwNameRoom.name!)).toBeInTheDocument(); + expect(screen.getByText(potatoRoom.name!)).toBeInTheDocument(); + }); }); }); @@ -575,7 +591,7 @@ describe("Spotlight Dialog", () => { jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - expect(screen.getByText("Failed to query public rooms")).toBeInTheDocument(); + await waitFor(() => expect(screen.getByText("Failed to query public rooms")).toBeInTheDocument()); }); describe("knock rooms", () => { @@ -601,9 +617,7 @@ describe("Spotlight Dialog", () => { describe("when disabling feature", () => { beforeEach(async () => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => - setting === "feature_ask_to_join" ? false : [], - ); + await SettingsStore.setValue("feature_ask_to_join", null, SettingLevel.DEVICE, false); render( {}} />); @@ -611,7 +625,7 @@ describe("Spotlight Dialog", () => { jest.advanceTimersByTime(200); await flushPromisesWithFakeTimers(); - fireEvent.click(screen.getByRole("button", { name: "View" })); + fireEvent.click(await screen.findByRole("button", { name: "View" })); }); it("should not skip to auto join", async () => { @@ -625,18 +639,12 @@ describe("Spotlight Dialog", () => { describe("when enabling feature", () => { beforeEach(async () => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => - setting === "feature_ask_to_join" ? true : [], - ); + await SettingsStore.setValue("feature_ask_to_join", null, SettingLevel.DEVICE, true); jest.spyOn(mockedClient, "getRoom").mockReturnValue(null); render( {}} />); - // search is debounced - jest.advanceTimersByTime(200); - await flushPromisesWithFakeTimers(); - - fireEvent.click(screen.getByRole("button", { name: "Ask to join" })); + await waitFor(() => fireEvent.click(screen.getByRole("button", { name: "Ask to join" }))); }); it("should skip to auto join", async () => { diff --git a/test/components/views/dialogs/UserSettingsDialog-test.tsx b/test/components/views/dialogs/UserSettingsDialog-test.tsx index f404b7f208f..c01cd3ebac0 100644 --- a/test/components/views/dialogs/UserSettingsDialog-test.tsx +++ b/test/components/views/dialogs/UserSettingsDialog-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React, { ReactElement } from "react"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { mocked, MockedObject } from "jest-mock"; import { MatrixClient } from "matrix-js-sdk/src/matrix"; @@ -57,14 +57,9 @@ jest.mock("../../../../src/settings/SettingsStore", () => ({ settingIsOveriddenAtConfigLevel: jest.fn(), })); -jest.mock("../../../../src/SdkConfig", () => ({ - get: jest.fn(), -})); - describe("", () => { const userId = "@alice:server.org"; const mockSettingsStore = mocked(SettingsStore); - const mockSdkConfig = mocked(SdkConfig); let mockClient!: MockedObject; let sdkContext: SdkContextClass; @@ -89,7 +84,8 @@ describe("", () => { 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("", () => { }); 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("", () => { 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("", () => { }); 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("", () => { }); 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("", () => { expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy(); }); - it("watches settings", () => { + it("watches settings", async () => { const watchSettingCallbacks: Record = {}; mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => { @@ -257,7 +247,7 @@ describe("", () => { watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", "", SettingLevel.ACCOUNT, true, true); // tab is rendered now - expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy(); + await waitFor(() => expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy()); unmount(); diff --git a/test/components/views/elements/Field-test.tsx b/test/components/views/elements/Field-test.tsx index 7cb3074927a..76a06af49bd 100644 --- a/test/components/views/elements/Field-test.tsx +++ b/test/components/views/elements/Field-test.tsx @@ -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, waitFor } 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")); - }); + fireEvent.focus(screen.getByRole("textbox")); // Expect 'alert' role - expect(screen.queryByRole("alert")).toBeInTheDocument(); + await waitFor(() => expect(screen.getByRole("alert")).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")); - }); + fireEvent.focus(screen.getByRole("textbox")); // Expect 'status' role - expect(screen.queryByRole("status")).toBeInTheDocument(); + await waitFor(() => expect(screen.queryByRole("status")).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")); - }); + fireEvent.focus(screen.getByRole("textbox")); // Expect 'tooltip' role - expect(screen.queryByRole("tooltip")).toBeInTheDocument(); + await waitFor(() => expect(screen.queryByRole("tooltip")).toBeInTheDocument()); // Close the feedback is Escape is pressed fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" }); diff --git a/test/components/views/elements/SearchWarning-test.tsx b/test/components/views/elements/SearchWarning-test.tsx index 870254d5112..8c3819af7a1 100644 --- a/test/components/views/elements/SearchWarning-test.tsx +++ b/test/components/views/elements/SearchWarning-test.tsx @@ -32,10 +32,10 @@ describe("", () => { }); it("renders with a logo by default", () => { - const { asFragment, queryByRole } = render( + const { asFragment, getByRole } = render( , ); - expect(queryByRole("img")).toBeInTheDocument(); + expect(getByRole("presentation")).toHaveAttribute("src", "https://logo"); expect(asFragment()).toMatchSnapshot(); }); diff --git a/test/components/views/elements/SpellCheckLanguagesDropdown-test.tsx b/test/components/views/elements/SpellCheckLanguagesDropdown-test.tsx index 069b2109483..e1ea6ce0612 100644 --- a/test/components/views/elements/SpellCheckLanguagesDropdown-test.tsx +++ b/test/components/views/elements/SpellCheckLanguagesDropdown-test.tsx @@ -22,7 +22,10 @@ import PlatformPeg from "../../../../src/PlatformPeg"; describe("", () => { 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( diff --git a/test/components/views/messages/MImageBody-test.tsx b/test/components/views/messages/MImageBody-test.tsx index 3e10c199ef5..27ee0c96c0b 100644 --- a/test/components/views/messages/MImageBody-test.tsx +++ b/test/components/views/messages/MImageBody-test.tsx @@ -211,7 +211,7 @@ describe("", () => { 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", { diff --git a/test/components/views/messages/MLocationBody-test.tsx b/test/components/views/messages/MLocationBody-test.tsx index 9f156eac94f..55c4f89e540 100644 --- a/test/components/views/messages/MLocationBody-test.tsx +++ b/test/components/views/messages/MLocationBody-test.tsx @@ -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(); - // simulate error initialising map in maplibregl - // @ts-ignore - mockMap.emit("error", { status: 404 }); + 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", () => { diff --git a/test/components/views/messages/MPollEndBody-test.tsx b/test/components/views/messages/MPollEndBody-test.tsx index f972e40bfd5..d2ec63e4213 100644 --- a/test/components/views/messages/MPollEndBody-test.tsx +++ b/test/components/views/messages/MPollEndBody-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { render } from "@testing-library/react"; +import { render, waitFor, waitForElementToBeRemoved } 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,13 @@ describe("", () => { 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, queryByRole, 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()); + await waitForElementToBeRemoved(() => queryByRole("progressbar")); expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId()); diff --git a/test/components/views/polls/pollHistory/PollHistory-test.tsx b/test/components/views/polls/pollHistory/PollHistory-test.tsx index 9d53f6ba480..29c1b5d7cf6 100644 --- a/test/components/views/polls/pollHistory/PollHistory-test.tsx +++ b/test/components/views/polls/pollHistory/PollHistory-test.tsx @@ -24,6 +24,7 @@ import { getMockClientWithEventEmitter, makePollEndEvent, makePollStartEvent, + mockClientMethodsRooms, mockClientMethodsUser, mockIntlDateTimeFormat, setupRoomWithPollEvents, @@ -41,7 +42,7 @@ describe("", () => { 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("", () => { 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("", () => { ); // flush filter creation request - await flushPromises(); + await act(flushPromises); // once per page expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(3); @@ -182,7 +183,7 @@ describe("", () => { 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(); }); diff --git a/test/components/views/polls/pollHistory/PollListItemEnded-test.tsx b/test/components/views/polls/pollHistory/PollListItemEnded-test.tsx index 7bf27ee4471..8efd0df52ac 100644 --- a/test/components/views/polls/pollHistory/PollListItemEnded-test.tsx +++ b/test/components/views/polls/pollHistory/PollListItemEnded-test.tsx @@ -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 { MatrixEvent, Poll, Room, M_TEXT } from "matrix-js-sdk/src/matrix"; import { PollListItemEnded } from "../../../../../src/components/views/polls/pollHistory/PollListItemEnded"; @@ -174,7 +174,7 @@ describe("", () => { ]); // updated with more responses - expect(getByText("Final result based on 3 votes")).toBeInTheDocument(); + await waitFor(() => expect(getByText("Final result based on 3 votes")).toBeInTheDocument()); expect(getByText("Nissan Silvia S15")).toBeInTheDocument(); expect(queryByText("Mitsubishi Lancer Evolution IX")).not.toBeInTheDocument(); }); diff --git a/test/components/views/right_panel/LegacyRoomHeaderButtons-test.tsx b/test/components/views/right_panel/LegacyRoomHeaderButtons-test.tsx index ef429562177..8f352d0fd5a 100644 --- a/test/components/views/right_panel/LegacyRoomHeaderButtons-test.tsx +++ b/test/components/views/right_panel/LegacyRoomHeaderButtons-test.tsx @@ -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); - expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeTruthy(); - expect(isIndicatorOfType(container, "notification")).toBe(true); + 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()); }); }); diff --git a/test/components/views/right_panel/PinnedMessagesCard-test.tsx b/test/components/views/right_panel/PinnedMessagesCard-test.tsx index d773b51fb9f..28052557554 100644 --- a/test/components/views/right_panel/PinnedMessagesCard-test.tsx +++ b/test/components/views/right_panel/PinnedMessagesCard-test.tsx @@ -87,20 +87,17 @@ describe("", () => { }; const mountPins = async (room: Room): Promise => { - let pins!: RenderResult; - await act(async () => { - pins = render( - - - , - ); - // Wait a tick for state updates - await sleep(0); - }); + const pins = render( + + + , + ); + // Wait a tick for state updates + await sleep(0); return pins; }; @@ -156,7 +153,7 @@ describe("", () => { const localPins = [pin1]; const nonLocalPins = [pin2]; const room = mkRoom(localPins, nonLocalPins); - const pins = await mountPins(room); + const pins = await act(() => mountPins(room)); expect(pins.container.querySelectorAll(".mx_PinnedEventTile")).toHaveLength(2); // Unpin the first message @@ -224,7 +221,7 @@ describe("", () => { events: [messageEvent], }); - const pins = await mountPins(mkRoom([], [pin1])); + const pins = await act(() => mountPins(mkRoom([], [pin1]))); const pinTile = pins.container.querySelectorAll(".mx_PinnedEventTile"); expect(pinTile.length).toBe(1); expect(pinTile[0].querySelector(".mx_EventTile_body")!).toHaveTextContent("First pinned message, edited"); @@ -299,7 +296,7 @@ describe("", () => { jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); const sendStateEvent = jest.spyOn(cli, "sendStateEvent"); - const pins = await mountPins(room); + const pins = await act(() => mountPins(room)); const pinTile = pins.container.querySelectorAll(".mx_PinnedEventTile"); expect(pinTile).toHaveLength(1); @@ -313,7 +310,7 @@ describe("", () => { it("should show spinner whilst loading", async () => { const room = mkRoom([], [pin1]); mountPins(room); - const spinner = await screen.findByTestId("spinner"); + const spinner = await screen.getByTestId("spinner"); await waitForElementToBeRemoved(spinner); }); }); diff --git a/test/components/views/right_panel/UserInfo-test.tsx b/test/components/views/right_panel/UserInfo-test.tsx index 3b364687869..1e14038ba73 100644 --- a/test/components/views/right_panel/UserInfo-test.tsx +++ b/test/components/views/right_panel/UserInfo-test.tsx @@ -287,10 +287,10 @@ describe("", () => { 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 waitFor(() => expect(screen.getByRole("tooltip")).toHaveTextContent("Cancel")); }); }); diff --git a/test/components/views/rooms/MemberList-test.tsx b/test/components/views/rooms/MemberList-test.tsx index 87806e5a85b..111e1eb64ed 100644 --- a/test/components/views/rooms/MemberList-test.tsx +++ b/test/components/views/rooms/MemberList-test.tsx @@ -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( { 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")); + + await waitFor(() => + expect(getByRole("button", { name: "Invite to this room" })).not.toHaveAttribute( + "aria-disabled", + "true", + ), + ); - fireEvent.click(screen.getByText("Invite to this room")); + fireEvent.click(getByRole("button", { name: "Invite to this room" })); expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "view_invite", diff --git a/test/components/views/rooms/RoomHeader-test.tsx b/test/components/views/rooms/RoomHeader-test.tsx index 06eaaa1837c..4ba4f9abeec 100644 --- a/test/components/views/rooms/RoomHeader-test.tsx +++ b/test/components/views/rooms/RoomHeader-test.tsx @@ -375,6 +375,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(, getWrapper()); @@ -393,6 +394,7 @@ describe("RoomHeader", () => { jest.spyOn(CallStore.instance, "getCall").mockReturnValue({ widget, on: () => {}, + off: () => {}, } as unknown as Call); jest.spyOn(WidgetStore.instance, "getApps").mockReturnValue([widget]); diff --git a/test/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx b/test/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx index daf075ce8e7..3a6f863a579 100644 --- a/test/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx +++ b/test/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx @@ -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("", () => { 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("", () => { 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("", () => { 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()); }); }); diff --git a/test/components/views/rooms/RoomPreviewBar-test.tsx b/test/components/views/rooms/RoomPreviewBar-test.tsx index 25758a096bc..f7f7d97bd3a 100644 --- a/test/components/views/rooms/RoomPreviewBar-test.tsx +++ b/test/components/views/rooms/RoomPreviewBar-test.tsx @@ -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("", () => { 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("", () => { 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("", () => { 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("", () => { 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("", () => { 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("", () => { 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)(); diff --git a/test/components/views/settings/Notifications-test.tsx b/test/components/views/settings/Notifications-test.tsx index 24a23832c1d..84136ebe7a1 100644 --- a/test/components/views/settings/Notifications-test.tsx +++ b/test/components/views/settings/Notifications-test.tsx @@ -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("", () => { // 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,7 @@ describe("", () => { // oneToOneRule is set to 'on' // and is kind: 'underride' const offToggle = screen.getByTestId(section + oneToOneRule.rule_id).querySelector('input[type="radio"]')!; - fireEvent.click(offToggle); + await act(() => fireEvent.click(offToggle)); await flushPromises(); @@ -552,7 +561,7 @@ describe("", () => { // oneToOneRule is set to 'on' // and is kind: 'underride' const offToggle = screen.getByTestId(section + oneToOneRule.rule_id).querySelector('input[type="radio"]')!; - fireEvent.click(offToggle); + await act(() => fireEvent.click(offToggle)); await flushPromises(); @@ -716,7 +725,7 @@ describe("", () => { mockClient.setPushRuleActions.mockRejectedValue("oups"); const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]')!; - fireEvent.click(offToggle); + await act(() => fireEvent.click(offToggle)); await flushPromises(); @@ -814,7 +823,9 @@ describe("", () => { mockClient.setPushRuleEnabled.mockRejectedValueOnce("oups"); - fireEvent.click(within(screen.getByTestId(section + keywordsRuleId)).getByLabelText("Off")); + await act(() => + fireEvent.click(within(screen.getByTestId(section + keywordsRuleId)).getByLabelText("Off")), + ); await flushPromises(); diff --git a/test/components/views/settings/SetIntegrationManager-test.tsx b/test/components/views/settings/SetIntegrationManager-test.tsx index 5b92e039400..7354b2ddf3a 100644 --- a/test/components/views/settings/SetIntegrationManager-test.tsx +++ b/test/components/views/settings/SetIntegrationManager-test.tsx @@ -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()); }); }); diff --git a/test/components/views/settings/devices/DeviceDetailHeading-test.tsx b/test/components/views/settings/devices/DeviceDetailHeading-test.tsx index 966c0a4be81..4cfeb8a0521 100644 --- a/test/components/views/settings/devices/DeviceDetailHeading-test.tsx +++ b/test/components/views/settings/devices/DeviceDetailHeading-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { fireEvent, render, RenderResult } from "@testing-library/react"; +import { fireEvent, render, RenderResult, waitFor } from "@testing-library/react"; import { DeviceDetailHeading } from "../../../../../src/components/views/settings/devices/DeviceDetailHeading"; import { flushPromisesWithFakeTimers } from "../../../../test-utils"; @@ -118,7 +118,7 @@ describe("", () => { await flushPromisesWithFakeTimers(); // read mode displayed - expect(getByTestId("device-detail-heading")).toBeTruthy(); + await waitFor(() => expect(getByTestId("device-detail-heading")).toBeTruthy()); }); it("displays error when device name fails to save", async () => { @@ -136,7 +136,7 @@ describe("", () => { await flushPromisesWithFakeTimers(); // error message displayed - expect(queryByText("Failed to set session name")).toBeTruthy(); + await waitFor(() => expect(queryByText("Failed to set session name")).toBeTruthy()); // spinner removed expect(container.getElementsByClassName("mx_Spinner").length).toBeFalsy(); diff --git a/test/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/components/views/settings/devices/FilteredDeviceList-test.tsx index c37ea612283..be1ec2aa873 100644 --- a/test/components/views/settings/devices/FilteredDeviceList-test.tsx +++ b/test/components/views/settings/devices/FilteredDeviceList-test.tsx @@ -120,16 +120,15 @@ describe("", () => { }); describe("filtering", () => { - 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 = container.querySelector('[aria-label="Filter devices"]'); - fireEvent.click(dropdown as Element); - // tick to let dropdown render - await flushPromises(); + fireEvent.click(dropdown as Element); + // tick to let dropdown render + 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", () => { const { container } = render(getComponent({ filter: undefined })); diff --git a/test/components/views/settings/discovery/EmailAddresses-test.tsx b/test/components/views/settings/discovery/EmailAddresses-test.tsx index 547f802873f..a0430898a60 100644 --- a/test/components/views/settings/discovery/EmailAddresses-test.tsx +++ b/test/components/views/settings/discovery/EmailAddresses-test.tsx @@ -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 { IThreepid, ThreepidMedium, IRequestTokenResponse, MatrixError } from "matrix-js-sdk/src/matrix"; import { TranslationKey, UserFriendlyError } from "../../../../../src/languageHandler"; @@ -104,6 +104,7 @@ describe("", () => { "https://fake-url/", ), ); + await waitFor(() => expect(screen.getByText("Complete")).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("", () => { 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 waitFor(() => expect(screen.getByText("Complete")).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("", () => { 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 waitFor(() => expect(screen.getByText("Complete")).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. diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index a88c322361c..e617ed4bf8c 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -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("", () => { // 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("", () => { 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["getByTestId"], @@ -920,37 +927,33 @@ describe("", () => { it("deletes a device when interactive auth is not required", async () => { mockClient.deleteMultipleDevices.mockResolvedValue({}); - mockClient.getDevices - .mockResolvedValueOnce({ - devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice], - }) - // pretend it was really deleted on refresh - .mockResolvedValueOnce({ - devices: [alicesDevice, alicesOlderMobileDevice], - }); + mockClient.getDevices.mockResolvedValue({ + devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice], + }); - const { getByTestId } = render(getComponent()); + const { getByTestId } = render(getComponent(), { legacyRoot: true }); - await act(async () => { - await flushPromises(); + await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar")); + await toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); + + const signOutButton = await waitFor(() => + within(getByTestId(`device-detail-${alicesMobileDevice.device_id}`)).getByTestId( + "device-detail-sign-out-cta", + ), + ); + + // pretend it was really deleted on refresh + mockClient.getDevices.mockResolvedValueOnce({ + devices: [alicesDevice, alicesOlderMobileDevice], }); - toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); + // sign out button is disabled with spinner + const prom = waitFor(() => expect(signOutButton).toHaveAttribute("aria-disabled", "true")); - 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); + await prom; - // sign out button is disabled with spinner - expect( - (deviceDetails.querySelector('[data-testid="device-detail-sign-out-cta"]') as Element).getAttribute( - "aria-disabled", - ), - ).toEqual("true"); // delete called expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith( [alicesMobileDevice.device_id], @@ -1006,11 +1009,9 @@ describe("", () => { devices: [alicesDevice, alicesOlderMobileDevice], }); - const { getByTestId, getByLabelText } = render(getComponent()); + const { getByTestId, getByLabelText } = render(getComponent(), { legacyRoot: true }); - await act(async () => { - await flushPromises(); - }); + await act(flushPromises); // reset mock count after initial load mockClient.getDevices.mockClear(); @@ -1052,7 +1053,7 @@ describe("", () => { type: "m.id.user", user: aliceId, }, - password: "", + password: "topsecret", type: "m.login.password", }); // devices refreshed @@ -1570,9 +1571,7 @@ describe("", () => { }); const { getByTestId, container } = render(getComponent()); - await act(async () => { - await flushPromises(); - }); + await act(flushPromises); // filter for inactive sessions await setFilter(container, DeviceSecurityVariation.Inactive); @@ -1765,6 +1764,7 @@ describe("", () => { await flushPromises(); fireEvent.click(getByText("Show QR code")); + await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar")); await expect(findByTestId("login-with-qr")).resolves.toBeTruthy(); }); diff --git a/test/hooks/useNotificationSettings-test.tsx b/test/hooks/useNotificationSettings-test.tsx index 5b0e4904265..d84cdb20ca0 100644 --- a/test/hooks/useNotificationSettings-test.tsx +++ b/test/hooks/useNotificationSettings-test.tsx @@ -68,99 +68,87 @@ describe("useNotificationSettings", () => { }); it("correctly parses model", async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useNotificationSettings(cli)); - expect(result.current.model).toEqual(null); - await waitForNextUpdate(); - expect(result.current.model).toEqual(expectedModel); - expect(result.current.hasPendingChanges).toBeFalsy(); - }); + const { result } = renderHook(() => useNotificationSettings(cli)); + expect(result.current.model).toEqual(null); + 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); - cli.deletePushRule = deletePushRule; - const setPushRuleEnabled = jest.fn(cli.setPushRuleEnabled); - cli.setPushRuleEnabled = setPushRuleEnabled; - const setPushRuleActions = jest.fn(cli.setPushRuleActions); - cli.setPushRuleActions = setPushRuleActions; + const addPushRule = jest.fn(cli.addPushRule); + cli.addPushRule = addPushRule; + const deletePushRule = jest.fn(cli.deletePushRule); + cli.deletePushRule = deletePushRule; + const setPushRuleEnabled = jest.fn(cli.setPushRuleEnabled); + cli.setPushRuleEnabled = setPushRuleEnabled; + const setPushRuleActions = jest.fn(cli.setPushRuleActions); + cli.setPushRuleActions = setPushRuleActions; - const { result, waitForNextUpdate } = renderHook(() => useNotificationSettings(cli)); - expect(result.current.model).toEqual(null); - await waitForNextUpdate(); - expect(result.current.model).toEqual(expectedModel); - expect(result.current.hasPendingChanges).toBeFalsy(); - await result.current.reconcile(DefaultNotificationSettings); - await waitForNextUpdate(); - expect(result.current.hasPendingChanges).toBeFalsy(); - expect(addPushRule).toHaveBeenCalledTimes(0); - expect(deletePushRule).toHaveBeenCalledTimes(9); - expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justjann3"); - expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nn3"); - expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nne"); - expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Janne"); - expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "J4nne"); - expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Jann3"); - expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "jann3"); - expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "j4nne"); - expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "janne"); - expect(setPushRuleEnabled).toHaveBeenCalledTimes(6); - expect(setPushRuleEnabled).toHaveBeenCalledWith( - "global", - PushRuleKind.Underride, - RuleId.EncryptedMessage, - 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.DM, true); - 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( - "global", - PushRuleKind.Underride, - RuleId.EncryptedMessage, - StandardActions.ACTION_NOTIFY, - ); - expect(setPushRuleActions).toHaveBeenCalledWith( - "global", - PushRuleKind.Underride, - RuleId.Message, - StandardActions.ACTION_NOTIFY, - ); - expect(setPushRuleActions).toHaveBeenCalledWith( - "global", - PushRuleKind.Underride, - RuleId.EncryptedDM, - StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, - ); - expect(setPushRuleActions).toHaveBeenCalledWith( - "global", - PushRuleKind.Underride, - RuleId.DM, - StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, - ); - expect(setPushRuleActions).toHaveBeenCalledWith( - "global", - PushRuleKind.Override, - RuleId.SuppressNotices, - StandardActions.ACTION_DONT_NOTIFY, - ); - expect(setPushRuleActions).toHaveBeenCalledWith( - "global", - PushRuleKind.Override, - RuleId.InviteToSelf, - StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, - ); - }); + const { result } = renderHook(() => useNotificationSettings(cli)); + expect(result.current.model).toEqual(null); + await waitFor(() => expect(result.current.model).toEqual(expectedModel)); + expect(result.current.hasPendingChanges).toBeFalsy(); + await result.current.reconcile(DefaultNotificationSettings); + await waitFor(() => expect(result.current.hasPendingChanges).toBeFalsy()); + expect(addPushRule).toHaveBeenCalledTimes(0); + expect(deletePushRule).toHaveBeenCalledTimes(9); + expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justjann3"); + expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nn3"); + expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nne"); + expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Janne"); + expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "J4nne"); + expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Jann3"); + expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "jann3"); + expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "j4nne"); + expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "janne"); + expect(setPushRuleEnabled).toHaveBeenCalledTimes(6); + expect(setPushRuleEnabled).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.EncryptedMessage, + 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.DM, true); + 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( + "global", + PushRuleKind.Underride, + RuleId.EncryptedMessage, + StandardActions.ACTION_NOTIFY, + ); + expect(setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.Message, + StandardActions.ACTION_NOTIFY, + ); + expect(setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.EncryptedDM, + StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, + ); + expect(setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Underride, + RuleId.DM, + StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, + ); + expect(setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.SuppressNotices, + StandardActions.ACTION_DONT_NOTIFY, + ); + expect(setPushRuleActions).toHaveBeenCalledWith( + "global", + PushRuleKind.Override, + RuleId.InviteToSelf, + StandardActions.ACTION_NOTIFY_DEFAULT_SOUND, + ); }); }); diff --git a/test/hooks/useUserOnboardingTasks-test.tsx b/test/hooks/useUserOnboardingTasks-test.tsx index 26a2ba4b756..f541747d108 100644 --- a/test/hooks/useUserOnboardingTasks-test.tsx +++ b/test/hooks/useUserOnboardingTasks-test.tsx @@ -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); - rerender(); - await waitFor(() => expect(result.current[4].completed).toBe(true)); + await waitFor(() => { + rerender(); + expect(result.current[4].completed).toBe(true); + }); }); }); diff --git a/test/languageHandler-test.tsx b/test/languageHandler-test.tsx index a9ad673a706..aeecaa0e187 100644 --- a/test/languageHandler-test.tsx +++ b/test/languageHandler-test.tsx @@ -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, - Record | 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"], diff --git a/test/modules/ProxiedModuleApi-test.tsx b/test/modules/ProxiedModuleApi-test.tsx index bec6215232d..1e98e69508d 100644 --- a/test/modules/ProxiedModuleApi-test.tsx +++ b/test/modules/ProxiedModuleApi-test.tsx @@ -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 = () =>

This is my example content.

; } @@ -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 = () =>

This is my example content.

; } @@ -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 = () => (