This commit is contained in:
2026-09-12 13:55:57 +08:00
commit 30f1cedd39
95 changed files with 42057 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
// 一次性验证脚本:分类名禁用 `|` 字符。
//
// 背景TaskList 用 `cat + '|' + id` 作为 selectedTaskIds 的 Set key_selKey
// 批量浮栏的 _updateBatchBarState / _handleBatchAction / _showBatchMoveMenu
// 都通过 key.split('|') 反查分类名与 id。
//
// 如果分类名本身含 `|`,比如 "Foo|Bar"
// - add 时的 key = "Foo|Bar|t_xxx_xxxxx"
// - split('|') → ["Foo", "Bar", "t_xxx_xxxxx"]
// - 解构 [cat, id] = key.split('|') → cat = "Foo", id = "Bar"
// - 后续 store.batchMoveTasks / batchToggleCompleted / batchDeleteTasks
// 全都按错误的 (cat, id) 去找任务 → 静默空操作 → 用户以为「批量没生效」。
//
// 防线store 的 addSubCategory / renameCategory 必须拒绝任何含 `|` 的输入。
// 这份脚本守住这条防线 —— 加了 `_taskRefKey` 死代码 + `|` 漏洞的回归测试都靠它。
//
// 用法node scripts/check-category-name-chars.mjs
import { TaskStore } from '../src/task-store.js';
import { check, ok, bad, summary, printSummary } from './_lib/check.mjs';
import { makeStore } from './_lib/store-fixture.mjs';
console.log('[1] addSubCategory 拒绝含 `|` 的名字');
{
const store = makeStore();
const names = [
'Foo|Bar', // 经典带管道符
'|leading', // 前导
'trailing|', // 末尾
'a||b', // 双管道
'工作|子分类', // 含中文字符 + |
];
for (const name of names) {
const before = store.categories.length;
const result = store.addSubCategory(name);
check(
`addSubCategory(${JSON.stringify(name)}) 返回 null`,
result === null,
`实际 ${JSON.stringify(result)}`
);
check(
`categories 长度未变`,
store.categories.length === before,
`${before}${store.categories.length}`
);
}
}
console.log('\n[2] addSubCategory 接受不含 `|` 的合法名字');
{
const store = makeStore();
const ok = store.addSubCategory('正常名字');
check('addSubCategory("正常名字") 返回 cat 对象', ok !== null);
check('分类名确实是"正常名字"', ok && ok.name === '正常名字');
}
console.log('\n[3] renameCategory 拒绝把名字改成含 `|`');
{
const store = makeStore();
store.addSubCategory('副业');
const badNames = ['Foo|Bar', 'a|', '|b'];
for (const newName of badNames) {
const ok = store.renameCategory('副业', newName);
check(
`rename('副业' → ${JSON.stringify(newName)}) 返回 false`,
ok === false,
`实际 ${JSON.stringify(ok)}`
);
check(
`「副业」名字没被改`,
store.getCategory('副业') !== null,
`当前: ${store.categories.map(c => c.name).join('、')}`
);
}
}
console.log('\n[4] 集成回归:合法分类的批量操作 key 拆 / 拼对称');
{
// 这条不属于「拒绝非法名字」的范畴,但是和 `|` 同款的端到端检查:
// 走完整 addSubCategory → addTask → 模拟 _selKey + split('|'),确认
// 「cat + '|' + id」拆出来仍然是 (cat, id) —— 不会被 split 干扰。
// 主要是防回归:将来谁把 _selKey 改成 JSON.stringify 之类方案,会让行为改变。
const store = makeStore();
// loadDefault 默认建了「工作/学习/生活/未分类」,用「副业」避开重名
const cat = store.addSubCategory('副业');
check('addSubCategory("副业") 返回非 null', cat !== null);
if (!cat) {
fail++; // 后续 check 无意义 —— 直接结束
} else {
const t = store.addTask('副业', '示例任务');
check('addTask 返回非 null', t !== null);
if (t) {
const key = `${cat.name}|${t.id}`;
const [catOut, idOut] = key.split('|');
check(`_selKey 拼出的 key 能 split 回 (cat, id)`, catOut === cat.name && idOut === t.id);
// 验证 split 不会产生多余片段(极端回归:`_selKey` 不会意外产生多余 `|`
check('split 后只有 2 段', key.split('|').length === 2);
}
}
}
console.log('\n' + '='.repeat(64));
console.log(`结果:${summary.pass} 通过, ${summary.fail} 失败`);
console.log('='.repeat(64));
process.exit(summary.fail === 0 ? 0 : 1);