From 6533a6b642d140f1a452139ce2c74bdfef88c5e3 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 14 Nov 2024 17:54:54 +0100 Subject: [PATCH 01/13] Add `asyncFilter` --- src/utils/arrays.ts | 10 ++++++++++ test/unit-tests/utils/arrays-test.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts index 99c69b98912..a7f97766b48 100644 --- a/src/utils/arrays.ts +++ b/src/utils/arrays.ts @@ -328,6 +328,16 @@ export async function asyncSome(values: Iterable, predicate: (value: T) => return false; } +/** + * Async version of Array.filter. + * @param values + * @param predicate + */ +export async function asyncFilter(values: Array, predicate: (value: T) => Promise): Promise> { + const results = await Promise.all(values.map(predicate)); + return values.filter((_, i) => results[i]); +} + export function filterBoolean(values: Array): T[] { return values.filter(Boolean) as T[]; } diff --git a/test/unit-tests/utils/arrays-test.ts b/test/unit-tests/utils/arrays-test.ts index 53baed8be3e..9bd8f15510d 100644 --- a/test/unit-tests/utils/arrays-test.ts +++ b/test/unit-tests/utils/arrays-test.ts @@ -23,6 +23,7 @@ import { concat, asyncEvery, asyncSome, + asyncFilter, } from "../../../src/utils/arrays"; type TestParams = { input: number[]; output: number[] }; @@ -460,4 +461,15 @@ describe("arrays", () => { expect(predicate).toHaveBeenCalledWith(2); }); }); + + describe("asyncFilter", () => { + it("when called with an empty array, it should return an empty array", async () => { + expect(await asyncFilter([], jest.fn().mockResolvedValue(true))).toEqual([]); + }); + + it("should filter the content", async () => { + const predicate = jest.fn().mockImplementation((value) => Promise.resolve(value === 2)); + expect(await asyncFilter([1, 2, 3], predicate)).toEqual([2]); + }); + }); }); From f8c5eac8a978a2beb3828893859ef5b38fed6c84 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 14 Nov 2024 17:59:18 +0100 Subject: [PATCH 02/13] Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `MemberListStore.tsx` --- src/stores/MemberListStore.ts | 6 +++--- test/unit-tests/stores/MemberListStore-test.ts | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/stores/MemberListStore.ts b/src/stores/MemberListStore.ts index 1455a4526f1..cf4b9339831 100644 --- a/src/stores/MemberListStore.ts +++ b/src/stores/MemberListStore.ts @@ -70,7 +70,7 @@ export class MemberListStore { return []; } - if (!this.isLazyLoadingEnabled(roomId) || this.loadedRooms.has(roomId)) { + if (!(await this.isLazyLoadingEnabled(roomId)) || this.loadedRooms.has(roomId)) { // nice and easy, we must already have all the members so just return them. return this.loadMembersInRoom(room); } @@ -121,10 +121,10 @@ export class MemberListStore { * @param roomId The room to check if lazy loading is enabled * @returns True if enabled */ - private isLazyLoadingEnabled(roomId: string): boolean { + private async isLazyLoadingEnabled(roomId: string): Promise { if (SettingsStore.getValue("feature_sliding_sync")) { // only unencrypted rooms use lazy loading - return !this.stores.client!.isRoomEncrypted(roomId); + return !(await this.stores.client?.getCrypto()?.isEncryptionEnabledInRoom(roomId)); } return this.stores.client!.hasLazyLoadMembersEnabled(); } diff --git a/test/unit-tests/stores/MemberListStore-test.ts b/test/unit-tests/stores/MemberListStore-test.ts index 889a9d35057..815dea8758e 100644 --- a/test/unit-tests/stores/MemberListStore-test.ts +++ b/test/unit-tests/stores/MemberListStore-test.ts @@ -189,8 +189,7 @@ describe("MemberListStore", () => { }); it("does not use lazy loading on encrypted rooms", async () => { - client.isRoomEncrypted = jest.fn(); - mocked(client.isRoomEncrypted).mockReturnValue(true); + jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); const { joined } = await store.loadMemberList(roomId); expect(joined).toEqual([room.getMember(alice)]); From d7ef409df837de9738d93e804c30303c07cadd8e Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 14 Nov 2024 17:59:31 +0100 Subject: [PATCH 03/13] Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `EventIndex.tsx` --- src/indexing/EventIndex.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts index ec3935cd684..fc1be4eba5d 100644 --- a/src/indexing/EventIndex.ts +++ b/src/indexing/EventIndex.ts @@ -39,6 +39,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg"; import SettingsStore from "../settings/SettingsStore"; import { SettingLevel } from "../settings/SettingLevel"; import { ICrawlerCheckpoint, IEventAndProfile, IIndexStats, ILoadArgs, ISearchArgs } from "./BaseEventIndexManager"; +import { asyncFilter } from "../utils/arrays.ts"; // The time in ms that the crawler will wait loop iterations if there // have not been any checkpoints to consume in the last iteration. @@ -103,13 +104,11 @@ export default class EventIndex extends EventEmitter { const client = MatrixClientPeg.safeGet(); const rooms = client.getRooms(); - const isRoomEncrypted = (room: Room): boolean => { - return client.isRoomEncrypted(room.roomId); - }; - // We only care to crawl the encrypted rooms, non-encrypted // rooms can use the search provided by the homeserver. - const encryptedRooms = rooms.filter(isRoomEncrypted); + const encryptedRooms = await asyncFilter(rooms, async (room) => + Boolean(await client.getCrypto()?.isEncryptionEnabledInRoom(room.roomId)), + ); logger.log("EventIndex: Adding initial crawler checkpoints"); From 6bf06da02ca2c0cf72f0e12a418d07f05a86da3a Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 14 Nov 2024 17:59:40 +0100 Subject: [PATCH 04/13] Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `SendMessageComposer.tsx` --- .../views/rooms/SendMessageComposer.tsx | 24 +++++++++---------- .../views/rooms/SendMessageComposer-test.tsx | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 252957c2c77..ea49e9fcbe3 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -243,7 +243,7 @@ export class SendMessageComposer extends React.Component; - private readonly prepareToEncrypt?: DebouncedFunc<() => void>; + private prepareToEncrypt?: DebouncedFunc<() => void>; private readonly editorRef = createRef(); private model: EditorModel; private currentlyComposedEditorState: SerializedPart[] | null = null; @@ -253,25 +253,25 @@ export class SendMessageComposer extends React.Component) { super(props, context); - if (this.props.mxClient.getCrypto() && this.props.mxClient.isRoomEncrypted(this.props.room.roomId)) { - this.prepareToEncrypt = throttle( - () => { - this.props.mxClient.getCrypto()?.prepareToEncrypt(this.props.room); - }, - 60000, - { leading: true, trailing: false }, - ); - } - const partCreator = new CommandPartCreator(this.props.room, this.props.mxClient); const parts = this.restoreStoredEditorState(partCreator) || []; this.model = new EditorModel(parts, partCreator); this.sendHistoryManager = new SendHistoryManager(this.props.room.roomId, "mx_cider_history_"); } - public componentDidMount(): void { + public async componentDidMount(): Promise { window.addEventListener("beforeunload", this.saveStoredEditorState); this.dispatcherRef = dis.register(this.onAction); + + if (await this.props.mxClient.getCrypto()?.isEncryptionEnabledInRoom(this.props.room.roomId)) { + this.prepareToEncrypt = throttle( + () => { + this.props.mxClient.getCrypto()?.prepareToEncrypt(this.props.room); + }, + 60000, + { leading: true, trailing: false }, + ); + } } public componentDidUpdate(prevProps: ISendMessageComposerProps): void { diff --git a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx index e423d03ea9f..aa45525c4f0 100644 --- a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx @@ -605,7 +605,7 @@ describe("", () => { it("should call prepareToEncrypt when the user is typing", async () => { const cli = stubClient(); - cli.isRoomEncrypted = jest.fn().mockReturnValue(true); + jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); const room = mkStubRoom("!roomId:server", "Room", cli); expect(cli.getCrypto()!.prepareToEncrypt).not.toHaveBeenCalled(); From de765bb51be2c082f84e452c3596354aca35b29d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 14 Nov 2024 17:59:52 +0100 Subject: [PATCH 05/13] Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `ScalarMessaging.ts` --- src/ScalarMessaging.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts index cac5b561a03..3e3723b8fce 100644 --- a/src/ScalarMessaging.ts +++ b/src/ScalarMessaging.ts @@ -514,7 +514,7 @@ function getWidgets(event: MessageEvent, roomId: string | null): void { sendResponse(event, widgetStateEvents); } -function getRoomEncState(event: MessageEvent, roomId: string): void { +async function getRoomEncState(event: MessageEvent, roomId: string): Promise { const client = MatrixClientPeg.get(); if (!client) { sendError(event, _t("widget|error_need_to_be_logged_in")); @@ -525,7 +525,7 @@ function getRoomEncState(event: MessageEvent, roomId: string): void { sendError(event, _t("scalar|error_room_unknown")); return; } - const roomIsEncrypted = MatrixClientPeg.safeGet().isRoomEncrypted(roomId); + const roomIsEncrypted = Boolean(await client.getCrypto()?.isEncryptionEnabledInRoom(roomId)); sendResponse(event, roomIsEncrypted); } @@ -841,7 +841,7 @@ async function readEvents( } } -const onMessage = function (event: MessageEvent): void { +const onMessage = async function (event: MessageEvent): Promise { if (!event.origin) { // @ts-ignore - stupid chrome event.origin = event.originalEvent.origin; @@ -928,7 +928,7 @@ const onMessage = function (event: MessageEvent): void { getMembershipCount(event, roomId); return; } else if (event.data.action === Action.GetRoomEncryptionState) { - getRoomEncState(event, roomId); + await getRoomEncState(event, roomId); return; } else if (event.data.action === Action.CanSendEvent) { canSendEvent(event, roomId); From a0d0fa787b28ce01126c3cbc06f8460d8b3931de Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 14 Nov 2024 18:00:07 +0100 Subject: [PATCH 06/13] Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `RolesRoomSettingsTab.tsx` --- .../tabs/room/RolesRoomSettingsTab.tsx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx index ec8a2b87188..56feaf6f13f 100644 --- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx @@ -127,12 +127,27 @@ interface IProps { room: Room; } -export default class RolesRoomSettingsTab extends React.Component { +interface RolesRoomSettingsTabState { + isRoomEncrypted: boolean; +} + +export default class RolesRoomSettingsTab extends React.Component { public static contextType = MatrixClientContext; public declare context: React.ContextType; - public componentDidMount(): void { + public constructor(props: IProps) { + super(props); + this.state = { + isRoomEncrypted: false, + }; + } + + public async componentDidMount(): Promise { this.context.on(RoomStateEvent.Update, this.onRoomStateUpdate); + this.setState({ + isRoomEncrypted: + (await this.context.getCrypto()?.isEncryptionEnabledInRoom(this.props.room.roomId)) || false, + }); } public componentWillUnmount(): void { @@ -416,7 +431,7 @@ export default class RolesRoomSettingsTab extends React.Component { .filter(Boolean); // hide the power level selector for enabling E2EE if it the room is already encrypted - if (client.isRoomEncrypted(this.props.room.roomId)) { + if (this.state.isRoomEncrypted) { delete eventsLevels[EventType.RoomEncryption]; } From 72eddef76c2390c1acbd0551c2a865f1b875a7c8 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 18 Nov 2024 18:15:55 +0100 Subject: [PATCH 07/13] Add reject doc to `asyncFilter` --- src/utils/arrays.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts index 82b54e9df69..da8157adce1 100644 --- a/src/utils/arrays.ts +++ b/src/utils/arrays.ts @@ -352,6 +352,7 @@ export async function asyncSomeParallel( /** * Async version of Array.filter. + * If one of the promises rejects, the whole operation will reject. * @param values * @param predicate */ From 60529538c39031ae7e91faac2d1338036cdc588f Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 18 Nov 2024 18:20:02 +0100 Subject: [PATCH 08/13] Reverse `MemberListStore.loadMembers` condition --- src/stores/MemberListStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/MemberListStore.ts b/src/stores/MemberListStore.ts index cf4b9339831..e500dec84c6 100644 --- a/src/stores/MemberListStore.ts +++ b/src/stores/MemberListStore.ts @@ -70,7 +70,7 @@ export class MemberListStore { return []; } - if (!(await this.isLazyLoadingEnabled(roomId)) || this.loadedRooms.has(roomId)) { + if (this.loadedRooms.has(roomId) || !(await this.isLazyLoadingEnabled(roomId))) { // nice and easy, we must already have all the members so just return them. return this.loadMembersInRoom(room); } From a5a9e5fe39b242afbe1856c1dae35ad5e59b3887 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 19 Nov 2024 10:08:41 +0100 Subject: [PATCH 09/13] Remove async for `ScalarMessaging.ts` --- src/ScalarMessaging.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts index 3e3723b8fce..82846b9abf4 100644 --- a/src/ScalarMessaging.ts +++ b/src/ScalarMessaging.ts @@ -841,7 +841,7 @@ async function readEvents( } } -const onMessage = async function (event: MessageEvent): Promise { +const onMessage = function (event: MessageEvent): void { if (!event.origin) { // @ts-ignore - stupid chrome event.origin = event.originalEvent.origin; @@ -928,7 +928,7 @@ const onMessage = async function (event: MessageEvent): Promise { getMembershipCount(event, roomId); return; } else if (event.data.action === Action.GetRoomEncryptionState) { - await getRoomEncState(event, roomId); + getRoomEncState(event, roomId); return; } else if (event.data.action === Action.CanSendEvent) { canSendEvent(event, roomId); From 90cd420f4ad63edcbd3bf63dbfc4854de49f08f8 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 19 Nov 2024 10:47:57 +0100 Subject: [PATCH 10/13] Display permission section only after `isEncrypted` is computed --- .../tabs/room/RolesRoomSettingsTab.tsx | 27 +++++----- .../tabs/room/RolesRoomSettingsTab-test.tsx | 49 ++++++++++--------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx index 56feaf6f13f..8261bfd3eb4 100644 --- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx @@ -129,6 +129,7 @@ interface IProps { interface RolesRoomSettingsTabState { isRoomEncrypted: boolean; + isReady: boolean; } export default class RolesRoomSettingsTab extends React.Component { @@ -138,6 +139,7 @@ export default class RolesRoomSettingsTab extends React.Component} {mutedUsersSection} {bannedUsersSection} - - {powerSelectors} - {eventPowerSelectors} - + {this.state.isReady && ( + + {powerSelectors} + {eventPowerSelectors} + + )} ); diff --git a/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx index 45855a0e25f..000c38c771d 100644 --- a/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx +++ b/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx @@ -27,16 +27,19 @@ describe("RolesRoomSettingsTab", () => { let cli: MatrixClient; let room: Room; - const renderTab = (propRoom: Room = room): RenderResult => { - return render(, withClientContextRenderOptions(cli)); + const renderTab = async (propRoom: Room = room): Promise => { + const renderResult = render(, withClientContextRenderOptions(cli)); + // Wait for the tab to be ready + await waitFor(() => expect(screen.getByText("Permissions")).toBeInTheDocument()); + return renderResult; }; - const getVoiceBroadcastsSelect = (): HTMLElement => { - return renderTab().container.querySelector("select[label='Voice broadcasts']")!; + const getVoiceBroadcastsSelect = async (): Promise => { + return (await renderTab()).container.querySelector("select[label='Voice broadcasts']")!; }; - const getVoiceBroadcastsSelectedOption = (): HTMLElement => { - return renderTab().container.querySelector("select[label='Voice broadcasts'] option:checked")!; + const getVoiceBroadcastsSelectedOption = async (): Promise => { + return (await renderTab()).container.querySelector("select[label='Voice broadcasts'] option:checked")!; }; beforeEach(() => { @@ -45,7 +48,7 @@ describe("RolesRoomSettingsTab", () => { room = mkStubRoom(roomId, "test room", cli); }); - it("should allow an Admin to demote themselves but not others", () => { + it("should allow an Admin to demote themselves but not others", async () => { mocked(cli.getRoom).mockReturnValue(room); // @ts-ignore - mocked doesn't support overloads properly mocked(room.currentState.getStateEvents).mockImplementation((type, key) => { @@ -67,19 +70,19 @@ describe("RolesRoomSettingsTab", () => { return null; }); mocked(room.currentState.mayClientSendStateEvent).mockReturnValue(true); - const { container } = renderTab(); + const { container } = await renderTab(); expect(container.querySelector(`[placeholder="${cli.getUserId()}"]`)).not.toBeDisabled(); expect(container.querySelector(`[placeholder="@admin:server"]`)).toBeDisabled(); }); - it("should initially show »Moderator« permission for »Voice broadcasts«", () => { - expect(getVoiceBroadcastsSelectedOption().textContent).toBe("Moderator"); + it("should initially show »Moderator« permission for »Voice broadcasts«", async () => { + expect((await getVoiceBroadcastsSelectedOption()).textContent).toBe("Moderator"); }); describe("when setting »Default« permission for »Voice broadcasts«", () => { - beforeEach(() => { - fireEvent.change(getVoiceBroadcastsSelect(), { + beforeEach(async () => { + fireEvent.change(await getVoiceBroadcastsSelect(), { target: { value: 0 }, }); }); @@ -122,12 +125,12 @@ describe("RolesRoomSettingsTab", () => { }); describe("Join Element calls", () => { - it("defaults to moderator for joining calls", () => { - expect(getJoinCallSelectedOption(renderTab())?.textContent).toBe("Moderator"); + it("defaults to moderator for joining calls", async () => { + expect(getJoinCallSelectedOption(await renderTab())?.textContent).toBe("Moderator"); }); - it("can change joining calls power level", () => { - const tab = renderTab(); + it("can change joining calls power level", async () => { + const tab = await renderTab(); fireEvent.change(getJoinCallSelect(tab), { target: { value: 0 }, @@ -143,12 +146,12 @@ describe("RolesRoomSettingsTab", () => { }); describe("Start Element calls", () => { - it("defaults to moderator for starting calls", () => { - expect(getStartCallSelectedOption(renderTab())?.textContent).toBe("Moderator"); + it("defaults to moderator for starting calls", async () => { + expect(getStartCallSelectedOption(await renderTab())?.textContent).toBe("Moderator"); }); - it("can change starting calls power level", () => { - const tab = renderTab(); + it("can change starting calls power level", async () => { + const tab = await renderTab(); fireEvent.change(getStartCallSelect(tab), { target: { value: 0 }, @@ -164,10 +167,10 @@ describe("RolesRoomSettingsTab", () => { }); }); - it("hides when group calls disabled", () => { + it("hides when group calls disabled", async () => { setGroupCallsEnabled(false); - const tab = renderTab(); + const tab = await renderTab(); expect(getStartCallSelect(tab)).toBeFalsy(); expect(getStartCallSelectedOption(tab)).toBeFalsy(); @@ -250,7 +253,7 @@ describe("RolesRoomSettingsTab", () => { return null; }); mocked(room.currentState.mayClientSendStateEvent).mockReturnValue(true); - const { container } = renderTab(); + const { container } = await renderTab(); const selector = container.querySelector(`[placeholder="${cli.getUserId()}"]`)!; fireEvent.change(selector, { target: { value: "50" } }); From 4d4e0373919ad4b0e6fa53de63786c880ed6845a Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 19 Nov 2024 15:02:03 +0100 Subject: [PATCH 11/13] Display composer only after `isEncrypted` is computed --- .../views/rooms/SendMessageComposer.tsx | 10 ++- .../views/rooms/MessageComposer-test.tsx | 73 +++++++++++-------- .../views/rooms/SendMessageComposer-test.tsx | 42 ++++++----- 3 files changed, 73 insertions(+), 52 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index ea49e9fcbe3..29f2a872060 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -239,7 +239,11 @@ interface ISendMessageComposerProps extends MatrixClientProps { toggleStickerPickerOpen: () => void; } -export class SendMessageComposer extends React.Component { +interface SendMessageComposerState { + isReady: boolean; +} + +export class SendMessageComposer extends React.Component { public static contextType = RoomContext; public declare context: React.ContextType; @@ -257,6 +261,7 @@ export class SendMessageComposer extends React.Component { @@ -272,6 +277,7 @@ export class SendMessageComposer extends React.Component { describe("for a Room", () => { const room = mkStubRoom("!roomId:server", "Room 1", cli); - it("Renders a SendMessageComposer and MessageComposerButtons by default", () => { - wrapAndRender({ room }); + it("Renders a SendMessageComposer and MessageComposerButtons by default", async () => { + await wrapAndRender({ room }); expect(screen.getByLabelText("Send a message…")).toBeInTheDocument(); }); - it("Does not render a SendMessageComposer or MessageComposerButtons when user has no permission", () => { - wrapAndRender({ room }, false); + it("Does not render a SendMessageComposer or MessageComposerButtons when user has no permission", async () => { + await wrapAndRender({ room }, false); expect(screen.queryByLabelText("Send a message…")).not.toBeInTheDocument(); expect(screen.getByText("You do not have permission to post to this room")).toBeInTheDocument(); }); - it("Does not render a SendMessageComposer or MessageComposerButtons when room is tombstoned", () => { - wrapAndRender( + it("Does not render a SendMessageComposer or MessageComposerButtons when room is tombstoned", async () => { + await wrapAndRender( { room }, true, false, @@ -135,15 +135,17 @@ describe("MessageComposer", () => { let roomContext: IRoomState; let resizeNotifier: ResizeNotifier; - beforeEach(() => { + beforeEach(async () => { jest.useFakeTimers(); resizeNotifier = { notifyTimelineHeightChanged: jest.fn(), } as unknown as ResizeNotifier; - roomContext = wrapAndRender({ - room, - resizeNotifier, - }).roomContext; + roomContext = ( + await wrapAndRender({ + room, + resizeNotifier, + }) + ).roomContext; }); it("should call notifyTimelineHeightChanged() for the same context", () => { @@ -185,8 +187,8 @@ describe("MessageComposer", () => { [true, false].forEach((value: boolean) => { describe(`when ${setting} = ${value}`, () => { beforeEach(async () => { - SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value); - wrapAndRender({ room }); + await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value); + await wrapAndRender({ room }); await act(async () => { await userEvent.click(screen.getByLabelText("More options")); }); @@ -230,14 +232,14 @@ describe("MessageComposer", () => { }); }); - it("should not render the send button", () => { - wrapAndRender({ room }); + it("should not render the send button", async () => { + await wrapAndRender({ room }); expect(screen.queryByLabelText("Send message")).not.toBeInTheDocument(); }); describe("when a message has been entered", () => { beforeEach(async () => { - const renderResult = wrapAndRender({ room }).renderResult; + const renderResult = (await wrapAndRender({ room })).renderResult; await addTextToComposerRTL(renderResult, "Hello"); }); @@ -259,7 +261,7 @@ describe("MessageComposer", () => { describe("when a non-resize event occurred in UIStore", () => { beforeEach(async () => { - wrapAndRender({ room }); + await wrapAndRender({ room }); await openStickerPicker(); resizeCallback("test", {}); }); @@ -271,7 +273,7 @@ describe("MessageComposer", () => { describe("when a resize to narrow event occurred in UIStore", () => { beforeEach(async () => { - wrapAndRender({ room }, true, true); + await wrapAndRender({ room }, true, true); await openStickerPicker(); resizeCallback(UI_EVENTS.Resize, {}); }); @@ -293,7 +295,7 @@ describe("MessageComposer", () => { describe("when a resize to non-narrow event occurred in UIStore", () => { beforeEach(async () => { - wrapAndRender({ room }, true, false); + await wrapAndRender({ room }, true, false); await openStickerPicker(); resizeCallback(UI_EVENTS.Resize, {}); }); @@ -315,13 +317,13 @@ describe("MessageComposer", () => { }); describe("when not replying to an event", () => { - it("should pass the expected placeholder to SendMessageComposer", () => { - wrapAndRender({ room }); + it("should pass the expected placeholder to SendMessageComposer", async () => { + await wrapAndRender({ room }); expect(screen.getByLabelText("Send a message…")).toBeInTheDocument(); }); - it("and an e2e status it should pass the expected placeholder to SendMessageComposer", () => { - wrapAndRender({ + it("and an e2e status it should pass the expected placeholder to SendMessageComposer", async () => { + await wrapAndRender({ room, e2eStatus: E2EStatus.Normal, }); @@ -334,8 +336,8 @@ describe("MessageComposer", () => { let props: Partial>; const checkPlaceholder = (expected: string) => { - it("should pass the expected placeholder to SendMessageComposer", () => { - wrapAndRender(props); + it("should pass the expected placeholder to SendMessageComposer", async () => { + await wrapAndRender(props); expect(screen.getByLabelText(expected)).toBeInTheDocument(); }); }; @@ -393,7 +395,7 @@ describe("MessageComposer", () => { describe("when clicking start a voice message", () => { beforeEach(async () => { - wrapAndRender({ room }); + await wrapAndRender({ room }); await startVoiceMessage(); await flushPromises(); }); @@ -406,7 +408,7 @@ describe("MessageComposer", () => { describe("when recording a voice broadcast and trying to start a voice message", () => { beforeEach(async () => { setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Started); - wrapAndRender({ room }); + await wrapAndRender({ room }); await startVoiceMessage(); await waitEnoughCyclesForModal(); }); @@ -420,7 +422,7 @@ describe("MessageComposer", () => { describe("when there is a stopped voice broadcast recording and trying to start a voice message", () => { beforeEach(async () => { setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Stopped); - wrapAndRender({ room }); + await wrapAndRender({ room }); await startVoiceMessage(); await waitEnoughCyclesForModal(); }); @@ -436,7 +438,7 @@ describe("MessageComposer", () => { const localRoom = new LocalRoom("!room:example.com", cli, cli.getUserId()!); it("should not show the stickers button", async () => { - wrapAndRender({ room: localRoom }); + await wrapAndRender({ room: localRoom }); await act(async () => { await userEvent.click(screen.getByLabelText("More options")); }); @@ -448,7 +450,7 @@ describe("MessageComposer", () => { const room = mkStubRoom("!roomId:server", "Room 1", cli); const messageText = "Test Text"; await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true); - const { renderResult, rawComponent } = wrapAndRender({ room }); + const { renderResult, rawComponent } = await wrapAndRender({ room }, true, false, undefined, true); const { unmount, rerender } = renderResult; await act(async () => { @@ -490,11 +492,12 @@ describe("MessageComposer", () => { }, 10000); }); -function wrapAndRender( +async function wrapAndRender( props: Partial> = {}, canSendMessages = true, narrow = false, tombstone?: MatrixEvent, + ignoreWaitForRender = false, ) { const mockClient = MatrixClientPeg.safeGet(); const roomId = "myroomid"; @@ -527,9 +530,15 @@ function wrapAndRender( ); + + const renderResult = render(getRawComponent(props, roomContext, mockClient)); + if (!ignoreWaitForRender && canSendMessages && !tombstone) { + await waitFor(() => expect(renderResult.getByRole("textbox")).toBeInTheDocument()); + } + return { rawComponent: getRawComponent(props, roomContext, mockClient), - renderResult: render(getRawComponent(props, roomContext, mockClient)), + renderResult, roomContext, }; } diff --git a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx index aa45525c4f0..09cc76191e3 100644 --- a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import React from "react"; -import { fireEvent, render, waitFor } from "jest-matrix-react"; +import { fireEvent, render, waitFor, screen } from "jest-matrix-react"; import { IContent, MatrixClient, MsgType } from "matrix-js-sdk/src/matrix"; import { mocked } from "jest-mock"; import userEvent from "@testing-library/user-event"; @@ -369,12 +369,15 @@ describe("", () => { ); - const getComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => { - return render(getRawComponent(props, roomContext, client)); + const getComponent = async (props = {}, roomContext = defaultRoomContext, client = mockClient) => { + const renderResult = render(getRawComponent(props, roomContext, client)); + // Wait for the composer to be rendered + await waitFor(() => expect(screen.getByRole("textbox")).toBeInTheDocument()); + return renderResult; }; - it("renders text and placeholder correctly", () => { - const { container } = getComponent({ placeholder: "placeholder string" }); + it("renders text and placeholder correctly", async () => { + const { container } = await getComponent({ placeholder: "placeholder string" }); expect(container.querySelectorAll('[aria-label="placeholder string"]')).toHaveLength(1); @@ -383,9 +386,9 @@ describe("", () => { expect(container.textContent).toBe("Test Text"); }); - it("correctly persists state to and from localStorage", () => { + it("correctly persists state to and from localStorage", async () => { const props = { replyToEvent: mockEvent }; - const { container, unmount, rerender } = getComponent(props); + const { container, unmount, rerender } = await getComponent(props); addTextToComposer(container, "Test Text"); @@ -403,7 +406,7 @@ describe("", () => { // ensure the correct model is re-loaded rerender(getRawComponent(props)); - expect(container.textContent).toBe("Test Text"); + await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent("Test Text")); expect(spyDispatcher).toHaveBeenCalledWith({ action: "reply_to_event", event: mockEvent, @@ -417,8 +420,8 @@ describe("", () => { expect(container.textContent).toBe(""); }); - it("persists state correctly without replyToEvent onbeforeunload", () => { - const { container } = getComponent(); + it("persists state correctly without replyToEvent onbeforeunload", async () => { + const { container } = await getComponent(); addTextToComposer(container, "Hello World"); @@ -437,7 +440,7 @@ describe("", () => { it("persists to session history upon sending", async () => { mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = getComponent({ replyToEvent: mockEvent }); + const { container } = await getComponent({ replyToEvent: mockEvent }); addTextToComposer(container, "This is a message"); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); @@ -458,7 +461,7 @@ describe("", () => { }); }); - it("correctly sends a message", () => { + it("correctly sends a message", async () => { mocked(doMaybeLocalRoomAction).mockImplementation( (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); @@ -466,7 +469,7 @@ describe("", () => { ); mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = getComponent(); + const { container } = await getComponent(); addTextToComposer(container, "test message"); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); @@ -495,7 +498,7 @@ describe("", () => { }); mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = getComponent({ replyToEvent }); + const { container } = await getComponent({ replyToEvent }); addTextToComposer(container, "/tableflip"); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); @@ -516,7 +519,7 @@ describe("", () => { ); }); - it("shows chat effects on message sending", () => { + it("shows chat effects on message sending", async () => { mocked(doMaybeLocalRoomAction).mockImplementation( (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); @@ -524,7 +527,7 @@ describe("", () => { ); mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = getComponent(); + const { container } = await getComponent(); addTextToComposer(container, "🎉"); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); @@ -538,7 +541,7 @@ describe("", () => { expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: `effects.confetti` }); }); - it("not to send chat effects on message sending for threads", () => { + it("not to send chat effects on message sending for threads", async () => { mocked(doMaybeLocalRoomAction).mockImplementation( (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); @@ -546,7 +549,7 @@ describe("", () => { ); mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = getComponent({ + const { container } = await getComponent({ relation: { rel_type: "m.thread", event_id: "$yolo", @@ -615,7 +618,8 @@ describe("", () => { , ); - + // Wait for the composer to be rendered + await waitFor(() => expect(screen.getByRole("textbox")).toBeInTheDocument()); const composer = container.querySelector(".mx_BasicMessageComposer_input")!; // Does not trigger on keydown as that'll cause false negatives for global shortcuts From cfd0ae1f9c5405bf91836e52272e5de4254688b8 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 19 Nov 2024 17:40:40 +0100 Subject: [PATCH 12/13] Revert "Display composer only after `isEncrypted` is computed" This reverts commit 4d4e0373919ad4b0e6fa53de63786c880ed6845a. --- .../views/rooms/SendMessageComposer.tsx | 10 +-- .../views/rooms/MessageComposer-test.tsx | 73 ++++++++----------- .../views/rooms/SendMessageComposer-test.tsx | 42 +++++------ 3 files changed, 52 insertions(+), 73 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 29f2a872060..ea49e9fcbe3 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -239,11 +239,7 @@ interface ISendMessageComposerProps extends MatrixClientProps { toggleStickerPickerOpen: () => void; } -interface SendMessageComposerState { - isReady: boolean; -} - -export class SendMessageComposer extends React.Component { +export class SendMessageComposer extends React.Component { public static contextType = RoomContext; public declare context: React.ContextType; @@ -261,7 +257,6 @@ export class SendMessageComposer extends React.Component { @@ -277,7 +272,6 @@ export class SendMessageComposer extends React.Component { describe("for a Room", () => { const room = mkStubRoom("!roomId:server", "Room 1", cli); - it("Renders a SendMessageComposer and MessageComposerButtons by default", async () => { - await wrapAndRender({ room }); + it("Renders a SendMessageComposer and MessageComposerButtons by default", () => { + wrapAndRender({ room }); expect(screen.getByLabelText("Send a message…")).toBeInTheDocument(); }); - it("Does not render a SendMessageComposer or MessageComposerButtons when user has no permission", async () => { - await wrapAndRender({ room }, false); + it("Does not render a SendMessageComposer or MessageComposerButtons when user has no permission", () => { + wrapAndRender({ room }, false); expect(screen.queryByLabelText("Send a message…")).not.toBeInTheDocument(); expect(screen.getByText("You do not have permission to post to this room")).toBeInTheDocument(); }); - it("Does not render a SendMessageComposer or MessageComposerButtons when room is tombstoned", async () => { - await wrapAndRender( + it("Does not render a SendMessageComposer or MessageComposerButtons when room is tombstoned", () => { + wrapAndRender( { room }, true, false, @@ -135,17 +135,15 @@ describe("MessageComposer", () => { let roomContext: IRoomState; let resizeNotifier: ResizeNotifier; - beforeEach(async () => { + beforeEach(() => { jest.useFakeTimers(); resizeNotifier = { notifyTimelineHeightChanged: jest.fn(), } as unknown as ResizeNotifier; - roomContext = ( - await wrapAndRender({ - room, - resizeNotifier, - }) - ).roomContext; + roomContext = wrapAndRender({ + room, + resizeNotifier, + }).roomContext; }); it("should call notifyTimelineHeightChanged() for the same context", () => { @@ -187,8 +185,8 @@ describe("MessageComposer", () => { [true, false].forEach((value: boolean) => { describe(`when ${setting} = ${value}`, () => { beforeEach(async () => { - await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value); - await wrapAndRender({ room }); + SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value); + wrapAndRender({ room }); await act(async () => { await userEvent.click(screen.getByLabelText("More options")); }); @@ -232,14 +230,14 @@ describe("MessageComposer", () => { }); }); - it("should not render the send button", async () => { - await wrapAndRender({ room }); + it("should not render the send button", () => { + wrapAndRender({ room }); expect(screen.queryByLabelText("Send message")).not.toBeInTheDocument(); }); describe("when a message has been entered", () => { beforeEach(async () => { - const renderResult = (await wrapAndRender({ room })).renderResult; + const renderResult = wrapAndRender({ room }).renderResult; await addTextToComposerRTL(renderResult, "Hello"); }); @@ -261,7 +259,7 @@ describe("MessageComposer", () => { describe("when a non-resize event occurred in UIStore", () => { beforeEach(async () => { - await wrapAndRender({ room }); + wrapAndRender({ room }); await openStickerPicker(); resizeCallback("test", {}); }); @@ -273,7 +271,7 @@ describe("MessageComposer", () => { describe("when a resize to narrow event occurred in UIStore", () => { beforeEach(async () => { - await wrapAndRender({ room }, true, true); + wrapAndRender({ room }, true, true); await openStickerPicker(); resizeCallback(UI_EVENTS.Resize, {}); }); @@ -295,7 +293,7 @@ describe("MessageComposer", () => { describe("when a resize to non-narrow event occurred in UIStore", () => { beforeEach(async () => { - await wrapAndRender({ room }, true, false); + wrapAndRender({ room }, true, false); await openStickerPicker(); resizeCallback(UI_EVENTS.Resize, {}); }); @@ -317,13 +315,13 @@ describe("MessageComposer", () => { }); describe("when not replying to an event", () => { - it("should pass the expected placeholder to SendMessageComposer", async () => { - await wrapAndRender({ room }); + it("should pass the expected placeholder to SendMessageComposer", () => { + wrapAndRender({ room }); expect(screen.getByLabelText("Send a message…")).toBeInTheDocument(); }); - it("and an e2e status it should pass the expected placeholder to SendMessageComposer", async () => { - await wrapAndRender({ + it("and an e2e status it should pass the expected placeholder to SendMessageComposer", () => { + wrapAndRender({ room, e2eStatus: E2EStatus.Normal, }); @@ -336,8 +334,8 @@ describe("MessageComposer", () => { let props: Partial>; const checkPlaceholder = (expected: string) => { - it("should pass the expected placeholder to SendMessageComposer", async () => { - await wrapAndRender(props); + it("should pass the expected placeholder to SendMessageComposer", () => { + wrapAndRender(props); expect(screen.getByLabelText(expected)).toBeInTheDocument(); }); }; @@ -395,7 +393,7 @@ describe("MessageComposer", () => { describe("when clicking start a voice message", () => { beforeEach(async () => { - await wrapAndRender({ room }); + wrapAndRender({ room }); await startVoiceMessage(); await flushPromises(); }); @@ -408,7 +406,7 @@ describe("MessageComposer", () => { describe("when recording a voice broadcast and trying to start a voice message", () => { beforeEach(async () => { setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Started); - await wrapAndRender({ room }); + wrapAndRender({ room }); await startVoiceMessage(); await waitEnoughCyclesForModal(); }); @@ -422,7 +420,7 @@ describe("MessageComposer", () => { describe("when there is a stopped voice broadcast recording and trying to start a voice message", () => { beforeEach(async () => { setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Stopped); - await wrapAndRender({ room }); + wrapAndRender({ room }); await startVoiceMessage(); await waitEnoughCyclesForModal(); }); @@ -438,7 +436,7 @@ describe("MessageComposer", () => { const localRoom = new LocalRoom("!room:example.com", cli, cli.getUserId()!); it("should not show the stickers button", async () => { - await wrapAndRender({ room: localRoom }); + wrapAndRender({ room: localRoom }); await act(async () => { await userEvent.click(screen.getByLabelText("More options")); }); @@ -450,7 +448,7 @@ describe("MessageComposer", () => { const room = mkStubRoom("!roomId:server", "Room 1", cli); const messageText = "Test Text"; await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true); - const { renderResult, rawComponent } = await wrapAndRender({ room }, true, false, undefined, true); + const { renderResult, rawComponent } = wrapAndRender({ room }); const { unmount, rerender } = renderResult; await act(async () => { @@ -492,12 +490,11 @@ describe("MessageComposer", () => { }, 10000); }); -async function wrapAndRender( +function wrapAndRender( props: Partial> = {}, canSendMessages = true, narrow = false, tombstone?: MatrixEvent, - ignoreWaitForRender = false, ) { const mockClient = MatrixClientPeg.safeGet(); const roomId = "myroomid"; @@ -530,15 +527,9 @@ async function wrapAndRender( ); - - const renderResult = render(getRawComponent(props, roomContext, mockClient)); - if (!ignoreWaitForRender && canSendMessages && !tombstone) { - await waitFor(() => expect(renderResult.getByRole("textbox")).toBeInTheDocument()); - } - return { rawComponent: getRawComponent(props, roomContext, mockClient), - renderResult, + renderResult: render(getRawComponent(props, roomContext, mockClient)), roomContext, }; } diff --git a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx index 09cc76191e3..aa45525c4f0 100644 --- a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import React from "react"; -import { fireEvent, render, waitFor, screen } from "jest-matrix-react"; +import { fireEvent, render, waitFor } from "jest-matrix-react"; import { IContent, MatrixClient, MsgType } from "matrix-js-sdk/src/matrix"; import { mocked } from "jest-mock"; import userEvent from "@testing-library/user-event"; @@ -369,15 +369,12 @@ describe("", () => { ); - const getComponent = async (props = {}, roomContext = defaultRoomContext, client = mockClient) => { - const renderResult = render(getRawComponent(props, roomContext, client)); - // Wait for the composer to be rendered - await waitFor(() => expect(screen.getByRole("textbox")).toBeInTheDocument()); - return renderResult; + const getComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => { + return render(getRawComponent(props, roomContext, client)); }; - it("renders text and placeholder correctly", async () => { - const { container } = await getComponent({ placeholder: "placeholder string" }); + it("renders text and placeholder correctly", () => { + const { container } = getComponent({ placeholder: "placeholder string" }); expect(container.querySelectorAll('[aria-label="placeholder string"]')).toHaveLength(1); @@ -386,9 +383,9 @@ describe("", () => { expect(container.textContent).toBe("Test Text"); }); - it("correctly persists state to and from localStorage", async () => { + it("correctly persists state to and from localStorage", () => { const props = { replyToEvent: mockEvent }; - const { container, unmount, rerender } = await getComponent(props); + const { container, unmount, rerender } = getComponent(props); addTextToComposer(container, "Test Text"); @@ -406,7 +403,7 @@ describe("", () => { // ensure the correct model is re-loaded rerender(getRawComponent(props)); - await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent("Test Text")); + expect(container.textContent).toBe("Test Text"); expect(spyDispatcher).toHaveBeenCalledWith({ action: "reply_to_event", event: mockEvent, @@ -420,8 +417,8 @@ describe("", () => { expect(container.textContent).toBe(""); }); - it("persists state correctly without replyToEvent onbeforeunload", async () => { - const { container } = await getComponent(); + it("persists state correctly without replyToEvent onbeforeunload", () => { + const { container } = getComponent(); addTextToComposer(container, "Hello World"); @@ -440,7 +437,7 @@ describe("", () => { it("persists to session history upon sending", async () => { mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = await getComponent({ replyToEvent: mockEvent }); + const { container } = getComponent({ replyToEvent: mockEvent }); addTextToComposer(container, "This is a message"); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); @@ -461,7 +458,7 @@ describe("", () => { }); }); - it("correctly sends a message", async () => { + it("correctly sends a message", () => { mocked(doMaybeLocalRoomAction).mockImplementation( (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); @@ -469,7 +466,7 @@ describe("", () => { ); mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = await getComponent(); + const { container } = getComponent(); addTextToComposer(container, "test message"); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); @@ -498,7 +495,7 @@ describe("", () => { }); mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = await getComponent({ replyToEvent }); + const { container } = getComponent({ replyToEvent }); addTextToComposer(container, "/tableflip"); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); @@ -519,7 +516,7 @@ describe("", () => { ); }); - it("shows chat effects on message sending", async () => { + it("shows chat effects on message sending", () => { mocked(doMaybeLocalRoomAction).mockImplementation( (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); @@ -527,7 +524,7 @@ describe("", () => { ); mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = await getComponent(); + const { container } = getComponent(); addTextToComposer(container, "🎉"); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); @@ -541,7 +538,7 @@ describe("", () => { expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: `effects.confetti` }); }); - it("not to send chat effects on message sending for threads", async () => { + it("not to send chat effects on message sending for threads", () => { mocked(doMaybeLocalRoomAction).mockImplementation( (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); @@ -549,7 +546,7 @@ describe("", () => { ); mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const { container } = await getComponent({ + const { container } = getComponent({ relation: { rel_type: "m.thread", event_id: "$yolo", @@ -618,8 +615,7 @@ describe("", () => { , ); - // Wait for the composer to be rendered - await waitFor(() => expect(screen.getByRole("textbox")).toBeInTheDocument()); + const composer = container.querySelector(".mx_BasicMessageComposer_input")!; // Does not trigger on keydown as that'll cause false negatives for global shortcuts From 88536b1bc358043b7e69e9c4387977b97c413c50 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 19 Nov 2024 17:40:50 +0100 Subject: [PATCH 13/13] Revert "Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `SendMessageComposer.tsx`" This reverts commit 6bf06da02ca2c0cf72f0e12a418d07f05a86da3a. --- .../views/rooms/SendMessageComposer.tsx | 24 +++++++++---------- .../views/rooms/SendMessageComposer-test.tsx | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index ea49e9fcbe3..252957c2c77 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -243,7 +243,7 @@ export class SendMessageComposer extends React.Component; - private prepareToEncrypt?: DebouncedFunc<() => void>; + private readonly prepareToEncrypt?: DebouncedFunc<() => void>; private readonly editorRef = createRef(); private model: EditorModel; private currentlyComposedEditorState: SerializedPart[] | null = null; @@ -253,17 +253,7 @@ export class SendMessageComposer extends React.Component) { super(props, context); - const partCreator = new CommandPartCreator(this.props.room, this.props.mxClient); - const parts = this.restoreStoredEditorState(partCreator) || []; - this.model = new EditorModel(parts, partCreator); - this.sendHistoryManager = new SendHistoryManager(this.props.room.roomId, "mx_cider_history_"); - } - - public async componentDidMount(): Promise { - window.addEventListener("beforeunload", this.saveStoredEditorState); - this.dispatcherRef = dis.register(this.onAction); - - if (await this.props.mxClient.getCrypto()?.isEncryptionEnabledInRoom(this.props.room.roomId)) { + if (this.props.mxClient.getCrypto() && this.props.mxClient.isRoomEncrypted(this.props.room.roomId)) { this.prepareToEncrypt = throttle( () => { this.props.mxClient.getCrypto()?.prepareToEncrypt(this.props.room); @@ -272,6 +262,16 @@ export class SendMessageComposer extends React.Component", () => { it("should call prepareToEncrypt when the user is typing", async () => { const cli = stubClient(); - jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); + cli.isRoomEncrypted = jest.fn().mockReturnValue(true); const room = mkStubRoom("!roomId:server", "Room", cli); expect(cli.getCrypto()!.prepareToEncrypt).not.toHaveBeenCalled();