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,125 @@
// 回归测试:「已完成任务」智能视图下右键菜单不出现「移到分类…」分组。
//
// 用户反馈2026-08-23在「已完成任务」分类里右键任务时「移到分类…」
// 分组没有意义 —— 按 v3.4 数据归属不变量task-store.js:950completed=true
// 任务只能活在 kind=COMPLETED 里,选任何一个普通分类都会被 store 硬路由回
// 「已完成任务」分类,等于死按钮。
//
// 测试路径:通过 src/ui/task-context-menu.js 的 buildStandardContextMenuItems
// 直接构造 items 数组helper 是 pure function不碰 DOM所以 Node 里能直接
// 跑helper 同时被 task-list.js 调,与 UI 共用一份判定逻辑——产品与测试不会发散)。
//
// [1] 「已完成任务」智能视图下菜单**不含** 'move-header' / 'move:*' 项
// 「标记为未完成」「移到回收站」必须仍在(防过度收紧)
// [2] 普通子分类(这里用「工作」)下右键**仍含** 'move-header' + 'move:*'
// 且 move 目标里不混进「已完成任务」/「回收站」/「全部任务」
//
// 用法node scripts/check-completed-context-menu.mjs
import { installApiStub } from './_lib/api-stub.mjs';
import { check, summary, printSummary } from './_lib/check.mjs';
// 让 TaskStore 不真去写文件(构造 store 时不会触发,但 toggleTask 等会调 window.api
installApiStub();
const { TaskStore } = await import('../src/task-store.js');
const { buildStandardContextMenuItems } = await import('../src/ui/task-context-menu.js');
// ============================================================
// 构造一个含「已完成任务」的最小 store
// ============================================================
const seedDoc = [
'# 全部任务', '',
'## 工作', '',
'- [ ] 写周报', '',
'## 未分类', ''
].join('\n');
const store = new TaskStore();
store.loadFromContent(seedDoc, null);
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
const workTask = store.getCategory('工作').tasks[0];
check('seed 文档工作分类里有任务', !!workTask && workTask.completed === false);
// toggle → 自动搬到 kind=COMPLETED 分类
store.toggleTask('工作', workTask.id);
const completedCat = store.categories.find(c => c.kind === 'completed');
check('toggle 后产生「已完成任务」分类kind=COMPLETED',
!!completedCat && completedCat.tasks.length === 1,
completedCat ? `${completedCat.tasks.length}` : '分类不存在');
const completedTask = completedCat.tasks[0];
check('完成的那条任务在「已完成任务」里', completedTask && completedTask.completed === true);
// 「工作」分类里再加一条未完成任务(用来测普通分类分支)
store.addTask('工作', '已加测试任务');
const workActiveTask = store.getCategory('工作').tasks
.find(t => !t.completed && t.text === '已加测试任务');
check('「工作」里有一条新的未完成任务', !!workActiveTask);
// ============================================================
// [1] 「已完成任务」智能视图下右键 → 不应出现「移到分类…」
// ============================================================
console.log('\n[1] 「已完成任务」智能视图下右键不出现「移到分类…」');
store.selectSmartList('completed');
const completedView = store.getSelectedCategory();
check('getSelectedCategory() 是「已完成任务」智能视图',
completedView.isSmartList === true && completedView.smartKey === 'completed',
JSON.stringify({ isSmartList: completedView.isSmartList, smartKey: completedView.smartKey }));
const completedMenu = buildStandardContextMenuItems(completedView, completedTask, store);
const hasMoveHeader = completedMenu.some(i => i.action === 'move-header');
const hasMoveTargets = completedMenu.some(i =>
typeof i.action === 'string' && i.action.startsWith('move:'));
const hasMoveLabel = completedMenu.some(i => i.label === '移到分类…');
check('已完成视图下菜单不含 move-header', hasMoveHeader === false);
check('已完成视图下菜单不含任何 move:* 目标项', hasMoveTargets === false);
check('已完成视图下菜单不含「移到分类…」label', hasMoveLabel === false);
// 但「移到回收站」「标记为未完成」「编辑」「标记为当前/重要」必须仍在
check('已完成视图下保留「移到回收站」',
completedMenu.some(i => i.action === 'delete'));
check('已完成视图下保留「标记为未完成」',
completedMenu.some(i => i.action === 'toggle'));
check('已完成视图下保留「编辑」',
completedMenu.some(i => i.action === 'edit'));
check('已完成视图下保留「标记为当前/取消当前」',
completedMenu.some(i => i.action === 'current'));
check('已完成视图下保留「标记为重要/取消星标」',
completedMenu.some(i => i.action === 'important'));
// ============================================================
// [2] 普通子分类下右键 → 「移到分类…」必须仍在(防过度收紧)
// ============================================================
console.log('\n[2] 普通子分类(工作)下右键「移到分类…」仍然存在');
store.selectCategory('工作');
const workView = store.getSelectedCategory();
check('getSelectedCategory() 切到了「工作」',
workView.isSmartList !== true && workView.name === '工作');
const normalMenu = buildStandardContextMenuItems(workView, workActiveTask, store);
check('普通分类下菜单含 move-header',
normalMenu.some(i => i.action === 'move-header'));
const moveActions = normalMenu
.filter(i => typeof i.action === 'string' && i.action.startsWith('move:'))
.map(i => i.action);
check('普通分类下菜单含至少一个 move:* 目标项',
moveActions.length > 0,
`actions: ${moveActions.join(',') || '(无)'}`);
// move 目标里绝对不能混进保留名分类
check('move 目标里不含「全部任务」OTHERTASKS 已过滤)',
!moveActions.includes('move:全部任务'));
check('move 目标里不含「回收站」TRASH 已过滤)',
!moveActions.includes('move:回收站'));
check('move 目标里不含「已完成任务」COMPLETED 已过滤)',
!moveActions.includes('move:已完成任务'));
check('move 目标里不含「工作」自身(与自身重命名等价,留着会让菜单很怪)',
!moveActions.includes('move:工作'));
printSummary('check-completed-context-menu');