Rich text Editor: Auto-replace plain text emoticons with emoji (#12828)
* Detect autoReplaceEmoji setting * Add plain text emoticon to emoji replacement for plain and rich text modes of the RTE. * Use latest wysiwyg * lint * fix existing jest tests and docs * Add unit tests * Update wysiwyg to fix flakes. * fix wording of tests and comments * use useSettingValue
This commit is contained in:
parent
e6835fe9d2
commit
5d16a38b17
8 changed files with 184 additions and 15 deletions
|
@ -423,6 +423,30 @@ describe("WysiwygComposer", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("When emoticons should be replaced by emojis", () => {
|
||||
const onChange = jest.fn();
|
||||
const onSend = jest.fn();
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
if (name === "MessageComposerInput.autoReplaceEmoji") return true;
|
||||
});
|
||||
customRender(onChange, onSend);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
|
||||
});
|
||||
it("typing a space to trigger an emoji replacement", async () => {
|
||||
fireEvent.input(screen.getByRole("textbox"), {
|
||||
data: ":P",
|
||||
inputType: "insertText",
|
||||
});
|
||||
fireEvent.input(screen.getByRole("textbox"), {
|
||||
data: " ",
|
||||
inputType: "insertText",
|
||||
});
|
||||
|
||||
await waitFor(() => expect(onChange).toHaveBeenNthCalledWith(3, expect.stringContaining("😛")));
|
||||
});
|
||||
});
|
||||
|
||||
describe("When settings require Ctrl+Enter to send", () => {
|
||||
const onChange = jest.fn();
|
||||
const onSend = jest.fn();
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
findSuggestionInText,
|
||||
getMappedSuggestion,
|
||||
processCommand,
|
||||
processEmojiReplacement,
|
||||
processMention,
|
||||
processSelectionChange,
|
||||
} from "../../../../../../src/components/views/rooms/wysiwyg_composer/hooks/useSuggestion";
|
||||
|
@ -34,6 +35,16 @@ function createMockPlainTextSuggestionPattern(props: Partial<Suggestion> = {}):
|
|||
};
|
||||
}
|
||||
|
||||
function createMockCustomSuggestionPattern(props: Partial<Suggestion> = {}): Suggestion {
|
||||
return {
|
||||
mappedSuggestion: { keyChar: "", type: "custom", text: "🙂", ...props.mappedSuggestion },
|
||||
node: document.createTextNode(":)"),
|
||||
startOffset: 0,
|
||||
endOffset: 2,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
|
||||
describe("processCommand", () => {
|
||||
it("does not change parent hook state if suggestion is null", () => {
|
||||
// create a mockSuggestion using the text node above
|
||||
|
@ -67,6 +78,39 @@ describe("processCommand", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("processEmojiReplacement", () => {
|
||||
it("does not change parent hook state if suggestion is null", () => {
|
||||
// create a mockSuggestion using the text node above
|
||||
const mockSetSuggestion = jest.fn();
|
||||
const mockSetText = jest.fn();
|
||||
|
||||
// call the function with a null suggestion
|
||||
processEmojiReplacement(null, mockSetSuggestion, mockSetText);
|
||||
|
||||
// check that the parent state setter has not been called
|
||||
expect(mockSetText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("can change the parent hook state when required", () => {
|
||||
// create a div and append a text node to it with some initial text
|
||||
const editorDiv = document.createElement("div");
|
||||
const initialText = ":)";
|
||||
const textNode = document.createTextNode(initialText);
|
||||
editorDiv.appendChild(textNode);
|
||||
|
||||
// create a mockSuggestion using the text node above
|
||||
const mockSuggestion = createMockCustomSuggestionPattern({ node: textNode });
|
||||
const mockSetSuggestion = jest.fn();
|
||||
const mockSetText = jest.fn();
|
||||
const replacementText = "🙂";
|
||||
|
||||
processEmojiReplacement(mockSuggestion, mockSetSuggestion, mockSetText);
|
||||
|
||||
// check that the text has changed and includes a trailing space
|
||||
expect(mockSetText).toHaveBeenCalledWith(replacementText);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processMention", () => {
|
||||
// TODO refactor and expand tests when mentions become <a> tags
|
||||
it("returns early when suggestion is null", () => {
|
||||
|
@ -334,6 +378,17 @@ describe("findSuggestionInText", () => {
|
|||
const mentionWithSpaceAfter = "@ somebody";
|
||||
expect(findSuggestionInText(mentionWithSpaceAfter, 2, true)).toBeNull();
|
||||
});
|
||||
|
||||
it("returns an object for an emoji suggestion", () => {
|
||||
const emoiticon = ":)";
|
||||
const precedingText = "hello ";
|
||||
const mentionInput = precedingText + emoiticon;
|
||||
expect(findSuggestionInText(mentionInput, precedingText.length, true, true)).toEqual({
|
||||
mappedSuggestion: getMappedSuggestion(emoiticon, true),
|
||||
startOffset: precedingText.length,
|
||||
endOffset: precedingText.length + emoiticon.length,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMappedSuggestion", () => {
|
||||
|
@ -361,4 +416,12 @@ describe("getMappedSuggestion", () => {
|
|||
text: "command",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the expected mapped suggestion when the text is a plain text emoiticon", () => {
|
||||
expect(getMappedSuggestion(":)", true)).toEqual({
|
||||
type: "custom",
|
||||
keyChar: "",
|
||||
text: "🙂",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue