update
This commit is contained in:
155
scripts/check-uncategorized-position.mjs
Normal file
155
scripts/check-uncategorized-position.mjs
Normal file
@@ -0,0 +1,155 @@
|
||||
// 一次性验证脚本:「未分类」必须始终排在子分类数组的末尾,
|
||||
// 新建的子分类不能挤到「未分类」下面。
|
||||
//
|
||||
// 背景:之前 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);
|
||||
Reference in New Issue
Block a user