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
This commit is contained in:
alunturner 2023-04-14 10:09:38 +01:00 committed by GitHub
parent 1ae0662872
commit e4ebcf5731
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 113 additions and 23 deletions

View file

@ -59,21 +59,44 @@ const WysiwygAutocomplete = forwardRef(
const client = useMatrixClientContext(); const client = useMatrixClientContext();
function handleConfirm(completion: ICompletion): void { function handleConfirm(completion: ICompletion): void {
// TODO handle all of the completion types if (client === undefined || room === undefined) {
// Using this to pick out the ones we can handle during implementation return;
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 && room && completion.href && (completion.type === "room" || completion.type === "user")) {
handleMention( switch (completion.type) {
completion.href, case "command": {
getMentionDisplayText(completion, client), // TODO determine if utils in SlashCommands.tsx are required.
getMentionAttributes(completion, client, room), // 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;
} }
} }

View file

@ -74,7 +74,7 @@ export function getRoomFromCompletion(completion: ICompletion, client: MatrixCli
* @returns the text to display in the mention * @returns the text to display in the mention
*/ */
export function getMentionDisplayText(completion: ICompletion, client: MatrixClient): string { export function getMentionDisplayText(completion: ICompletion, client: MatrixClient): string {
if (completion.type === "user") { if (completion.type === "user" || completion.type === "at-room") {
return completion.completion; return completion.completion;
} else if (completion.type === "room") { } else if (completion.type === "room") {
// try and get the room and use it's name, if not available, fall back to // 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, "data-mention-type": completion.type,
"style": `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`, "style": `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`,
}; };
} else if (completion.type === "at-room") {
return { "data-mention-type": completion.type };
} }
return {}; return {};
} }

View file

@ -158,7 +158,7 @@ describe("WysiwygComposer", () => {
}); });
}); });
describe("Mentions", () => { describe("Mentions and commands", () => {
const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch"); const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
const mockCompletions: ICompletion[] = [ const mockCompletions: ICompletion[] = [
@ -181,6 +181,7 @@ describe("WysiwygComposer", () => {
{ {
// no href user // no href user
type: "user", type: "user",
href: undefined,
completion: "user_without_href", completion: "user_without_href",
completionId: "@user_3:host.local", completionId: "@user_3:host.local",
range: { start: 1, end: 1 }, range: { start: 1, end: 1 },
@ -201,6 +202,24 @@ describe("WysiwygComposer", () => {
range: { start: 1, end: 1 }, range: { start: 1, end: 1 },
component: <div>room_without_completion_id</div>, component: <div>room_without_completion_id</div>,
}, },
{
type: "command",
completion: "/spoiler",
range: { start: 1, end: 1 },
component: <div>/spoiler</div>,
},
{
type: "at-room",
completion: "@room",
range: { start: 1, end: 1 },
component: <div>@room</div>,
},
{
type: "community",
completion: "community-completion",
range: { start: 1, end: 1 },
component: <div>community</div>,
},
]; ];
const constructMockProvider = (data: ICompletion[]) => const constructMockProvider = (data: ICompletion[]) =>
@ -211,9 +230,10 @@ describe("WysiwygComposer", () => {
} as unknown as AutocompleteProvider); } as unknown as AutocompleteProvider);
// for each test we will insert input simulating a user mention // for each test we will insert input simulating a user mention
const initialInput = "@abc";
const insertMentionInput = async () => { const insertMentionInput = async () => {
fireEvent.input(screen.getByRole("textbox"), { fireEvent.input(screen.getByRole("textbox"), {
data: "@abc", data: initialInput,
inputType: "insertText", inputType: "insertText",
}); });
@ -349,6 +369,36 @@ describe("WysiwygComposer", () => {
// check that it has inserted a link and falls back to the completion text // check that it has inserted a link and falls back to the completion text
expect(screen.getByRole("link", { name: "#room_without_completion_id" })).toBeInTheDocument(); 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", () => { describe("When settings require Ctrl+Enter to send", () => {

View file

@ -100,8 +100,8 @@ describe("getRoomFromCompletion", () => {
}); });
describe("getMentionDisplayText", () => { describe("getMentionDisplayText", () => {
it("returns an empty string if we are not handling a user or a room type", () => { it("returns an empty string if we are not handling a user, room or at-room type", () => {
const nonHandledCompletionTypes = ["at-room", "community", "command"] as const; const nonHandledCompletionTypes = ["community", "command"] as const;
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type })); const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
nonHandledCompletions.forEach((completion) => { 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 // as this uses the mockClient, the name will be the mock room name returned from there
expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion); 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", () => { describe("getMentionAttributes", () => {
// TODO handle all completion types it("returns an empty object for completion types other than room, user or at-room", () => {
it("returns an empty object for completion types other than room or user", () => { const nonHandledCompletionTypes = ["community", "command"] as const;
const nonHandledCompletionTypes = ["at-room", "community", "command"] as const;
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type })); const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
nonHandledCompletions.forEach((completion) => { 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" });
});
});
}); });