Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Handle more completion types in rte autocomplete #10560

Merged
merged 10 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe("WysiwygComposer", () => {
});
});

describe("Mentions", () => {
describe("Mentions and commands", () => {
const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");

const mockCompletions: ICompletion[] = [
Expand All @@ -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 },
Expand All @@ -201,6 +202,24 @@ describe("WysiwygComposer", () => {
range: { start: 1, end: 1 },
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[]) =>
Expand All @@ -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",
});

Expand Down Expand Up @@ -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", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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" });
});
});
});