Files
DevFlow/scripts/check-i18n.test.cjs
T
lxy b37279f3a2 新增: 待发箱/唤醒原语/预算随窗口 + 治理门禁与场景护栏
- 待发箱地基: V51 ai_outbox 表 + 手写 OutboxRepo(claim CAS/崩溃残留复位/瞬时失败保留) + outbox.rs 调度器(KV 默认关/Notify 唤醒/会话忙跳过不丢)
- Wake 异步唤醒原语: 三条件(delay/file-appear/process-exit)+纯判定+注入走正典 spawn_agentic_session,默认关
- 上下文预算随模型窗口: model_context.rs 三级优先(池显式>预设表>回退 128K);上界取 1M 而非 128K——max_tokens 仅裁剪预算,卡 128K 等于不治根因
- 文件租约补纯函数与单测(未另造双轨:LEASE 早随 cb260ab 落地) / 跨会话召回补纯函数单测(主体早已落地)
- 子代理: 完整卡已消化 + 子会话压缩层 + P3 批量
- 隧道/中继: 指令离线不丢 + Hello proto 版本 + 心跳与 runbook
- df-mcp: 写工具后落 KV 变更标记(供 GUI 轮询兜底,补独立 stdio 进程无感知)
- 迁移链顶点改单一真值常量 LATEST_SCHEMA_VERSION(修 2 处因 V51 失效的断言)
- 治理: i18n 特殊字符提交门禁(含反引号覆盖 + 10 自测) / 根目录卫生门禁 / 看板归档器 / 单一 lockfile(bun) / swap-install 落 temp/ 且兼容无 Get-FileHash
- 场景护栏: 流式正文可见性 e2e(playwright 免真机事件回放) + 列表续写/外置句柄 node 测试
2026-10-10 08:09:17 +08:00

102 lines
3.8 KiB
JavaScript

// check-i18n.cjs 的门禁自测:用 node 内置测试跑,不引第三方依赖。
// node --test scripts/check-i18n.test.cjs
//
// 覆盖(对应 2026-10-10 对抗审查发现的假阴性 + 常规门禁行为):
// ① 单引号值含裸 @ → 报错
// ② 双引号值含裸 @ → 报错
// ③ 反引号模板字面量含裸 @ → 报错(原实现只匹配引号,漏报)
// ④ 含 ${} 插值不误报(模板字面量里的插值需先占位再编译)
// ⑤ 转义写法 {'@'} → 通过
// ⑥ 含 | 与裸 {} 的用例
// 另附 scanDirectory 对临时目录样本的端到端断言。
const test = require('node:test');
const assert = require('node:assert');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { scanText, scanDirectory } = require('./check-i18n.cjs');
let compilerAvailable = true;
try {
require('@intlify/message-compiler');
} catch {
compilerAvailable = false;
}
const opts = compilerAvailable ? {} : { skip: '@intlify/message-compiler 不可用,跳过编译相关用例' };
// 构造一份 TS 源样本:每行一个 `key: '<literal>',`
const src = (...lines) => `export default {\n${lines.map((l) => ` ${l},`).join('\n')}\n}\n`;
test('①单引号值含裸 @ → 报错', opts, () => {
const r = scanText(src("a: 'hello @ world'"));
assert.equal(r.checked, 1);
assert.equal(r.violations.length, 1);
assert.equal(r.violations[0].key, 'a');
});
test('②双引号值含裸 @ → 报错', opts, () => {
const r = scanText(src('b: "hello @ world"'));
assert.equal(r.checked, 1);
assert.equal(r.violations.length, 1);
assert.equal(r.violations[0].key, 'b');
});
test('③反引号模板字面量含裸 @ → 报错(假阴性回归)', opts, () => {
const r = scanText(src('c: `hello @ world`'));
assert.equal(r.checked, 1);
assert.equal(r.violations.length, 1);
assert.equal(r.violations[0].key, 'c');
});
test('④-1 模板字面量含 ${} 插值(无特殊字符)不误报', opts, () => {
const r = scanText(src('d: `hello ${name}`'));
// 插值被占位后不含 @ { |,不计入 checked,也无违规
assert.equal(r.violations.length, 0);
});
test('④-2 插值位于花括号内不误报(占位替代的关键场景)', opts, () => {
// 原值 `total {${count}}` 直接编译会报 Invalid token in placeholder: '$',
// 必须先替换 ${...} 为占位符成为 `total {X}`(合法具名插值)才不误报。
const r = scanText(src('e: `total {${count}}`'));
assert.equal(r.violations.length, 0);
assert.equal(r.checked, 1);
});
test('⑤转义写法 {\'@\'} → 通过', opts, () => {
const r = scanText(src("f: \"{'@'}\""));
assert.equal(r.checked, 1);
assert.equal(r.violations.length, 0);
});
test('⑥-1 值含 | 会进入编译且正常通过', opts, () => {
const r = scanText(src("g: 'one | two'"));
assert.equal(r.checked, 1);
assert.equal(r.violations.length, 0);
});
test('⑥-2 值以 | 结尾(plural 缺省) → 报错', opts, () => {
const r = scanText(src("h: 'one |'"));
assert.equal(r.checked, 1);
assert.equal(r.violations.length, 1);
});
test('⑥-3 裸 { 未闭合 → 报错', opts, () => {
const r = scanText(src("i: 'x { y'"));
assert.equal(r.checked, 1);
assert.equal(r.violations.length, 1);
});
test('scanDirectory 扫临时目录样本:定位到违规文件', opts, () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'i18n-check-'));
try {
fs.writeFileSync(path.join(dir, 'ok.ts'), src("ok: 'plain text'"));
fs.writeFileSync(path.join(dir, 'bad.ts'), src('bad: `oops @ here`'));
const r = scanDirectory(dir);
assert.equal(r.violations.length, 1);
assert.equal(r.violations[0].file, 'bad.ts');
assert.equal(r.violations[0].key, 'bad');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});