Files
jiang13-bbs/frontend/lib/dyEmoji.test.ts
freefire 34aee91a13 feat: 论坛 emoji 表情支持
- 新增 dyEmoji 表情库与解析测试、EmojiPicker 选择器与 EmojiImg 组件
- Markdown 编辑器/渲染支持表情图片,新增 public/emoji 资源与生成脚本
- 聊天室与首页适配
2026-09-28 01:28:41 +08:00

101 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
});