From e4ebcf5731dacfe34e759a7ef49611cf5d4cd1b2 Mon Sep 17 00:00:00 2001 From: alunturner <56027671+alunturner@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:09:38 +0100 Subject: [PATCH] Handle more completion types in rte autocomplete (#10560) * handle at-room * remove console log * update and add tests * tidy up * refactor to switch statement * fix TS error * expand tests * consolidate similar if/else if blocks --- .../components/WysiwygAutocomplete.tsx | 51 +++++++++++++----- .../wysiwyg_composer/utils/autocomplete.ts | 5 +- .../components/WysiwygComposer-test.tsx | 54 ++++++++++++++++++- .../utils/autocomplete-test.ts | 26 +++++++-- 4 files changed, 113 insertions(+), 23 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 709de5fbbc..2b4e0f9682 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -59,21 +59,44 @@ const WysiwygAutocomplete = forwardRef( const client = useMatrixClientContext(); function handleConfirm(completion: ICompletion): void { - // TODO handle all of the completion types - // Using this to pick out the ones we can handle during implementation - if (completion.type === "command") { - // TODO determine if utils in SlashCommands.tsx are required - - // trim the completion as some include trailing spaces, but we always insert a - // trailing space in the rust model anyway - handleCommand(completion.completion.trim()); + if (client === undefined || room === undefined) { + return; } - if (client && room && completion.href && (completion.type === "room" || completion.type === "user")) { - handleMention( - completion.href, - getMentionDisplayText(completion, client), - getMentionAttributes(completion, client, room), - ); + + switch (completion.type) { + case "command": { + // TODO determine if utils in SlashCommands.tsx are required. + // Trim the completion as some include trailing spaces, but we always insert a + // trailing space in the rust model anyway + handleCommand(completion.completion.trim()); + return; + } + case "at-room": { + // TODO improve handling of at-room to either become a span or use a placeholder href + // We have an issue in that we can't use a placeholder because the rust model is always + // applying a prefix to the href, so an href of "#" becomes https://# and also we can not + // represent a plain span in rust + handleMention( + window.location.href, + getMentionDisplayText(completion, client), + getMentionAttributes(completion, client, room), + ); + return; + } + case "room": + case "user": { + if (typeof completion.href === "string") { + handleMention( + completion.href, + getMentionDisplayText(completion, client), + getMentionAttributes(completion, client, room), + ); + } + return; + } + // TODO - handle "community" type + default: + return; } } diff --git a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts index 2bf68f5c76..7f48d8afea 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts @@ -74,7 +74,7 @@ export function getRoomFromCompletion(completion: ICompletion, client: MatrixCli * @returns the text to display in the mention */ export function getMentionDisplayText(completion: ICompletion, client: MatrixClient): string { - if (completion.type === "user") { + if (completion.type === "user" || completion.type === "at-room") { return completion.completion; } else if (completion.type === "room") { // try and get the room and use it's name, if not available, fall back to @@ -132,7 +132,8 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie "data-mention-type": completion.type, "style": `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`, }; + } else if (completion.type === "at-room") { + return { "data-mention-type": completion.type }; } - return {}; } diff --git a/test/components/views/rooms/wysiwyg_composer/components/WysiwygComposer-test.tsx b/test/components/views/rooms/wysiwyg_composer/components/WysiwygComposer-test.tsx index 3f9694e2a3..25f3c8815e 100644 --- a/test/components/views/rooms/wysiwyg_composer/components/WysiwygComposer-test.tsx +++ b/test/components/views/rooms/wysiwyg_composer/components/WysiwygComposer-test.tsx @@ -158,7 +158,7 @@ describe("WysiwygComposer", () => { }); }); - describe("Mentions", () => { + describe("Mentions and commands", () => { const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch"); const mockCompletions: ICompletion[] = [ @@ -181,6 +181,7 @@ describe("WysiwygComposer", () => { { // no href user type: "user", + href: undefined, completion: "user_without_href", completionId: "@user_3:host.local", range: { start: 1, end: 1 }, @@ -201,6 +202,24 @@ describe("WysiwygComposer", () => { range: { start: 1, end: 1 }, component:
room_without_completion_id
, }, + { + type: "command", + completion: "/spoiler", + range: { start: 1, end: 1 }, + component:
/spoiler
, + }, + { + type: "at-room", + completion: "@room", + range: { start: 1, end: 1 }, + component:
@room
, + }, + { + type: "community", + completion: "community-completion", + range: { start: 1, end: 1 }, + component:
community
, + }, ]; const constructMockProvider = (data: ICompletion[]) => @@ -211,9 +230,10 @@ describe("WysiwygComposer", () => { } as unknown as AutocompleteProvider); // for each test we will insert input simulating a user mention + const initialInput = "@abc"; const insertMentionInput = async () => { fireEvent.input(screen.getByRole("textbox"), { - data: "@abc", + data: initialInput, inputType: "insertText", }); @@ -349,6 +369,36 @@ describe("WysiwygComposer", () => { // check that it has inserted a link and falls back to the completion text expect(screen.getByRole("link", { name: "#room_without_completion_id" })).toBeInTheDocument(); }); + + it("selecting a command inserts the command", async () => { + await insertMentionInput(); + + // select the room suggestion + await userEvent.click(screen.getByText("/spoiler")); + + // check that it has inserted the plain text + expect(screen.getByText("/spoiler")).toBeInTheDocument(); + }); + + it("selecting an at-room completion inserts @room", async () => { + await insertMentionInput(); + + // select the room suggestion + await userEvent.click(screen.getByText("@room")); + + // check that it has inserted the @room link + expect(screen.getByRole("link", { name: "@room" })).toBeInTheDocument(); + }); + + it("allows a community completion to pass through", async () => { + await insertMentionInput(); + + // select the room suggestion + await userEvent.click(screen.getByText("community")); + + // check that it we still have the initial text + expect(screen.getByText(initialInput)).toBeInTheDocument(); + }); }); describe("When settings require Ctrl+Enter to send", () => { diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index 7629a5a291..366375380c 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -100,8 +100,8 @@ describe("getRoomFromCompletion", () => { }); 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; + 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) => { @@ -131,12 +131,18 @@ describe("getMentionDisplayText", () => { // 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", () => { - // 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; + it("returns an empty object 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) => { @@ -218,4 +224,14 @@ describe("getMentionAttributes", () => { }); }); }); + + describe("at-room mentions", () => { + it("returns expected attributes", () => { + const atRoomCompletion = createMockCompletion({ type: "at-room" }); + + const result = getMentionAttributes(atRoomCompletion, mockClient, mockRoom); + + expect(result).toEqual({ "data-mention-type": "at-room" }); + }); + }); });