element-web-Github/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts
alunturner 3fa6f8cbf0
Handle command completions in RTE (#10521)
* pass handleCommand prop down and use it in WysiwygAutocomplete

* allow a command to generate a query from buildQuery

* port command functionality into the sendMessage util

* tidy up comments

* remove use of shouldSend and update comments

* remove console log

* make logic more explicit and amend comment

* uncomment replyToEvent block

* update util test

* remove commented out test

* use local text over import from current composer

* expand tests

* expand tests

* handle the FocusAComposer action for the wysiwyg composer

* remove TODO comment

* remove TODO

* test for action dispatch

* fix failing tests

* tidy up tests

* fix TS error and improve typing

* fix TS error

* amend return types for sendMessage, editMessage

* fix null content TS error

* fix another null content TS error

* use as to correct final TS error

* remove undefined argument

* try to fix TS errors for editMessage function usage

* tidy up

* add TODO

* improve comments

* update comment
2023-04-10 12:47:42 +00:00

222 lines
9.0 KiB
TypeScript

/*
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 { 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 },
...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>): 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 or a room type", () => {
const nonHandledCompletionTypes = ["at-room", "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);
});
});
describe("getMentionAttributes", () => {
// TODO handle all completion types
it("returns an empty object for completion types other than room or user", () => {
const nonHandledCompletionTypes = ["at-room", "community", "command"] as const;
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
nonHandledCompletions.forEach((completion) => {
expect(getMentionAttributes(completion, mockClient, mockRoom)).toEqual({});
});
});
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 object 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({});
});
it("returns expected attributes when avatar url is not default", () => {
const userCompletion = createMockCompletion({ type: "user" });
const result = getMentionAttributes(userCompletion, mockClient, mockRoom);
expect(result).toEqual({
"data-mention-type": "user",
"style": `--avatar-background: url(${testAvatarUrlForMember}); --avatar-letter: '-'`,
});
});
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({
"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({
"data-mention-type": "room",
"style": `--avatar-background: url(${testAvatarUrlForRoom}); --avatar-letter: '-'`,
});
});
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({
"data-mention-type": "room",
"style": `--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`,
});
});
});
});