diff --git a/linked-dependencies/matrix-react-sdk/src/components/views/dialogs/InviteDialog.tsx b/linked-dependencies/matrix-react-sdk/src/components/views/dialogs/InviteDialog.tsx index bb81d7a05f..71587aee3d 100644 --- a/linked-dependencies/matrix-react-sdk/src/components/views/dialogs/InviteDialog.tsx +++ b/linked-dependencies/matrix-react-sdk/src/components/views/dialogs/InviteDialog.tsx @@ -809,7 +809,10 @@ export default class InviteDialog extends React.PureComponent): void => { - const term = e.target.value; + // :TCHAP: lowercase-invite - const term = e.target.value; + const term = e.target.value?.toLowerCase(); + // end :TCHAP: + this.setState({ filterText: term }); // Debounce server lookups to reduce spam. We don't clear the existing server @@ -879,8 +882,9 @@ export default class InviteDialog extends React.PureComponent screen.getByTestId("invite-dialog-input"); + +const enterIntoSearchField = async (value: string) => { + const searchField = getSearchField(); + await userEvent.clear(searchField); + await userEvent.type(searchField, value + "{enter}"); +}; + +const pasteIntoSearchField = async (value: string) => { + const searchField = getSearchField(); + await userEvent.clear(searchField); + searchField.focus(); + await userEvent.paste(value); + await userEvent.type(searchField, value + "{enter}"); +}; + +const roomId = "!111111111111111111:example.org"; +const aliceId = "@alice:example.org"; +const aliceEmail = "foobar@email.com"; +const aliceUppercaseEmail = "FOOBar@email.com"; + +const aliceProfileInfo: IProfileInfo = { + user_id: aliceId, + display_name: "Alice", +}; + +const bobId = "@bob:example.org"; +const bobProfileInfo: IProfileInfo = { + user_id: bobId, + display_name: "Bob", +}; + +describe("InviteDialog", () => { + let mockClient: Mocked; + let room: Room; + + filterConsole( + "Error retrieving profile for userId @carol:example.com", + "Error retrieving profile for userId @localpart:server.tld", + "Error retrieving profile for userId @localpart:server:tld", + "[Invite:Recents] Excluding @alice:example.org from recents", + ); + + beforeEach(() => { + mockClient = getMockClientWithEventEmitter({ + getUserId: jest.fn().mockReturnValue(bobId), + getSafeUserId: jest.fn().mockReturnValue(bobId), + isGuest: jest.fn().mockReturnValue(false), + getVisibleRooms: jest.fn().mockReturnValue([]), + getRoom: jest.fn(), + getRooms: jest.fn(), + getAccountData: jest.fn(), + getPushActionsForEvent: jest.fn(), + mxcUrlToHttp: jest.fn().mockReturnValue(""), + isRoomEncrypted: jest.fn().mockReturnValue(false), + getProfileInfo: jest.fn().mockImplementation(async (userId: string) => { + if (userId === aliceId) return aliceProfileInfo; + if (userId === bobId) return bobProfileInfo; + + throw new MatrixError({ + errcode: "M_NOT_FOUND", + error: "Profile not found", + }); + }), + getIdentityServerUrl: jest.fn(), + searchUserDirectory: jest.fn().mockResolvedValue({}), + lookupThreePid: jest.fn(), + registerWithIdentityServer: jest.fn().mockResolvedValue({ + access_token: "access_token", + token: "token", + }), + getOpenIdToken: jest.fn().mockResolvedValue({}), + getIdentityAccount: jest.fn().mockResolvedValue({}), + getTerms: jest.fn().mockResolvedValue({ policies: [] }), + supportsThreads: jest.fn().mockReturnValue(false), + isInitialSyncComplete: jest.fn().mockReturnValue(true), + getClientWellKnown: jest.fn().mockResolvedValue({}), + }); + SdkConfig.put({ validated_server_config: {} as ValidatedServerConfig } as IConfigOptions); + DMRoomMap.makeShared(mockClient); + jest.clearAllMocks(); + + room = new Room(roomId, mockClient, mockClient.getSafeUserId()); + + jest.spyOn(DMRoomMap.shared(), "getUniqueRoomsWithIndividuals").mockReturnValue({ + [aliceId]: room, + }); + mockClient.getRooms.mockReturnValue([room]); + mockClient.getRoom.mockReturnValue(room); + + SdkContextClass.instance.client = mockClient; + }); + + afterEach(() => { + Modal.closeCurrentModal(); + SdkContextClass.instance.onLoggedOut(); + SdkContextClass.instance.client = undefined; + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it("should entered values as lowercase", async () => { + mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server"); + mockClient.lookupThreePid.mockResolvedValue({}); + + render(); + + const input = getSearchField(); + input.focus(); + + // Type and enter + await enterIntoSearchField(aliceUppercaseEmail); + + // Because it has been transoformed to lowercase, it shoyld not be found in the document + expect(screen.queryByText(aliceUppercaseEmail)).not.toBeInTheDocument(); + + expect(screen.queryByText(aliceEmail)).toBeInTheDocument(); + + // If it was transformed correctly to a pill, the input should have no value + expect(input).toHaveValue(""); + }); + + it("should add pasted email values as lowercase", async () => { + mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server"); + mockClient.lookupThreePid.mockResolvedValue({}); + + render(); + + // Juste paste some values without enter + await pasteIntoSearchField(aliceUppercaseEmail); + + await flushPromises(); + + // Because it has been transoformed to lowercase, it shoyld not be found in the document + expect(screen.queryByText(aliceUppercaseEmail)).not.toBeInTheDocument(); + + // contrary to the entered values, on this paste test we don't enter, so we have multiple result + expect(screen.queryAllByText(aliceEmail)[0]).toBeInTheDocument(); + }); + + it("should not crash if empty values are entered", async () => { + mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server"); + mockClient.lookupThreePid.mockResolvedValue({}); + + render(); + + const input = getSearchField(); + input.focus(); + + // Type and enter + await enterIntoSearchField(""); + + expect(input).toHaveValue(""); + }); + + it("should not crash if empty values are pasted", async () => { + mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server"); + mockClient.lookupThreePid.mockResolvedValue({}); + + render(); + const input = getSearchField(); + + // Juste paste some values without enter + await pasteIntoSearchField(""); + + expect(input).toHaveValue(""); + }); +}); diff --git a/yarn.lock b/yarn.lock index b52307071b..9f2528cf68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3146,6 +3146,11 @@ "@testing-library/dom" "^8.0.0" "@types/react-dom" "<18.0.0" +"@testing-library/user-event@^14.5.2": + version "14.5.2" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd" + integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ== + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"