Skip to content
File

Blob: tests/client/lib/emoji.dom.test.ts

typescript100 lines
1import { beforeEach, describe, expect, it } from "vitest";
2import { emojis as tiptapEmojis } from "@tiptap/extension-emoji";
3import { normalizeEmoji } from "@/client/lib/emoji";
4import { getEmojiAssetUrl } from "@/client/lib/emoji/asset-lookup";
5import { PICKER_EMOJI_ITEMS } from "@/client/lib/emoji/picker-data";
6import { getEmojiSuggestionItems } from "@/client/components/editor/extensions/emoji";
7import { readRecentEmojis, writeRecentEmoji } from "@/client/lib/emoji/recents";
8import { STORAGE_KEYS } from "@/client/lib/constants";
9 
10describe("emoji helpers", () => {
11 it("normalizes variation selectors while keeping the correct Apple asset mapping", () => {
12 expect(normalizeEmoji("☺️")).toBe("☺");
13 expect(getEmojiAssetUrl("☺️")).toBe(
14 "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/263a-fe0f.png",
15 );
16 });
17 
18 it("resolves Apple assets for skin-tone variants", () => {
19 expect(getEmojiAssetUrl("👍🏽")).toBe(
20 "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f44d-1f3fd.png",
21 );
22 expect(getEmojiAssetUrl("🧔🏻‍♂️")).toBe(
23 "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f9d4-1f3fb-200d-2642-fe0f.png",
24 );
25 });
26 
27 it("keeps grouped native-fallback emojis eligible for the picker", () => {
28 expect(PICKER_EMOJI_ITEMS.some((item) => item.emoji === "🧑‍🩰")).toBe(true);
29 expect(PICKER_EMOJI_ITEMS.some((item) => item.emoji === "♀")).toBe(true);
30 });
31 
32 it("keeps component glyphs out of the picker dataset", () => {
33 expect(PICKER_EMOJI_ITEMS.some((item) => item.group === "components")).toBe(false);
34 });
35});
36 
37describe("emoji recents", () => {
38 beforeEach(() => {
39 localStorage.clear();
40 });
41 
42 it("returns empty when storage is unset", () => {
43 expect(readRecentEmojis()).toEqual([]);
44 });
45 
46 it("returns empty when storage holds malformed JSON", () => {
47 localStorage.setItem(STORAGE_KEYS.EMOJI_RECENTS, "{not-json");
48 expect(readRecentEmojis()).toEqual([]);
49 });
50 
51 it("returns empty when storage holds a non-array", () => {
52 localStorage.setItem(STORAGE_KEYS.EMOJI_RECENTS, JSON.stringify({ emoji: "👍" }));
53 expect(readRecentEmojis()).toEqual([]);
54 });
55 
56 it("filters out non-string entries", () => {
57 localStorage.setItem(STORAGE_KEYS.EMOJI_RECENTS, JSON.stringify(["👍", 42, null, "🎉", ""]));
58 expect(readRecentEmojis()).toEqual(["👍", "🎉"]);
59 });
60 
61 it("prepends new picks and deduplicates earlier entries", () => {
62 writeRecentEmoji("👍");
63 writeRecentEmoji("🎉");
64 writeRecentEmoji("👍");
65 expect(readRecentEmojis()).toEqual(["👍", "🎉"]);
66 });
67 
68 it("caps the stored list at 16 entries", () => {
69 for (let i = 0; i < 20; i++) {
70 writeRecentEmoji(`e${i}`);
71 }
72 const stored = readRecentEmojis();
73 expect(stored).toHaveLength(16);
74 expect(stored[0]).toBe("e19");
75 expect(stored[15]).toBe("e4");
76 });
77 
78 it("ignores empty-string writes", () => {
79 writeRecentEmoji("👍");
80 writeRecentEmoji("");
81 expect(readRecentEmojis()).toEqual(["👍"]);
82 });
83});
84 
85describe("emoji suggestions", () => {
86 it("returns the first eight base suggestions for an empty query", () => {
87 const results = getEmojiSuggestionItems(tiptapEmojis, "");
88 
89 expect(results).toHaveLength(8);
90 expect(results.every((item) => !!item.emoji || !!item.fallbackImage)).toBe(true);
91 });
92 
93 it("finds base emoji matches by shortcode search", () => {
94 const results = getEmojiSuggestionItems(tiptapEmojis, "thumb");
95 
96 expect(results.length).toBeGreaterThan(0);
97 expect(results.some((item) => item.shortcodes.includes("thumbsup"))).toBe(true);
98 });
99});