update
This commit is contained in:
110
scripts/check-category-name-chars.mjs
Normal file
110
scripts/check-category-name-chars.mjs
Normal 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);
|
||||
Reference in New Issue
Block a user