Files
lingxi/packages/shared/src/naming.spec.ts
T
Stev_Wang cc6e84cdbc feat(shared): A4 数据字典与系统配置契约
背景:A4 需要字典三层合并、配置版本与命名规则的共享契约。\n改动:新增 dict/config 契约、system-configs 定义与校验、命名模板解析;新增 dict_types/dict_items/system_config_versions 迁移与 13 类/96 条字典、26 项配置种子。\n影响面:shared 新增导出;prisma 新增表与迁移;客户基础字段 DTO 硬上界调整为 50(实际生效上限仍由配置动态校验)。\n回滚:回滚本提交并保留新增表(不删数据)。\n验证:shared typecheck/test 147;seed 幂等;prisma migrate dev 已应用。
2026-11-04 11:48:38 +08:00

112 lines
3.6 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 { describe, expect, it } from 'vitest';
import {
dedupeName,
formatNamingDate,
formatNamingSequence,
resolveNamingTemplate,
sanitizeFilename,
validateNamingTemplate,
} from './naming.js';
describe('sanitizeFilename', () => {
it('替换路径分隔符与非法字符,保留中文', () => {
expect(sanitizeFilename('素材/标签:01*?.png')).toBe('素材_标签_01__.png');
});
it('去除首尾空格与点,空名回退', () => {
expect(sanitizeFilename(' ..脚本草稿 .. ')).toBe('脚本草稿');
expect(sanitizeFilename(' ')).toBe('未命名');
expect(sanitizeFilename('***', { fallback: 'file' })).toBe('file');
});
it('Windows 保留名追加下划线', () => {
expect(sanitizeFilename('CON')).toBe('CON_');
expect(sanitizeFilename('lpt1')).toBe('lpt1_');
});
it('超长时保留扩展名', () => {
const name = sanitizeFilename(`${'a'.repeat(200)}.png`, { maxLength: 20 });
expect(name).toHaveLength(20);
expect(name.endsWith('.png')).toBe(true);
});
});
describe('dedupeName(R-01 同名自动加序号)', () => {
it('不冲突时原样返回', () => {
expect(dedupeName('脚本-01', new Set())).toBe('脚本-01');
});
it('冲突时追加两位序号', () => {
const existing = new Set(['素材.png', '素材-02.png']);
expect(dedupeName('素材.png', existing)).toBe('素材-03.png');
});
it('带序号的名字基于原始基名递增', () => {
const existing = new Set(['素材-02.png', '素材-03.png']);
expect(dedupeName('素材-03.png', existing)).toBe('素材-04.png');
});
it('序号耗尽时抛错', () => {
const existing = new Set(['a', ...Array.from({ length: 3 }, (_, i) => `a-0${String(i + 2)}`)]);
expect(() => dedupeName('a', existing, 3)).toThrow(RangeError);
});
});
describe('命名模板(A4-BE-03 / R-01)', () => {
const context = {
industry: '电商',
customer: '花间集',
direction: '口播',
date: '20261104',
sequence: 1,
spec: '1080x1920',
};
it('默认脚本模板解析为「行业-客户-方向-日期-序号」', () => {
expect(resolveNamingTemplate('{行业}-{客户}-{脚本方向}-{日期}-{序号}', context)).toBe(
'电商-花间集-口播-20261104-1',
);
});
it('序号可先补零后传入', () => {
expect(
resolveNamingTemplate('{行业}-{客户}-{日期}-{序号}', {
...context,
sequence: formatNamingSequence(context.sequence),
}),
).toBe('电商-花间集-20261104-01');
});
it('兼容英文 key 与规格后缀', () => {
expect(resolveNamingTemplate('{industry}-{customer}-{date}-{seq}-{spec}', context)).toBe(
'电商-花间集-20261104-1-1080x1920',
);
});
it('未知占位符 / 非法字符 / 空模板被拒绝', () => {
expect(validateNamingTemplate('{行业}-{未知}').valid).toBe(false);
expect(validateNamingTemplate('{行业}*{客户}').valid).toBe(false);
expect(validateNamingTemplate(' ').valid).toBe(false);
});
it('缺失占位符值时抛错', () => {
expect(() => resolveNamingTemplate('{行业}-{客户}', { ...context, customer: '' })).toThrow(
'命名占位符缺少值',
);
});
it('超过最大长度抛错', () => {
expect(() =>
resolveNamingTemplate('{客户}', { ...context, customer: '客'.repeat(101) }),
).toThrow('不能超过 100 个字符');
});
it('日期与序号格式化', () => {
expect(formatNamingDate(new Date(2026, 10, 4))).toBe('20261104');
expect(formatNamingSequence(7)).toBe('07');
expect(formatNamingSequence(123)).toBe('123');
expect(() => formatNamingSequence(-1)).toThrow();
});
});