Files
outline/shared/editor/lib/emoji.test.ts
T
Tom Moor 9811ab6aea feat: Emoji reaction shorthand (#12650)
* Add "+:emoji:" reaction shorthand to comment form

Typing a comment that consists solely of a leading "+" followed by a
single emoji now adds that emoji as a reaction to the comment above,
instead of posting a new reply — mirroring the Slack shorthand.

https://claude.ai/code/session_01RSiUiEFLBaRF6YBfPNPiX6

* Move parseReactionShorthand into editor/lib/emoji

https://claude.ai/code/session_01RSiUiEFLBaRF6YBfPNPiX6

* Open emoji menu when colon is preceded by a plus

The suggestion menu's trigger boundary excluded "+", so typing "+:" never
opened the emoji menu — preventing the "+:emoji:" reaction shorthand from
being typed. Add a configurable `precededBy` option to the Suggestion
extension and set it to "+" for the emoji menu.

https://claude.ai/code/session_01RSiUiEFLBaRF6YBfPNPiX6

* Always allow "+" before suggestion trigger

Simplify by adding "+" to the trigger boundary for all suggestion menus
rather than making it a per-menu option. This lets the "+:emoji:" reaction
shorthand open the emoji menu.

https://claude.ai/code/session_01RSiUiEFLBaRF6YBfPNPiX6

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-11 21:51:11 -04:00

112 lines
2.7 KiB
TypeScript

import type { ProsemirrorData } from "../../types";
import {
getNameFromEmoji,
getEmojiFromName,
loadEmojiData,
parseReactionShorthand,
} from "./emoji";
beforeAll(async () => {
await loadEmojiData();
});
describe("getNameFromEmoji", () => {
it("returns the correct shortcode", () => {
expect(getNameFromEmoji("🤔")).toBe("thinking_face");
});
});
describe("getEmojiFromName", () => {
it("returns the correct native character", () => {
expect(getEmojiFromName("thinking_face")).toBe("🤔");
});
});
describe("parseReactionShorthand", () => {
const doc = (content: ProsemirrorData[]): ProsemirrorData => ({
type: "doc",
content,
});
const paragraph = (content: ProsemirrorData[]): ProsemirrorData => ({
type: "paragraph",
content,
});
const text = (value: string): ProsemirrorData => ({
type: "text",
text: value,
});
const emoji = (name: string): ProsemirrorData => ({
type: "emoji",
attrs: { "data-name": name },
});
it("resolves a '+' followed by an emoji node", () => {
expect(
parseReactionShorthand(doc([paragraph([text("+"), emoji("thumbs_up")])]))
).toBe("👍");
});
it("ignores whitespace between the '+' and the emoji node", () => {
expect(
parseReactionShorthand(
doc([paragraph([text("+"), text(" "), emoji("thinking_face")])])
)
).toBe("🤔");
});
it("resolves a custom emoji UUID to its UUID", () => {
const uuid = "550e8400-e29b-41d4-a716-446655440000";
expect(
parseReactionShorthand(doc([paragraph([text("+"), emoji(uuid)])]))
).toBe(uuid);
});
it("resolves literal '+:shortcode:' text", () => {
expect(
parseReactionShorthand(doc([paragraph([text("+:thinking_face:")])]))
).toBe("🤔");
});
it("returns undefined for an unknown shortcode", () => {
expect(
parseReactionShorthand(
doc([paragraph([text("+"), emoji("not_an_emoji")])])
)
).toBeUndefined();
});
it("returns undefined when there is text alongside the emoji", () => {
expect(
parseReactionShorthand(
doc([paragraph([text("+ nice "), emoji("thumbs_up")])])
)
).toBeUndefined();
});
it("returns undefined for a regular comment", () => {
expect(
parseReactionShorthand(doc([paragraph([text("Looks good to me")])]))
).toBeUndefined();
});
it("returns undefined when the '+' prefix is missing", () => {
expect(
parseReactionShorthand(doc([paragraph([emoji("thumbs_up")])]))
).toBeUndefined();
});
it("returns undefined for multiple paragraphs", () => {
expect(
parseReactionShorthand(
doc([
paragraph([text("+"), emoji("thumbs_up")]),
paragraph([text("more")]),
])
)
).toBeUndefined();
});
});