client/packages/markdown/src/emoji.test.ts
Alois 25c7ee7c26
Some checks failed
/ build-web (push) Failing after 4m33s
/ build-desktop (linux) (push) Failing after 4m36s
/ build-mobile (push) Failing after 7m7s
/ release (push) Has been skipped
(fix): actually migrate all the imports from tensamin/ui to methanium/ui
(feat): update legal loading screen
2026-07-25 18:12:54 +02:00

138 lines
4.6 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { ReactNode } from "react";
vi.mock("@methanium/ui", () => ({
Tooltip: ({ children }: { children: ReactNode }) => children,
TooltipContent: ({ children }: { children: ReactNode }) => children,
TooltipTrigger: ({ render }: { render: ReactNode }) => render,
}));
import { EditorState } from "@codemirror/state";
import { CompletionContext } from "@codemirror/autocomplete";
import { markdown } from "@codemirror/lang-markdown";
import { normalizeShortcode, resolveEmoji, searchEmojis } from "./emojiData";
import { parseEmojiText, parseInlineNodes } from "./markdown";
import {
createEmojiCompletionSource,
findEmojiRanges,
MAX_RENDERED_EMOJI_OPTIONS,
} from "./input";
describe("emoji shortcodes", () => {
it("normalizes aliases to their canonical shortcode", () => {
expect(normalizeShortcode(":flame:")).toBe(":fire:");
expect(normalizeShortcode("+1")).toBe(":thumbsup:");
});
it("resolves every search result to a Twemoji hexcode", () => {
const results = searchEmojis("fire");
expect(results[0]?.shortcode).toBe(":fire:");
expect(results.every((emoji) => emoji.hexcode.length > 0)).toBe(true);
});
it("shows all emojis for an empty query", () => {
expect(searchEmojis("").length).toBeGreaterThan(1000);
});
it("bounds the number of mounted autocomplete rows", () => {
expect(MAX_RENDERED_EMOJI_OPTIONS).toBeLessThanOrEqual(100);
});
it("parses known shortcodes and preserves unknown ones", () => {
expect(parseEmojiText("a :fire: b :not_an_emoji:")).toEqual([
{ type: "text", value: "a " },
{ type: "emoji", shortcode: ":fire:" },
{ type: "text", value: " b :not_an_emoji:" },
]);
});
it("recognizes a valid shortcode sharing an unknown closing colon", () => {
expect(parseEmojiText(":bla:thumbsup:")).toEqual([
{ type: "text", value: ":bla" },
{ type: "emoji", shortcode: ":thumbsup:" },
]);
const state = EditorState.create({
doc: ":bla:thumbsup:",
extensions: [markdown()],
});
expect(findEmojiRanges(state)[0]?.from).toBe(4);
});
it("does not parse underscores inside emoji shortcodes as emphasis", () => {
expect(parseInlineNodes("before :white_check_mark: after")).toEqual([
{ type: "text", value: "before " },
{ type: "emoji", shortcode: ":white_check_mark:" },
{ type: "text", value: " after" },
]);
});
it("contains the picker defaults", () => {
expect(resolveEmoji(":thumbsup:")).toBeDefined();
expect(resolveEmoji(":white_check_mark:")).toBeDefined();
});
it("finds completed emoji shortcodes in editor state", () => {
const state = EditorState.create({
doc: "before :fire: after :not_an_emoji:",
extensions: [markdown()],
});
expect(
findEmojiRanges(state).map(({ from, shortcode, to }) => ({
from,
shortcode,
to,
})),
).toEqual([{ from: 7, shortcode: ":fire:", to: 13 }]);
});
it("does not replace emoji shortcodes inside code", () => {
const state = EditorState.create({
doc: "`:fire:`\n\n```\n:fire:\n```\n\n:fire:",
extensions: [markdown()],
});
expect(findEmojiRanges(state)).toHaveLength(1);
expect(findEmojiRanges(state)[0]?.from).toBe(26);
});
it("ranks frequently used emojis first for a bare colon", async () => {
const state = EditorState.create({ doc: ":", extensions: [markdown()] });
const result = await createEmojiCompletionSource({
":fire:": 50,
":thumbsup:": 2,
})(new CompletionContext(state, 1, false));
expect(result?.options[0]?.displayLabel).toBe(":fire:");
expect(result?.options[1]?.displayLabel).toBe(":thumbsup:");
});
it("keeps typed relevance above usage frequency", async () => {
const state = EditorState.create({
doc: ":fire",
extensions: [markdown()],
});
const result = await createEmojiCompletionSource({
":fire_engine:": 10000,
":fire:": 1,
})(new CompletionContext(state, 5, false));
expect(result?.options[0]?.displayLabel).toBe(":fire:");
expect(result?.options[0]?.boost).toBeGreaterThan(
result?.options[1]?.boost ?? 0,
);
});
it("merges alias frequencies into canonical completions", async () => {
const state = EditorState.create({ doc: ":", extensions: [markdown()] });
const result = await createEmojiCompletionSource({
":fire:": 2,
":flame:": 3,
})(new CompletionContext(state, 1, false));
const fire = result?.options.find(
(option) => option.displayLabel === ":fire:",
);
expect(fire?.boost).toBe(15);
});
});