/* Copyright 2023 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import { mocked } from "jest-mock"; import React from "react"; import { ICompletion } from "../../../../../../src/autocomplete/Autocompleter"; import { buildQuery, getRoomFromCompletion, getMentionDisplayText, getMentionAttributes, } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/autocomplete"; import { createTestClient, mkRoom } from "../../../../../test-utils"; import * as _mockAvatar from "../../../../../../src/Avatar"; const mockClient = createTestClient(); const mockRoomId = "mockRoomId"; const mockRoom = mkRoom(mockClient, mockRoomId); const createMockCompletion = (props: Partial): ICompletion => { return { completion: "mock", range: { beginning: true, start: 0, end: 0 }, component: React.createElement("div"), ...props, }; }; jest.mock("../../../../../../src/Avatar"); beforeEach(() => jest.clearAllMocks()); afterAll(() => jest.restoreAllMocks()); describe("buildQuery", () => { it("returns an empty string for a falsy argument", () => { expect(buildQuery(null)).toBe(""); }); it("returns an empty string when keyChar is falsy", () => { const noKeyCharSuggestion = { keyChar: "" as const, text: "test", type: "unknown" as const }; expect(buildQuery(noKeyCharSuggestion)).toBe(""); }); it("combines the keyChar and text of the suggestion in the query", () => { const handledSuggestion = { keyChar: "@" as const, text: "alice", type: "mention" as const }; expect(buildQuery(handledSuggestion)).toBe("@alice"); const handledCommand = { keyChar: "/" as const, text: "spoiler", type: "mention" as const }; expect(buildQuery(handledCommand)).toBe("/spoiler"); }); }); describe("getRoomFromCompletion", () => { const createMockRoomCompletion = (props: Partial): ICompletion => { return createMockCompletion({ ...props, type: "room" }); }; it("calls getRoom with completionId if present in the completion", () => { const testId = "arbitraryId"; const completionWithId = createMockRoomCompletion({ completionId: testId }); getRoomFromCompletion(completionWithId, mockClient); expect(mockClient.getRoom).toHaveBeenCalledWith(testId); }); it("calls getRoom with completion if present and correct format", () => { const testCompletion = "arbitraryCompletion"; const completionWithId = createMockRoomCompletion({ completionId: testCompletion }); getRoomFromCompletion(completionWithId, mockClient); expect(mockClient.getRoom).toHaveBeenCalledWith(testCompletion); }); it("calls getRooms if no completionId is present and completion starts with #", () => { const completionWithId = createMockRoomCompletion({ completion: "#hash" }); const result = getRoomFromCompletion(completionWithId, mockClient); expect(mockClient.getRoom).not.toHaveBeenCalled(); expect(mockClient.getRooms).toHaveBeenCalled(); // in this case, because the mock client returns an empty array of rooms // from the call to get rooms, we'd expect the result to be null expect(result).toBe(null); }); }); describe("getMentionDisplayText", () => { it("returns an empty string if we are not handling a user, room or at-room type", () => { const nonHandledCompletionTypes = ["community", "command"] as const; const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type })); nonHandledCompletions.forEach((completion) => { expect(getMentionDisplayText(completion, mockClient)).toBe(""); }); }); it("returns the completion if we are handling a user", () => { const testCompletion = "display this"; const userCompletion = createMockCompletion({ type: "user", completion: testCompletion }); expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion); }); it("returns the room name when the room has a valid completionId", () => { const testCompletionId = "testId"; const userCompletion = createMockCompletion({ type: "room", completionId: testCompletionId }); // as this uses the mockClient, the name will be the mock room name returned from there expect(getMentionDisplayText(userCompletion, mockClient)).toBe(mockClient.getRoom("")?.name); }); it("falls back to the completion for a room if completion starts with #", () => { const testCompletion = "#hash"; const userCompletion = createMockCompletion({ type: "room", completion: testCompletion }); // as this uses the mockClient, the name will be the mock room name returned from there expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion); }); it("returns the completion if we are handling an at-room completion", () => { const testCompletion = "display this"; const atRoomCompletion = createMockCompletion({ type: "at-room", completion: testCompletion }); expect(getMentionDisplayText(atRoomCompletion, mockClient)).toBe(testCompletion); }); }); describe("getMentionAttributes", () => { it("returns an empty map for completion types other than room, user or at-room", () => { const nonHandledCompletionTypes = ["community", "command"] as const; const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type })); nonHandledCompletions.forEach((completion) => { expect(getMentionAttributes(completion, mockClient, mockRoom)).toEqual(new Map()); }); }); const testAvatarUrlForString = "www.stringUrl.com"; const testAvatarUrlForMember = "www.memberUrl.com"; const testAvatarUrlForRoom = "www.roomUrl.com"; const testInitialLetter = "z"; const mockAvatar = mocked(_mockAvatar); mockAvatar.defaultAvatarUrlForString.mockReturnValue(testAvatarUrlForString); mockAvatar.avatarUrlForMember.mockReturnValue(testAvatarUrlForMember); mockAvatar.avatarUrlForRoom.mockReturnValue(testAvatarUrlForRoom); mockAvatar.getInitialLetter.mockReturnValue(testInitialLetter); describe("user mentions", () => { it("returns an empty map when no member can be found", () => { const userCompletion = createMockCompletion({ type: "user" }); // mock not being able to find a member mockRoom.getMember.mockImplementationOnce(() => null); const result = getMentionAttributes(userCompletion, mockClient, mockRoom); expect(result).toEqual(new Map()); }); it("returns expected attributes when avatar url is not default", () => { const userCompletion = createMockCompletion({ type: "user" }); const result = getMentionAttributes(userCompletion, mockClient, mockRoom); expect(result).toEqual( new Map([ ["data-mention-type", "user"], ["style", `--avatar-background: url(${testAvatarUrlForMember}); --avatar-letter: '\u200b'`], ]), ); }); it("returns expected style attributes when avatar url matches default", () => { const userCompletion = createMockCompletion({ type: "user" }); // mock a single implementation of avatarUrlForMember to make it match the default mockAvatar.avatarUrlForMember.mockReturnValueOnce(testAvatarUrlForString); const result = getMentionAttributes(userCompletion, mockClient, mockRoom); expect(result).toEqual( new Map([ ["data-mention-type", "user"], [ "style", `--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`, ], ]), ); }); }); describe("room mentions", () => { it("returns expected attributes when avatar url for room is truthy", () => { const userCompletion = createMockCompletion({ type: "room" }); const result = getMentionAttributes(userCompletion, mockClient, mockRoom); expect(result).toEqual( new Map([ ["data-mention-type", "room"], ["style", `--avatar-background: url(${testAvatarUrlForRoom}); --avatar-letter: '\u200b'`], ]), ); }); it("returns expected style attributes when avatar url for room is falsy", () => { const userCompletion = createMockCompletion({ type: "room" }); // mock a single implementation of avatarUrlForRoom to make it falsy mockAvatar.avatarUrlForRoom.mockReturnValueOnce(null); const result = getMentionAttributes(userCompletion, mockClient, mockRoom); expect(result).toEqual( new Map([ ["data-mention-type", "room"], [ "style", `--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`, ], ]), ); }); }); describe("at-room mentions", () => { it("returns expected attributes when avatar url for room is truthyf", () => { const atRoomCompletion = createMockCompletion({ type: "at-room" }); const result = getMentionAttributes(atRoomCompletion, mockClient, mockRoom); expect(result).toEqual( new Map([ ["data-mention-type", "at-room"], ["style", `--avatar-background: url(${testAvatarUrlForRoom}); --avatar-letter: '\u200b'`], ]), ); }); it("returns expected style attributes when avatar url for room is falsy", () => { const atRoomCompletion = createMockCompletion({ type: "at-room" }); // mock a single implementation of avatarUrlForRoom to make it falsy mockAvatar.avatarUrlForRoom.mockReturnValueOnce(null); const result = getMentionAttributes(atRoomCompletion, mockClient, mockRoom); expect(result).toEqual( new Map([ ["data-mention-type", "at-room"], [ "style", `--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`, ], ]), ); }); }); });