Files
Todo-List/scripts/check-uncategorized-position.mjs
2026-09-12 13:55:57 +08:00

155 lines
6.8 KiB
JavaScript
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.
// 一次性验证脚本:「未分类」必须始终排在子分类数组的末尾,
// 新建的子分类不能挤到「未分类」下面。
//
// 背景:之前 addSubCategory 用「容器之后最后一个 parentOtherTasks 之后」定位插入点,
// 当「未分类」已经是最后一个 parentOtherTasks 时,新分类会被插入到「未分类」之后,
// 用户体验就是「我新建的分类怎么跑到兜底下面了」。
//
// 修复后 addSubCategory 优先把新分类插到「未分类」之前;只有「未分类」不存在时
// 才退回到「容器之后最后一个 parentOtherTasks 之后」的旧位置。
//
// 用法node scripts/check-uncategorized-position.mjs
import { TaskStore, UNCATEGORIZED_NAME } 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] loadDefault 默认结构:「未分类」在子分类末尾');
{
const store = makeStore();
const subs = store.getSubCategories().map(c => c.name);
check(`子分类顺序 = 工作 / 学习 / 生活 / 未分类`, JSON.stringify(subs) === '["工作","学习","生活","未分类"]',
`实际:${JSON.stringify(subs)}`);
check(`最后一位是「${UNCATEGORIZED_NAME}`, subs[subs.length - 1] === UNCATEGORIZED_NAME);
}
console.log('\n[2] 新建子分类应插在「未分类」之前,不应把它挤到中间');
{
const store = makeStore();
// 第一次:默认已有 工作/学习/生活/未分类
const before = store.getSubCategories().map(c => c.name);
check(`新建前:「${UNCATEGORIZED_NAME}」在末尾`, before[before.length - 1] === UNCATEGORIZED_NAME);
store.addSubCategory('副业');
const after = store.getSubCategories().map(c => c.name);
check(`新建后:「${UNCATEGORIZED_NAME}」仍在末尾`, after[after.length - 1] === UNCATEGORIZED_NAME,
`实际:${JSON.stringify(after)}`);
check(`新建后:「副业」在「${UNCATEGORIZED_NAME}」之前`,
after.indexOf('副业') < after.indexOf(UNCATEGORIZED_NAME),
`实际:${JSON.stringify(after)}`);
check(`新建后:原有顺序 工作 / 学习 / 生活 / 副业 / ${UNCATEGORIZED_NAME} 全部保持`,
JSON.stringify(after) === '["工作","学习","生活","副业","未分类"]',
`实际:${JSON.stringify(after)}`);
}
console.log('\n[3] 多次新建:新分类依次插在「未分类」之前');
{
const store = makeStore();
store.addSubCategory('副业');
store.addSubCategory('阅读');
store.addSubCategory('运动');
const subs = store.getSubCategories().map(c => c.name);
check(`连续新建后,「${UNCATEGORIZED_NAME}」仍在末尾`,
subs[subs.length - 1] === UNCATEGORIZED_NAME,
`实际:${JSON.stringify(subs)}`);
check(`新建分类按创建顺序排在「${UNCATEGORIZED_NAME}」之前`,
JSON.stringify(subs) === '["工作","学习","生活","副业","阅读","运动","未分类"]',
`实际:${JSON.stringify(subs)}`);
}
console.log('\n[4] 「未分类」不存在时退回旧逻辑(容器之后最后一个 parentOtherTasks 之后)');
{
const store = new TaskStore();
// 手动构造一个没有「未分类」的场景:只有容器 + 工作 + 学习(人为删掉「未分类」)
// 这里直接调 _ensureBaseStructure 之外的路径会违反不变量,所以走更稳的办法:
// 把所有 parentOtherTasks 重命名(包括「未分类」),让它的 c.name !== UNCATEGORIZED_NAME
// addSubCategory 的 findIndex 就会找不到,从而走旧逻辑。
store.loadDefault(null);
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
// 把「未分类」改名为「临时」(不能直接 renameCategory —— 它会拒绝,
// 所以这里直接修改 cat.name。模拟「文件里没有「未分类」这个魔法名」的状态。
const unCat = store.categories.find(c => c.parentOtherTasks && c.name === UNCATEGORIZED_NAME);
if (unCat) unCat.name = '临时';
store.addSubCategory('副业');
const subs = store.getSubCategories().map(c => c.name);
// 没有「未分类」魔法名时,新分类应该走"容器之后最后一个 parentOtherTasks 之后"的旧路径
check(`「未分类」魔法名不存在时,新分类追加到 parentOtherTasks 末尾`,
subs[subs.length - 1] === '副业',
`实际:${JSON.stringify(subs)}`);
}
console.log('\n[5] 加载文件时把「未分类」从中间拉到末尾_normalizeOrder 兜底)');
{
// 手工编辑的文件 / 旧版迁移残留都可能让「未分类」落在中间。
// _normalizeOrder 是唯一兜底 —— 加在子分类末尾,与 reorderSubCategory 在运行时侧
// 阻止「未分类」被拖走形成对称(运行时守入口,加载时归位)。
const content = `# 全部任务
## 工作
- [ ] 工作1
## 学习
- [ ] 学习1
## ${UNCATEGORIZED_NAME}
- [ ] 兜底1
## 生活
- [ ] 生活1
`;
const store = new TaskStore();
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
store.loadFromContent(content, null);
const subs = store.getSubCategories().map(c => c.name);
check(`加载后「${UNCATEGORIZED_NAME}」在末尾`, subs[subs.length - 1] === UNCATEGORIZED_NAME,
`实际:${JSON.stringify(subs)}`);
check(`加载后其它子分类的相对顺序保持`,
JSON.stringify(subs.slice(0, -1)) === '["工作","学习","生活"]',
`实际:${JSON.stringify(subs.slice(0, -1))}`);
}
console.log('\n[6] loadFromContent 软合并分支也会归位「未分类」');
{
// 模拟「外部编辑把「未分类」放中间,用户合并磁盘改动」的场景 —— 合并结果里
// 「未分类」也被 _normalizeOrder 拉回末尾。
const store = new TaskStore();
store.loadDefault(null);
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
// 内存里现在顺序是 工作 / 学习 / 生活 / 未分类
// 模拟磁盘版本:把「未分类」挪到中间(解析器读到的样子)
const diskContent = `# 全部任务
## 工作
- [ ] 工作-磁盘版
## ${UNCATEGORIZED_NAME}
- [ ] 兜底-磁盘版
## 学习
- [ ] 学习-磁盘版
## 生活
- [ ] 生活-磁盘版
`;
// 用 mergeResolutions 走软合并分支addSubCategory 测试不到这条路径)
store.loadFromContent(diskContent, null, {
mergeResolutions: { '__all__': 'a' }
});
const subs = store.getSubCategories().map(c => c.name);
check(`软合并后「${UNCATEGORIZED_NAME}」在末尾`, subs[subs.length - 1] === UNCATEGORIZED_NAME,
`实际:${JSON.stringify(subs)}`);
}
console.log('\n' + '='.repeat(64));
console.log(`结果:${summary.pass} 通过, ${summary.fail} 失败`);
console.log('='.repeat(64));
process.exit(summary.fail === 0 ? 0 : 1);