diff --git a/apps/web/jest.config.ts b/apps/web/jest.config.ts index 5d3d16f4641..13f13dcb365 100644 --- a/apps/web/jest.config.ts +++ b/apps/web/jest.config.ts @@ -24,6 +24,7 @@ const config: Config = { globalSetup: "/test/globalSetup.ts", setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"], setupFilesAfterEnv: ["/test/setupTests.ts"], + restoreMocks: true, moduleNameMapper: { // Support CSS module "\\.(module.css)$": "identity-obj-proxy", diff --git a/apps/web/test/setupTests.ts b/apps/web/test/setupTests.ts index f514c104823..1867f5d302b 100644 --- a/apps/web/test/setupTests.ts +++ b/apps/web/test/setupTests.ts @@ -11,6 +11,7 @@ import "@testing-library/jest-dom"; import "blob-polyfill"; import { secureRandomString } from "matrix-js-sdk/src/randomstring"; import { mocked } from "jest-mock"; +import React, { useRef } from "react"; import { PredictableRandom } from "./test-utils/predictableRandom"; import * as rageshake from "../src/rageshake/rageshake"; @@ -39,6 +40,22 @@ beforeEach(() => { }); }); +// Mock useId to return a predictable, incrementing string +let idCounter = 0; + +// Reset the counter before each test so 'test-id-0' is always first +beforeEach(() => { + idCounter = 0; + + jest.spyOn(React, "useId").mockImplementation(() => { + // IDs need to be consistent for re-renders of the same component otherwise Radix etc get confused. + // We can use a ref to store the one we generated which is broadly similar to how react's version + // works in that it generates one at mount time and returns that). + const myId = useRef(`test-id-${idCounter++}`); + return myId.current; + }); +}); + // Somewhat hacky workaround for https://github.com/jestjs/jest/issues/15747: if the GHA reporter is enabled, // capture logs using the rageshake infrastructure, then dump them out after the test. if (env["GITHUB_ACTIONS"] !== undefined) { diff --git a/apps/web/test/unit-tests/components/structures/MessagePanel-test.tsx b/apps/web/test/unit-tests/components/structures/MessagePanel-test.tsx index cef53109200..f57adecd077 100644 --- a/apps/web/test/unit-tests/components/structures/MessagePanel-test.tsx +++ b/apps/web/test/unit-tests/components/structures/MessagePanel-test.tsx @@ -9,7 +9,14 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import { EventEmitter } from "events"; -import { type MatrixEvent, Room, RoomMember, type Thread, ReceiptType } from "matrix-js-sdk/src/matrix"; +import { + type MatrixEvent, + Room, + RoomMember, + type Thread, + ReceiptType, + type MatrixClient, +} from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { render, within } from "jest-matrix-react"; @@ -42,22 +49,9 @@ const roomId = "!roomId:server_name"; describe("MessagePanel", function () { const events = mkEvents(); const userId = "@me:here"; - const client = getMockClientWithEventEmitter({ - ...mockClientMethodsUser(userId), - ...mockClientMethodsEvents(), - ...mockClientMethodsCrypto(), - ...mockClientPushProcessor(), - getAccountData: jest.fn(), - isUserIgnored: jest.fn().mockReturnValue(false), - isRoomEncrypted: jest.fn().mockReturnValue(false), - getRoom: jest.fn(), - getClientWellKnown: jest.fn().mockReturnValue({}), - supportsThreads: jest.fn().mockReturnValue(true), - }); + let client: MatrixClient; let sdkContext: SdkContextClass; - jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client); - - const room = new Room(roomId, client, userId); + let room: Room; const bobMember = new RoomMember(roomId, "@bob:id"); bobMember.name = "Bob"; @@ -73,7 +67,6 @@ describe("MessagePanel", function () { const defaultProps = { resizeNotifier: new EventEmitter() as unknown as ResizeNotifier, callEventGroupers: new Map(), - room, className: "cls", events: [] as MatrixEvent[], }; @@ -81,8 +74,6 @@ describe("MessagePanel", function () { const defaultRoomContext = { ...RoomContext, timelineRenderingType: TimelineRenderingType.Room, - room, - roomId: room.roomId, canReact: true, canSendMessages: true, showReadReceipts: true, @@ -93,14 +84,30 @@ describe("MessagePanel", function () { showHiddenEvents: false, } as unknown as RoomContextType; - const getComponent = (props = {}, roomContext: Partial = {}) => ( - - + const getComponent = (room: Room, props = {}, roomContext: Partial = {}) => ( + + ); beforeEach(function () { - jest.clearAllMocks(); + client = getMockClientWithEventEmitter({ + ...mockClientMethodsUser(userId), + ...mockClientMethodsEvents(), + ...mockClientMethodsCrypto(), + ...mockClientPushProcessor(), + getAccountData: jest.fn(), + isUserIgnored: jest.fn().mockReturnValue(false), + isRoomEncrypted: jest.fn().mockReturnValue(false), + getRoom: jest.fn(), + getClientWellKnown: jest.fn().mockReturnValue({}), + supportsThreads: jest.fn().mockReturnValue(true), + }); + + jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client); + + room = new Room(roomId, client, userId); + // HACK: We assume all settings want to be disabled jest.spyOn(SettingsStore, "getValue").mockImplementation((arg) => { return arg === "showDisplaynameChanges"; @@ -315,7 +322,10 @@ describe("MessagePanel", function () { } it("should show the events", function () { - const { container } = render(getComponent({ events }), clientAndSDKContextRenderOptions(client, sdkContext)); + const { container } = render( + getComponent(room, { events }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); // just check we have the right number of tiles for now const tiles = container.getElementsByClassName("mx_EventTile"); @@ -324,7 +334,7 @@ describe("MessagePanel", function () { it("should collapse adjacent member events", function () { const { container } = render( - getComponent({ events: mkMelsEvents() }), + getComponent(room, { events: mkMelsEvents() }), clientAndSDKContextRenderOptions(client, sdkContext), ); @@ -338,7 +348,7 @@ describe("MessagePanel", function () { it("should insert the read-marker in the right place", function () { const { container } = render( - getComponent({ + getComponent(room, { events, readMarkerEventId: events[4].getId(), readMarkerVisible: true, @@ -359,7 +369,7 @@ describe("MessagePanel", function () { it("should show the read-marker that fall in summarised events after the summary", function () { const melsEvents = mkMelsEvents(); const { container } = render( - getComponent({ + getComponent(room, { events: melsEvents, readMarkerEventId: melsEvents[4].getId(), readMarkerVisible: true, @@ -382,7 +392,7 @@ describe("MessagePanel", function () { const melsEvents = mkMelsEventsOnly(); const { container } = render( - getComponent({ + getComponent(room, { events: melsEvents, readMarkerEventId: melsEvents[9].getId(), readMarkerVisible: true, @@ -407,7 +417,7 @@ describe("MessagePanel", function () { const { container, rerender } = render(
- {getComponent({ + {getComponent(room, { events, readMarkerEventId: events[4].getId(), readMarkerVisible: true, @@ -424,7 +434,7 @@ describe("MessagePanel", function () { rerender(
- {getComponent({ + {getComponent(room, { events, readMarkerEventId: events[6].getId(), readMarkerVisible: true, @@ -453,10 +463,13 @@ describe("MessagePanel", function () { const events = mkCreationEvents(); const createEvent = events.find((event) => event.getType() === "m.room.create")!; const encryptionEvent = events.find((event) => event.getType() === "m.room.encryption")!; - client.getRoom.mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null)); + jest.mocked(client.getRoom).mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null)); TestUtilsMatrix.upsertRoomStateEvents(room, events); - const { container } = render(getComponent({ events }), clientAndSDKContextRenderOptions(client, sdkContext)); + const { container } = render( + getComponent(room, { events }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); // we expect that // - the room creation event, the room encryption event, and Alice inviting Bob, @@ -485,7 +498,7 @@ describe("MessagePanel", function () { const combinedEvents = [...events, beaconInfoEvent]; TestUtilsMatrix.upsertRoomStateEvents(room, combinedEvents); const { container } = render( - getComponent({ events: combinedEvents }), + getComponent(room, { events: combinedEvents }), clientAndSDKContextRenderOptions(client, sdkContext), ); @@ -500,11 +513,11 @@ describe("MessagePanel", function () { it("should hide read-marker at the end of creation event summary", function () { const events = mkCreationEvents(); const createEvent = events.find((event) => event.getType() === "m.room.create"); - client.getRoom.mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null)); + jest.mocked(client.getRoom).mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null)); TestUtilsMatrix.upsertRoomStateEvents(room, events); const { container } = render( - getComponent({ + getComponent(room, { events, readMarkerEventId: events[5].getId(), readMarkerVisible: true, @@ -527,7 +540,7 @@ describe("MessagePanel", function () { it("should render Date separators for the events", function () { const events = mkOneDayEvents(); const { queryAllByRole } = render( - getComponent({ events }), + getComponent(room, { events }), clientAndSDKContextRenderOptions(client, sdkContext), ); const dates = queryAllByRole("separator"); @@ -539,7 +552,7 @@ describe("MessagePanel", function () { const events = mkMelsEvents().slice(1, 11); const { container, rerender } = render( - getComponent({ events }), + getComponent(room, { events }), clientAndSDKContextRenderOptions(client, sdkContext), ); let els = container.getElementsByClassName("mx_GenericEventListSummary"); @@ -560,7 +573,7 @@ describe("MessagePanel", function () { name: "A user", }), ]; - rerender(getComponent({ events: updatedEvents })); + rerender(getComponent(room, { events: updatedEvents })); els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); @@ -572,7 +585,7 @@ describe("MessagePanel", function () { const events = mkMelsEvents().slice(1, 11); const { container, rerender } = render( - getComponent({ events }), + getComponent(room, { events }), clientAndSDKContextRenderOptions(client, sdkContext), ); let els = container.getElementsByClassName("mx_GenericEventListSummary"); @@ -593,7 +606,7 @@ describe("MessagePanel", function () { }), ...events, ]; - rerender(getComponent({ events: updatedEvents })); + rerender(getComponent(room, { events: updatedEvents })); els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); @@ -605,7 +618,7 @@ describe("MessagePanel", function () { const events = mkMelsEvents().slice(1, 11); const { container, rerender } = render( - getComponent({ events }), + getComponent(room, { events }), clientAndSDKContextRenderOptions(client, sdkContext), ); let els = container.getElementsByClassName("mx_GenericEventListSummary"); @@ -623,7 +636,7 @@ describe("MessagePanel", function () { }), ...events.slice(5, 10), ]; - rerender(getComponent({ events: updatedEvents })); + rerender(getComponent(room, { events: updatedEvents })); // summaries split becuase room messages are not summarised els = container.getElementsByClassName("mx_GenericEventListSummary"); @@ -640,12 +653,15 @@ describe("MessagePanel", function () { it("doesn't lookup showHiddenEventsInTimeline while rendering", () => { // We're only interested in the setting lookups that happen on every render, // rather than those happening on first mount, so let's get those out of the way - const { rerender } = render(getComponent({ events: [] }), clientAndSDKContextRenderOptions(client, sdkContext)); + const { rerender } = render( + getComponent(room, { events: [] }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); // Set up our spy and re-render with new events const settingsSpy = jest.spyOn(SettingsStore, "getValue").mockClear(); - rerender(getComponent({ events: mkMixedHiddenAndShownEvents() })); + rerender(getComponent(room, { events: mkMixedHiddenAndShownEvents() })); expect(settingsSpy).not.toHaveBeenCalledWith("showHiddenEventsInTimeline"); settingsSpy.mockRestore(); @@ -679,7 +695,7 @@ describe("MessagePanel", function () { }), ]; const { container } = render( - getComponent({ events }, { showHiddenEvents: true }), + getComponent(room, { events }, { showHiddenEvents: true }), clientAndSDKContextRenderOptions(client, sdkContext), ); @@ -706,7 +722,7 @@ describe("MessagePanel", function () { ); } const { asFragment } = render( - getComponent({ events }, { showHiddenEvents: false }), + getComponent(room, { events }, { showHiddenEvents: false }), clientAndSDKContextRenderOptions(client, sdkContext), ); expect(asFragment()).toMatchSnapshot(); @@ -730,7 +746,7 @@ describe("MessagePanel", function () { ); } const { asFragment } = render( - getComponent({ events }, { showHiddenEvents: false }), + getComponent(room, { events }, { showHiddenEvents: false }), clientAndSDKContextRenderOptions(client, sdkContext), ); expect(asFragment()).toMatchSnapshot(); @@ -754,7 +770,7 @@ describe("MessagePanel", function () { ); } const { asFragment } = render( - getComponent({ events }, { showHiddenEvents: true }), + getComponent(room, { events }, { showHiddenEvents: true }), clientAndSDKContextRenderOptions(client, sdkContext), ); const cpt = asFragment(); @@ -769,7 +785,7 @@ describe("MessagePanel", function () { }); it("should set lastSuccessful=true on non-last event if last event is not eligible for special receipt", () => { - client.getRoom.mockImplementation((id) => (id === room.roomId ? room : null)); + jest.mocked(client.getRoom).mockImplementation((id) => (id === room.roomId ? room : null)); const events = [ TestUtilsMatrix.mkMessage({ event: true, @@ -788,7 +804,7 @@ describe("MessagePanel", function () { }), ]; const { container } = render( - getComponent({ events, showReadReceipts: true }), + getComponent(room, { events, showReadReceipts: true }), clientAndSDKContextRenderOptions(client, sdkContext), ); @@ -799,7 +815,7 @@ describe("MessagePanel", function () { }); it("should set lastSuccessful=false on non-last event if last event has a receipt from someone else", () => { - client.getRoom.mockImplementation((id) => (id === room.roomId ? room : null)); + jest.mocked(client.getRoom).mockImplementation((id) => (id === room.roomId ? room : null)); const events = [ TestUtilsMatrix.mkMessage({ event: true, @@ -824,7 +840,7 @@ describe("MessagePanel", function () { true, ); const { container } = render( - getComponent({ events, showReadReceipts: true }), + getComponent(room, { events, showReadReceipts: true }), clientAndSDKContextRenderOptions(client, sdkContext), ); diff --git a/apps/web/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap b/apps/web/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap index 07c557e9d03..97be6f310ac 100644 --- a/apps/web/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap +++ b/apps/web/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap @@ -19,7 +19,7 @@ exports[`FilePanel renders empty state 1`] = `

Could not start a chat with this user

@@ -417,7 +417,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] = >
-
- -
-
-
-
diff --git a/apps/web/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap b/apps/web/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap index 589f0eaf106..12adb3d7f5b 100644 --- a/apps/web/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap +++ b/apps/web/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap @@ -70,7 +70,7 @@ exports[`SpaceHierarchy renders 1`] = ` role="tree" >
  • @@ -100,7 +100,7 @@ exports[`SpaceHierarchy renders 1`] = ` class="mx_SpaceHierarchy_roomTile_name" > Unnamed Room @@ -134,7 +134,7 @@ exports[`SpaceHierarchy renders 1`] = ` class="_container_153f2_10" > renders 1`] = `
  • @@ -198,7 +198,7 @@ exports[`SpaceHierarchy renders 1`] = ` class="mx_SpaceHierarchy_roomTile_name" > Unnamed Room @@ -232,7 +232,7 @@ exports[`SpaceHierarchy renders 1`] = ` class="_container_153f2_10" > renders 1`] = `
  • @@ -297,7 +297,7 @@ exports[`SpaceHierarchy renders 1`] = ` class="mx_SpaceHierarchy_roomTile_name" > Nested space @@ -331,7 +331,7 @@ exports[`SpaceHierarchy renders 1`] = ` class="_container_153f2_10" > renders 1`] = ` role="group" >
  • @@ -413,7 +413,7 @@ exports[`SpaceHierarchy renders 1`] = ` class="mx_SpaceHierarchy_roomTile_name" > Nested room @@ -435,7 +435,7 @@ exports[`SpaceHierarchy renders 1`] = ` Join
  • renders 1`] = ` class="_container_153f2_10" > renders 1`] = `
  • @@ -519,7 +519,7 @@ exports[`SpaceHierarchy renders 1`] = ` class="mx_SpaceHierarchy_roomTile_name" > Knock room @@ -553,7 +553,7 @@ exports[`SpaceHierarchy renders 1`] = ` class="_container_153f2_10" > should not render cycles 1`] = ` role="tree" >
  • @@ -690,7 +690,7 @@ exports[`SpaceHierarchy should not render cycles 1`] = ` class="mx_SpaceHierarchy_roomTile_name" > Unnamed Room @@ -724,7 +724,7 @@ exports[`SpaceHierarchy should not render cycles 1`] = ` class="_container_153f2_10" > should not render cycles 1`] = `
  • @@ -788,7 +788,7 @@ exports[`SpaceHierarchy should not render cycles 1`] = ` class="mx_SpaceHierarchy_roomTile_name" > Unnamed Room @@ -822,7 +822,7 @@ exports[`SpaceHierarchy should not render cycles 1`] = ` class="_container_153f2_10" > should not render cycles 1`] = `
  • @@ -887,7 +887,7 @@ exports[`SpaceHierarchy should not render cycles 1`] = ` class="mx_SpaceHierarchy_roomTile_name" > Nested space @@ -921,7 +921,7 @@ exports[`SpaceHierarchy should not render cycles 1`] = ` class="_container_153f2_10" >
  • should list spaces which are not par class="_container_153f2_10" > should list spaces which are not par 0 members diff --git a/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/MessageEditHistoryDialog-test.tsx.snap b/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/MessageEditHistoryDialog-test.tsx.snap index 6f0078db869..238ddd49262 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/MessageEditHistoryDialog-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/MessageEditHistoryDialog-test.tsx.snap @@ -113,7 +113,7 @@ exports[` should match the snapshot 1`] = `
    should support events with 1`] = `
    Report this room to your account provider. If the messages are encrypted, your admin will not be able to read them. @@ -76,7 +76,6 @@ exports[`ReportRoomDialog displays admin message 1`] = ` > @@ -90,7 +89,7 @@ exports[`ReportRoomDialog displays admin message 1`] = ` > diff --git a/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/ServerPickerDialog-test.tsx.snap b/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/ServerPickerDialog-test.tsx.snap index 79a6d5d38fc..fb95129b393 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/ServerPickerDialog-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/dialogs/__snapshots__/ServerPickerDialog-test.tsx.snap @@ -51,7 +51,7 @@ exports[` should render dialog 1`] = ` class="mx_StyledRadioButton_content" >
    @@ -70,7 +70,7 @@ exports[`WidgetOpenIDPermissionsDialog should render 1`] = ` > diff --git a/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Users-test.tsx.snap b/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Users-test.tsx.snap index 65510b96e57..aa22fe4e71f 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Users-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Users-test.tsx.snap @@ -704,7 +704,7 @@ exports[` should render a user list 1`] = ` @@ -718,7 +718,7 @@ exports[` should render a user list 1`] = ` > diff --git a/apps/web/test/unit-tests/components/views/dialogs/security/__snapshots__/RestoreKeyBackupDialog-test.tsx.snap b/apps/web/test/unit-tests/components/views/dialogs/security/__snapshots__/RestoreKeyBackupDialog-test.tsx.snap index b82770405ca..35764679a46 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/security/__snapshots__/RestoreKeyBackupDialog-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/dialogs/security/__snapshots__/RestoreKeyBackupDialog-test.tsx.snap @@ -310,7 +310,7 @@ exports[` should render 1`] = `
    should restore key backup when the key is ca
    should restore key backup when the key is in