2023-04-06 16:43:49 +08:00
|
|
|
/*
|
|
|
|
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";
|
2023-04-17 15:31:58 +08:00
|
|
|
import React from "react";
|
2023-04-06 16:43:49 +08:00
|
|
|
|
|
|
|
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>): ICompletion => {
|
|
|
|
return {
|
|
|
|
completion: "mock",
|
|
|
|
range: { beginning: true, start: 0, end: 0 },
|
2023-04-17 15:31:58 +08:00
|
|
|
component: React.createElement("div"),
|
2023-04-06 16:43:49 +08:00
|
|
|
...props,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
jest.mock("../../../../../../src/Avatar");
|
2024-01-22 18:53:27 +08:00
|
|
|
jest.mock("../../../../../../src/stores/WidgetStore");
|
|
|
|
jest.mock("../../../../../../src/stores/widgets/WidgetLayoutStore");
|
2023-04-06 16:43:49 +08:00
|
|
|
|
|
|
|
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");
|
2023-04-10 20:47:42 +08:00
|
|
|
|
|
|
|
const handledCommand = { keyChar: "/" as const, text: "spoiler", type: "mention" as const };
|
|
|
|
expect(buildQuery(handledCommand)).toBe("/spoiler");
|
2023-04-06 16:43:49 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("getRoomFromCompletion", () => {
|
|
|
|
const createMockRoomCompletion = (props: Partial<ICompletion>): 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", () => {
|
2023-04-14 17:09:38 +08:00
|
|
|
it("returns an empty string if we are not handling a user, room or at-room type", () => {
|
|
|
|
const nonHandledCompletionTypes = ["community", "command"] as const;
|
2023-04-06 16:43:49 +08:00
|
|
|
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);
|
|
|
|
});
|
2023-04-14 17:09:38 +08:00
|
|
|
|
|
|
|
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);
|
|
|
|
});
|
2023-04-06 16:43:49 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
describe("getMentionAttributes", () => {
|
2023-06-19 16:00:11 +08:00
|
|
|
it("returns an empty map for completion types other than room, user or at-room", () => {
|
2023-04-14 17:09:38 +08:00
|
|
|
const nonHandledCompletionTypes = ["community", "command"] as const;
|
2023-04-06 16:43:49 +08:00
|
|
|
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
|
|
|
|
|
|
|
|
nonHandledCompletions.forEach((completion) => {
|
2023-06-19 16:00:11 +08:00
|
|
|
expect(getMentionAttributes(completion, mockClient, mockRoom)).toEqual(new Map());
|
2023-04-06 16:43:49 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
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", () => {
|
2023-06-19 16:00:11 +08:00
|
|
|
it("returns an empty map when no member can be found", () => {
|
2023-04-06 16:43:49 +08:00
|
|
|
const userCompletion = createMockCompletion({ type: "user" });
|
|
|
|
|
|
|
|
// mock not being able to find a member
|
|
|
|
mockRoom.getMember.mockImplementationOnce(() => null);
|
|
|
|
|
|
|
|
const result = getMentionAttributes(userCompletion, mockClient, mockRoom);
|
2023-06-19 16:00:11 +08:00
|
|
|
expect(result).toEqual(new Map());
|
2023-04-06 16:43:49 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
it("returns expected attributes when avatar url is not default", () => {
|
|
|
|
const userCompletion = createMockCompletion({ type: "user" });
|
|
|
|
|
|
|
|
const result = getMentionAttributes(userCompletion, mockClient, mockRoom);
|
|
|
|
|
2023-06-19 16:00:11 +08:00
|
|
|
expect(result).toEqual(
|
|
|
|
new Map([
|
|
|
|
["data-mention-type", "user"],
|
|
|
|
["style", `--avatar-background: url(${testAvatarUrlForMember}); --avatar-letter: '\u200b'`],
|
|
|
|
]),
|
|
|
|
);
|
2023-04-06 16:43:49 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-06-19 16:00:11 +08:00
|
|
|
expect(result).toEqual(
|
|
|
|
new Map([
|
|
|
|
["data-mention-type", "user"],
|
|
|
|
[
|
|
|
|
"style",
|
|
|
|
`--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
);
|
2023-04-06 16:43:49 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-06-19 16:00:11 +08:00
|
|
|
expect(result).toEqual(
|
|
|
|
new Map([
|
|
|
|
["data-mention-type", "room"],
|
|
|
|
["style", `--avatar-background: url(${testAvatarUrlForRoom}); --avatar-letter: '\u200b'`],
|
|
|
|
]),
|
|
|
|
);
|
2023-04-06 16:43:49 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-06-19 16:00:11 +08:00
|
|
|
expect(result).toEqual(
|
|
|
|
new Map([
|
|
|
|
["data-mention-type", "room"],
|
|
|
|
[
|
|
|
|
"style",
|
|
|
|
`--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
);
|
2023-04-06 16:43:49 +08:00
|
|
|
});
|
|
|
|
});
|
2023-04-14 17:09:38 +08:00
|
|
|
|
|
|
|
describe("at-room mentions", () => {
|
2023-06-21 15:57:22 +08:00
|
|
|
it("returns expected attributes when avatar url for room is truthyf", () => {
|
2023-04-14 17:09:38 +08:00
|
|
|
const atRoomCompletion = createMockCompletion({ type: "at-room" });
|
|
|
|
|
|
|
|
const result = getMentionAttributes(atRoomCompletion, mockClient, mockRoom);
|
|
|
|
|
2023-06-21 15:57:22 +08:00
|
|
|
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}'`,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
);
|
2023-04-14 17:09:38 +08:00
|
|
|
});
|
|
|
|
});
|
2023-04-06 16:43:49 +08:00
|
|
|
});
|