Rich Text Editor: Add emoji suggestion support (#30873)
* Add support for emoji suggestions To both the rich text/plain text modes of the RTE. * Add emoji completion test to WysiwygComposer * Fix code as per test case, do no-op for community case * bump wysiwyg to the version with suggestions supported. * Add more unit tests for processTextReplacement
This commit is contained in:
@@ -66,6 +66,7 @@ describe("WysiwygAutocomplete", () => {
|
||||
const mockHandleMention = jest.fn();
|
||||
const mockHandleCommand = jest.fn();
|
||||
const mockHandleAtRoomMention = jest.fn();
|
||||
const mockHandleEmoji = jest.fn();
|
||||
|
||||
const renderComponent = (props: Partial<React.ComponentProps<typeof WysiwygAutocomplete>> = {}) => {
|
||||
const mockClient = stubClient();
|
||||
@@ -81,6 +82,7 @@ describe("WysiwygAutocomplete", () => {
|
||||
handleMention={mockHandleMention}
|
||||
handleCommand={mockHandleCommand}
|
||||
handleAtRoomMention={mockHandleAtRoomMention}
|
||||
handleEmoji={mockHandleEmoji}
|
||||
{...props}
|
||||
/>
|
||||
</ScopedRoomContextProvider>
|
||||
@@ -96,6 +98,7 @@ describe("WysiwygAutocomplete", () => {
|
||||
handleMention={mockHandleMention}
|
||||
handleCommand={mockHandleCommand}
|
||||
handleAtRoomMention={mockHandleAtRoomMention}
|
||||
handleEmoji={mockHandleEmoji}
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryByTestId("autocomplete-wrapper")).not.toBeInTheDocument();
|
||||
|
||||
@@ -234,6 +234,11 @@ describe("WysiwygComposer", () => {
|
||||
range: { start: 1, end: 1 },
|
||||
component: <div>community</div>,
|
||||
},
|
||||
{
|
||||
completion: "😄",
|
||||
range: { start: 1, end: 1 },
|
||||
component: <div>😄</div>,
|
||||
},
|
||||
];
|
||||
|
||||
const constructMockProvider = (data: ICompletion[]) =>
|
||||
@@ -435,6 +440,16 @@ describe("WysiwygComposer", () => {
|
||||
// check that it we still have the initial text
|
||||
expect(screen.getByText(initialInput)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("selecting an emoji suggestion inserts the emoji", async () => {
|
||||
await insertMentionInput();
|
||||
|
||||
// select the room suggestion
|
||||
await userEvent.click(screen.getByText("😄"));
|
||||
|
||||
// check that it has inserted the plain text
|
||||
expect(screen.getByText("😄")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("When emoticons should be replaced by emojis", () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
processEmojiReplacement,
|
||||
processMention,
|
||||
processSelectionChange,
|
||||
processTextReplacement,
|
||||
} from "../../../../../../../src/components/views/rooms/wysiwyg_composer/hooks/useSuggestion";
|
||||
|
||||
function createMockPlainTextSuggestionPattern(props: Partial<Suggestion> = {}): Suggestion {
|
||||
@@ -382,6 +383,95 @@ describe("findSuggestionInText", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("processTextReplacement", () => {
|
||||
it("does not change parent hook state if suggestionData is null", () => {
|
||||
const mockSetSuggestionData = jest.fn();
|
||||
const mockSetText = jest.fn();
|
||||
const replacementText = "replacement";
|
||||
|
||||
// call the function with null suggestionData
|
||||
processTextReplacement(replacementText, null, mockSetSuggestionData, mockSetText);
|
||||
|
||||
// check that the parent state setters have not been called
|
||||
expect(mockSetText).not.toHaveBeenCalled();
|
||||
expect(mockSetSuggestionData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not change parent hook state if existingContent is null", () => {
|
||||
const mockSetSuggestionData = jest.fn();
|
||||
const mockSetText = jest.fn();
|
||||
const replacementText = "replacement";
|
||||
|
||||
// create a mock node with null textContent
|
||||
const mockNode = {
|
||||
textContent: null,
|
||||
} as unknown as Text;
|
||||
|
||||
const mockSuggestion: Suggestion = {
|
||||
mappedSuggestion: { keyChar: ":", type: "emoji", text: ":)" },
|
||||
node: mockNode,
|
||||
startOffset: 0,
|
||||
endOffset: 2,
|
||||
};
|
||||
|
||||
// call the function with a node that has null textContent
|
||||
processTextReplacement(replacementText, mockSuggestion, mockSetSuggestionData, mockSetText);
|
||||
|
||||
// check that the parent state setters have not been called
|
||||
expect(mockSetText).not.toHaveBeenCalled();
|
||||
expect(mockSetSuggestionData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("can replace text content when both suggestionData and existingContent are valid", () => {
|
||||
const mockSetSuggestionData = jest.fn();
|
||||
const mockSetText = jest.fn();
|
||||
const replacementText = "🙂";
|
||||
const initialText = "Hello :) world";
|
||||
|
||||
// create a div and append a text node to it
|
||||
const editorDiv = document.createElement("div");
|
||||
const textNode = document.createTextNode(initialText);
|
||||
editorDiv.appendChild(textNode);
|
||||
document.body.appendChild(editorDiv);
|
||||
|
||||
const mockSuggestion: Suggestion = {
|
||||
mappedSuggestion: { keyChar: ":", type: "emoji", text: ":)" },
|
||||
node: textNode,
|
||||
startOffset: 6, // position of ":)"
|
||||
endOffset: 8, // end of ":)"
|
||||
};
|
||||
|
||||
// mock document.getSelection
|
||||
const mockSelection = {
|
||||
setBaseAndExtent: jest.fn(),
|
||||
};
|
||||
jest.spyOn(document, "getSelection").mockReturnValue(mockSelection as any);
|
||||
|
||||
// call the function
|
||||
processTextReplacement(replacementText, mockSuggestion, mockSetSuggestionData, mockSetText);
|
||||
|
||||
// check that the text content was updated correctly
|
||||
expect(textNode.textContent).toBe("Hello 🙂 world");
|
||||
|
||||
// check that setText was called with the new content
|
||||
expect(mockSetText).toHaveBeenCalledWith("Hello 🙂 world");
|
||||
|
||||
// check that suggestionData was cleared
|
||||
expect(mockSetSuggestionData).toHaveBeenCalledWith(null);
|
||||
|
||||
// check that the cursor was positioned at the end
|
||||
expect(mockSelection.setBaseAndExtent).toHaveBeenCalledWith(
|
||||
textNode,
|
||||
"Hello 🙂 world".length,
|
||||
textNode,
|
||||
"Hello 🙂 world".length,
|
||||
);
|
||||
|
||||
// clean up
|
||||
document.body.removeChild(editorDiv);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMappedSuggestion", () => {
|
||||
it("returns null when the first character is not / # @", () => {
|
||||
expect(getMappedSuggestion("Zzz")).toBe(null);
|
||||
|
||||
Reference in New Issue
Block a user