Files
Genarrative/src/components/image-editor/ImageCanvasBackgroundMusicPresetModel.test.ts
T
kdletters 071faa482c 统一 Rust 与 TypeScript 格式化门禁
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口

完成项目 TypeScript/Prettier 与 Rust 全量格式化

修复 Pingora expected executable 门禁的空白敏感误报

同步开发运维文档与 AGC skill pack 格式化忽略规则
2026-09-01 16:28:34 +08:00

230 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { describe, expect, it } from 'vitest';
import {
appendBackgroundMusicPromptPreset,
BACKGROUND_MUSIC_PROMPT_PRESETS,
type BackgroundMusicPromptPreset,
} from './ImageCanvasBackgroundMusicPresetModel';
// 权威设计《画板音乐生成入口设计》「预设库与追加规则」的固定文案。
// 这张表就是转录校验本身,改动必须先改权威设计。
const AUTHORITATIVE_PRESETS: ReadonlyArray<
[BackgroundMusicPromptPreset['group'], string, string]
> = [
['purpose', '菜单待机', '低干扰、适合菜单待机的背景音乐'],
['purpose', '休闲消除', '轻快可爱的休闲消除背景音乐'],
['purpose', '解谜思考', '安静专注的解谜思考背景音乐'],
['purpose', '探索冒险', '温和推进的探索冒险背景音乐'],
['purpose', '对白场景', '克制柔和、留出对白空间的背景音乐'],
['purpose', '战斗前', '蓄势待发的战斗前背景音乐'],
['purpose', '胜利结算', '明亮满足的胜利结算背景音乐'],
['purpose', '失败结算', '克制低落的失败结算背景音乐'],
['purpose', '日常经营', '轻松有序的日常经营背景音乐'],
['purpose', '农场经营', '自然温暖的农场经营背景音乐'],
['purpose', '校园日常', '青春轻松的校园日常背景音乐'],
['purpose', '美食厨房', '温暖活泼的美食厨房背景音乐'],
['atmosphere', '温暖治愈', '柔和明亮的治愈背景音乐'],
['atmosphere', '神秘悬疑', '克制神秘的悬疑背景音乐'],
['atmosphere', '紧张推进', '稳定推进、逐渐紧张的背景音乐'],
['scene', '森林自然', '清新自然的森林背景音乐'],
['scene', '雨夜静谧', '雨夜静谧、略带神秘感的背景音乐'],
['scene', '海洋漂流', '开阔舒缓的海洋漂流背景音乐'],
['scene', '山野远行', '自由舒展的山野远行背景音乐'],
['scene', '糖果乐园', '甜美活泼的糖果乐园背景音乐'],
['scene', '温馨小屋', '温暖安静的温馨小屋背景音乐'],
['scene', '城市夜晚', '克制迷人的城市夜晚背景音乐'],
['scene', '太空科幻', '空灵未来感的太空科幻背景音乐'],
['scene', '赛博街区', '冷静律动的赛博街区背景音乐'],
['scene', '古风幻想', '空灵雅致的古风幻想背景音乐'],
['scene', '童话花园', '梦幻轻盈的童话花园背景音乐'],
['scene', '海底遗迹', '深邃神秘的海底遗迹背景音乐'],
['scene', '沙漠遗迹', '苍茫神秘的沙漠遗迹背景音乐'],
['scene', '熔岩洞穴', '炽热压迫的熔岩洞穴背景音乐'],
['scene', '奇妙博物馆', '好奇灵动的奇妙博物馆背景音乐'],
];
function presetByLabel(label: string) {
const preset = BACKGROUND_MUSIC_PROMPT_PRESETS.find(
(candidate) => candidate.label === label,
);
if (!preset) {
throw new Error(`missing background music preset: ${label}`);
}
return preset;
}
const explorationPreset = presetByLabel('探索冒险');
const healingPreset = presetByLabel('温暖治愈');
describe('ImageCanvasBackgroundMusicPresetModel', () => {
it('keeps the authoritative three groups at 12 / 3 / 15 and 30 total', () => {
const groupCounts = BACKGROUND_MUSIC_PROMPT_PRESETS.reduce<
Record<string, number>
>(
(counts, preset) => ({
...counts,
[preset.group]: (counts[preset.group] ?? 0) + 1,
}),
{},
);
expect(BACKGROUND_MUSIC_PROMPT_PRESETS).toHaveLength(30);
expect(groupCounts).toEqual({ purpose: 12, atmosphere: 3, scene: 15 });
});
it('copies every authoritative label and prompt without rewriting them', () => {
expect(
BACKGROUND_MUSIC_PROMPT_PRESETS.map((preset) => [
preset.group,
preset.label,
preset.prompt,
]),
).toEqual(AUTHORITATIVE_PRESETS);
});
it('exposes stable unique ids that never appear in the visible prompt', () => {
const ids = BACKGROUND_MUSIC_PROMPT_PRESETS.map((preset) => preset.id);
expect(new Set(ids).size).toBe(ids.length);
for (const preset of BACKGROUND_MUSIC_PROMPT_PRESETS) {
expect(preset.id).toMatch(/^[a-z][a-z0-9-]*$/u);
expect(preset.prompt).not.toContain(preset.id);
expect(preset.prompt).not.toContain(preset.group);
}
});
it('writes the preset text directly into an empty or all-whitespace prompt', () => {
// 不可见 code point 一律用显式转义:字面量在编辑器和 lint autofix 里容易被吃掉。
for (const currentPrompt of ['', ' ', '…', ' \t\n\r\n  

']) {
expect(
appendBackgroundMusicPromptPreset(currentPrompt, explorationPreset),
).toBe(explorationPreset.prompt);
}
});
it('appends directly after Chinese, ASCII and other Unicode punctuation', () => {
for (const ending of [
'。',
'',
'、',
'',
'',
'',
'',
'”',
'',
'.',
',',
'!',
'?',
';',
':',
'-',
'_',
'(',
')',
'"',
"'",
'…',
'—',
'「',
'」',
'·',
]) {
const currentPrompt = `阳光动物园${ending}`;
expect(
appendBackgroundMusicPromptPreset(currentPrompt, explorationPreset),
).toBe(`${currentPrompt}${explorationPreset.prompt}`);
}
});
it('inserts a Chinese period after non-punctuation, symbols, emoji and combining marks', () => {
// `+` `=` `$` 属于 Unicode 符号而不是标点,必须补句号;emoji 与组合字符
// 用于证明末位是按 code point 而不是 UTF-16 code unit 读取的。
for (const ending of [
'园',
'a',
'7',
'+',
'=',
'$',
'\u{1f3b5}',
'\u{1f469}\u{1f3a4}',
'á',
]) {
const currentPrompt = `阳光动物园${ending}`;
expect(
appendBackgroundMusicPromptPreset(currentPrompt, explorationPreset),
).toBe(`${currentPrompt}${explorationPreset.prompt}`);
}
});
it('removes only boundary whitespace and keeps internal CR / LF / CRLF unchanged', () => {
const currentPrompt = ' 阳光动物园\n第二行\r\n第三行\r第四行  ';
expect(
appendBackgroundMusicPromptPreset(currentPrompt, explorationPreset),
).toBe(`阳光动物园\n第二行\r\n第三行\r第四行。${explorationPreset.prompt}`);
});
it('keeps boundary zero-width code points because they are not Unicode White_Space', () => {
const currentPrompt = '​阳光动物园';
expect(
appendBackgroundMusicPromptPreset(currentPrompt, explorationPreset),
).toBe(`${currentPrompt}${explorationPreset.prompt}`);
});
it('appends again on every repeated click of the same preset', () => {
const first = appendBackgroundMusicPromptPreset('', explorationPreset);
const second = appendBackgroundMusicPromptPreset(first, explorationPreset);
const third = appendBackgroundMusicPromptPreset(second, explorationPreset);
expect(second).toBe(`${first}${explorationPreset.prompt}`);
expect(third).toBe(`${second}${explorationPreset.prompt}`);
});
it('merges different presets in click order without deduplicating', () => {
const withExploration = appendBackgroundMusicPromptPreset(
'阳光动物园',
explorationPreset,
);
expect(
appendBackgroundMusicPromptPreset(withExploration, healingPreset),
).toBe(`阳光动物园。${explorationPreset.prompt}${healingPreset.prompt}`);
});
it('keeps the whole text after crossing the 200 and 2000 code point limits', () => {
for (const currentLength of [200, 2001]) {
const currentPrompt = 'A'.repeat(currentLength);
const appended = appendBackgroundMusicPromptPreset(
currentPrompt,
explorationPreset,
);
expect(appended.startsWith(currentPrompt)).toBe(true);
expect(appended).toBe(`${currentPrompt}${explorationPreset.prompt}`);
expect(Array.from(appended)).toHaveLength(
currentLength + 1 + Array.from(explorationPreset.prompt).length,
);
}
});
it('returns only the visible prompt, never the preset id or group metadata', () => {
const appended = appendBackgroundMusicPromptPreset(
'阳光动物园',
explorationPreset,
);
// chip 上的 label 可能本来就是预设正文的一部分,所以只断言结果逐字等于
// 「canonical + 分隔符 + preset.prompt」,并禁止隐藏元数据泄漏。
expect(typeof appended).toBe('string');
expect(appended).toBe(`阳光动物园。${explorationPreset.prompt}`);
expect(appended).not.toContain(explorationPreset.id);
expect(appended).not.toContain(explorationPreset.group);
});
});