- 新增 dyEmoji 表情库与解析测试、EmojiPicker 选择器与 EmojiImg 组件 - Markdown 编辑器/渲染支持表情图片,新增 public/emoji 资源与生成脚本 - 聊天室与首页适配
101 lines
3.8 KiB
TypeScript
101 lines
3.8 KiB
TypeScript
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("你好[:猫猫:]!"),
|
||
`你好!`
|
||
);
|
||
});
|
||
it("合成表情组合名短代码", () => {
|
||
assert.equal(
|
||
expandDyEmojiShortcodes("[:001--002--heart:]"),
|
||
""
|
||
);
|
||
});
|
||
it("多次替换且共享前缀", () => {
|
||
assert.equal(
|
||
expandDyEmojiShortcodes("[:猫猫:][:aa:][:猫猫:]"),
|
||
``
|
||
);
|
||
});
|
||
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第二行"),
|
||
`第一行\n第二行`
|
||
);
|
||
});
|
||
it("连续使用共享同一正则(lastIndex 复用安全)", () => {
|
||
assert.equal(expandDyEmojiShortcodes("[:猫猫:]"), ``);
|
||
assert.equal(expandDyEmojiShortcodes("[:b:]"), "");
|
||
assert.equal(expandDyEmojiShortcodes("纯文本"), "纯文本");
|
||
assert.equal(expandDyEmojiShortcodes("[:c:]"), "");
|
||
});
|
||
it("正则含 g 标志(供分段渲染使用)", () => {
|
||
assert.ok(DY_EMOJI_RE.global);
|
||
});
|
||
});
|