import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { DY_EMOJI_ALIASES, DY_EMOJI_GROUPS, DY_EMOJI_RE, dyEmojiFileKey, dyEmojiUrl, expandDyEmojiShortcodes, } from "./dyEmoji.ts"; const CAT_HASH = DY_EMOJI_ALIASES["猫猫"]; describe("DY_EMOJI_ALIASES", () => { it("中文语义名 → hash 文件名,文件名全局唯一", () => { assert.match(CAT_HASH, /^[A-Za-z0-9_-]+$/); const files = Object.values(DY_EMOJI_ALIASES); assert.equal(new Set(files).size, files.length); }); it("完整覆盖 manifest.tos(集合一致;面板分组顺序独立于 manifest)", () => { const manifest = JSON.parse( readFileSync(fileURLToPath(new URL("../public/emoji/dy/manifest.json", import.meta.url)), "utf8") ); assert.deepEqual(Object.values(DY_EMOJI_ALIASES).sort(), [...manifest.tos].sort()); }); }); describe("DY_EMOJI_GROUPS", () => { it("分组 keys 无重复", () => { const all = DY_EMOJI_GROUPS.flatMap((g) => g.keys); assert.equal(new Set(all).size, all.length); }); it("分组 keys 恰好覆盖全部中文语义名(不多不少)", () => { const grouped = DY_EMOJI_GROUPS.flatMap((g) => g.keys).sort(); const allKeys = Object.keys(DY_EMOJI_ALIASES).sort(); assert.deepEqual(grouped, allKeys); }); }); describe("dyEmojiFileKey / dyEmojiUrl", () => { it("中文 key 解析为实际文件名,合成表情组合名透传", () => { assert.equal(dyEmojiFileKey("猫猫"), CAT_HASH); assert.equal(dyEmojiFileKey("001--002--heart"), "001--002--heart"); }); it("拼接扁平目录 URL", () => { assert.equal(dyEmojiUrl("猫猫"), `/emoji/dy/${CAT_HASH}.png`); assert.equal(dyEmojiUrl("001--002--heart"), "/emoji/dy/001--002--heart.png"); }); it("未知 key 原样 URL 编码透传", () => { assert.equal(dyEmojiUrl("不存在"), `/emoji/dy/${encodeURIComponent("不存在")}.png`); }); }); describe("expandDyEmojiShortcodes", () => { it("中文短代码替换为 markdown 图片", () => { assert.equal( expandDyEmojiShortcodes("你好[:猫猫:]!"), `你好![dy:猫猫](/emoji/dy/${CAT_HASH}.png)!` ); }); it("合成表情组合名短代码", () => { assert.equal( expandDyEmojiShortcodes("[:001--002--heart:]"), "![dy:001--002--heart](/emoji/dy/001--002--heart.png)" ); }); it("多次替换且共享前缀", () => { assert.equal( expandDyEmojiShortcodes("[:猫猫:][:aa:][:猫猫:]"), `![dy:猫猫](/emoji/dy/${CAT_HASH}.png)![dy:aa](/emoji/dy/aa.png)![dy:猫猫](/emoji/dy/${CAT_HASH}.png)` ); }); it("无短代码时原样返回", () => { const s = "普通文本 [a:] :x: [[y]]"; assert.equal(expandDyEmojiShortcodes(s), s); assert.equal(expandDyEmojiShortcodes(""), ""); }); it("非法 key 不匹配(含 . / 空 / @ / 空白)", () => { assert.equal(expandDyEmojiShortcodes("[:a.b:][:]"), "[:a.b:][:]"); assert.equal(expandDyEmojiShortcodes("[:空 格:]"), "[:空 格:]"); assert.equal(expandDyEmojiShortcodes("[:@x:]"), "[:@x:]"); }); it("跨行文本", () => { assert.equal( expandDyEmojiShortcodes("第一行[:猫猫:]\n第二行"), `第一行![dy:猫猫](/emoji/dy/${CAT_HASH}.png)\n第二行` ); }); it("连续使用共享同一正则(lastIndex 复用安全)", () => { assert.equal(expandDyEmojiShortcodes("[:猫猫:]"), `![dy:猫猫](/emoji/dy/${CAT_HASH}.png)`); assert.equal(expandDyEmojiShortcodes("[:b:]"), "![dy:b](/emoji/dy/b.png)"); assert.equal(expandDyEmojiShortcodes("纯文本"), "纯文本"); assert.equal(expandDyEmojiShortcodes("[:c:]"), "![dy:c](/emoji/dy/c.png)"); }); it("正则含 g 标志(供分段渲染使用)", () => { assert.ok(DY_EMOJI_RE.global); }); });