update
This commit is contained in:
545
scripts/check-insert-position.mjs
Normal file
545
scripts/check-insert-position.mjs
Normal file
@@ -0,0 +1,545 @@
|
||||
// 回归测试:「任务插入位置」设置(newTaskPosition / completedPosition /
|
||||
// uncompletePosition / trashPosition / restorePosition 五件套)
|
||||
//
|
||||
// 用户偏好五类插入动作的落点:前面(front, 最新在最上)/ 后面(back, 最新在最下)。
|
||||
// 五类动作分别是:
|
||||
// - 新增任务 (addTask / importTasksToCategory 的 pending 部分)
|
||||
// - 完成 (toggleTask 打勾 / batchToggleCompleted(true) / importTasksToCategory 的 completed 部分)
|
||||
// - 取消完成 (toggleTask 取消勾选 / restoreCompletedTask / batchToggleCompleted(false) / picker 强制降级)
|
||||
// - 删除 (deleteTask / batchDeleteTasks / clearCompleted)
|
||||
// - 恢复 (restoreTask 从回收站归位)
|
||||
//
|
||||
// 这份脚本直接构造 TaskStore + 桩 SettingsStore,验证:
|
||||
// 1. 默认(不注入 settingsStore)= front,等价于 DEFAULT_SETTINGS。
|
||||
// 2. settingsStore 注入后,按 *Position 的值决定 push / unshift。
|
||||
// 3. 切换设置后再插入:仅新插入的任务受影响,已存在的任务不会被回溯重排。
|
||||
// 4. SettingsStore 拒绝非法值(与 settings-store 的 VALID_VALUES 对齐)。
|
||||
// 5. settingsStore.update 持久化设置后,TaskStore 立即读到新值(无需重启)。
|
||||
//
|
||||
// 跑法:node scripts/check-insert-position.mjs
|
||||
|
||||
import { check, ok, bad, summary, printSummary } from './_lib/check.mjs';
|
||||
import { installApiStub } from './_lib/api-stub.mjs';
|
||||
|
||||
// TaskStore 的模块作用域会注册事件回调,但仅用到内存对象,没有副作用。
|
||||
installApiStub();
|
||||
|
||||
const { TaskStore, OTHER_TASKS_NAME, COMPLETED_NAME, TRASH_NAME } = await import('../src/task-store.js');
|
||||
const { SettingsStore } = await import('../src/settings-store.js');
|
||||
|
||||
// 简单桩:复刻 SettingsStore.get 的最小接口,行为上把 'newTaskPosition' 等字段
|
||||
// 当作对象属性读取。TaskStore 不关心 SettingsStore 是不是 EventEmitter、不关心
|
||||
// load()/update(),所以不实现事件也够用。
|
||||
function makeSettingsStub(values = {}) {
|
||||
return {
|
||||
_values: { ...values },
|
||||
get(key) { return this._values[key]; }
|
||||
};
|
||||
}
|
||||
|
||||
// 取分类下任务文本数组 —— 断言时只关心「顺序」与「文本」,不看 id / 标记字段。
|
||||
function taskTexts(cat) {
|
||||
return (cat?.tasks || []).map(t => t.text);
|
||||
}
|
||||
|
||||
// 取「已完成」分类下的任务文本。
|
||||
function completedTexts(store) {
|
||||
const c = store.getCompletedCategory();
|
||||
return taskTexts(c);
|
||||
}
|
||||
|
||||
// 取「回收站」分类下的任务文本。
|
||||
function trashTexts(store) {
|
||||
const c = store.getTrashCategory();
|
||||
return taskTexts(c);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 1. 默认行为:未注入 settingsStore → 等价于 'front'(最新在最上)
|
||||
// ============================================
|
||||
console.log('[1] 默认(未注入 settingsStore)→ 插入到列表前面');
|
||||
|
||||
{
|
||||
const store = new TaskStore();
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
// 新增任务:默认应该在最前面
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
check('未注入 settingsStore 时新增任务落到前面',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['B', 'A']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
|
||||
// 完成:默认应该在「已完成任务」前面 —— 勾掉 A(位置 [1]),再勾掉 B(位置 [0]),
|
||||
// 期望 completedTexts 顺序 = [B, A](最新完成的最上面)。
|
||||
store.toggleTask('工作', workCat.tasks[1].id); // 勾 A
|
||||
store.toggleTask('工作', workCat.tasks[0].id); // 勾 B
|
||||
check('未注入 settingsStore 时打勾任务按完成顺序落到「已完成」前面',
|
||||
JSON.stringify(completedTexts(store)) === JSON.stringify(['B', 'A']),
|
||||
`actual=${JSON.stringify(completedTexts(store))}`);
|
||||
|
||||
// 删除:默认应该在「回收站」前面
|
||||
store.addTask('生活', 'X');
|
||||
store.addTask('生活', 'Y');
|
||||
const liveCat = store.getCategory('生活');
|
||||
// liveCat.tasks = [Y, X](front 模式)。先删 Y(tasks[0]),再删 X(tasks[0])
|
||||
store.deleteTask('生活', liveCat.tasks[0].id); // 删 Y
|
||||
store.deleteTask('生活', liveCat.tasks[0].id); // 删 X
|
||||
check('未注入 settingsStore 时删除任务按删除顺序落到「回收站」前面',
|
||||
JSON.stringify(trashTexts(store)) === JSON.stringify(['X', 'Y']),
|
||||
`actual=${JSON.stringify(trashTexts(store))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 2. 注入 settingsStore 后按 *Position 决定 push / unshift
|
||||
// ============================================
|
||||
console.log('\n[2] settingsStore.newTaskPosition="back" → 新增任务落到列表后面');
|
||||
|
||||
{
|
||||
const settings = makeSettingsStub({ newTaskPosition: 'back' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
check('newTaskPosition=back 时新增任务按入参顺序落到末尾',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['A', 'B']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
console.log('\n[3] settingsStore.completedPosition="back" → 完成的任务落到「已完成」后面');
|
||||
|
||||
{
|
||||
const settings = makeSettingsStub({ completedPosition: 'back' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
const workCat = store.getCategory('工作');
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
// 工作顺序:默认 front → [B, A]。先勾 A(后入参 = 最新,但 front 模式下 [1]),
|
||||
// 再勾 B(最旧)→ back 模式下完成顺序:[A, B](最早完成的在最上,最后完成的在最下)。
|
||||
store.toggleTask('工作', workCat.tasks[1].id); // 勾 A
|
||||
store.toggleTask('工作', workCat.tasks[0].id); // 勾 B
|
||||
check('completedPosition=back 时按完成顺序追加到「已完成」末尾',
|
||||
JSON.stringify(completedTexts(store)) === JSON.stringify(['A', 'B']),
|
||||
`actual=${JSON.stringify(completedTexts(store))}`);
|
||||
}
|
||||
|
||||
console.log('\n[4] settingsStore.trashPosition="back" → 删除的任务落到「回收站」后面');
|
||||
|
||||
{
|
||||
const settings = makeSettingsStub({ trashPosition: 'back' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
// 工作顺序:默认 front → [B, A]。先删 A(最新),再删 B(最旧)→ back 模式下
|
||||
// 回收站顺序:[A, B](最新删除的... 等等,back 模式下"最新"在最下面)。
|
||||
// 等等,这里 user 期望"最新在最下"(back 模式语义):先删 A → 在 [0],再删 B → 追加在 [1]。
|
||||
store.deleteTask('工作', workCat.tasks[1].id); // 删 A
|
||||
store.deleteTask('工作', workCat.tasks[0].id); // 删 B
|
||||
check('trashPosition=back 时按删除顺序追加到「回收站」末尾',
|
||||
JSON.stringify(trashTexts(store)) === JSON.stringify(['A', 'B']),
|
||||
`actual=${JSON.stringify(trashTexts(store))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 5. 切换设置 → 下一条新插入立即生效,已存在的任务不重排
|
||||
// ============================================
|
||||
console.log('\n[5] 切换设置只影响新插入,已存在的任务不被回溯重排');
|
||||
|
||||
{
|
||||
const settings = makeSettingsStub({ newTaskPosition: 'front' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
|
||||
// 切到 'back'
|
||||
settings._values.newTaskPosition = 'back';
|
||||
store.addTask('工作', 'C');
|
||||
|
||||
check('切到 back 后新插入的 C 落到末尾',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['B', 'A', 'C']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
|
||||
// 再切回 'front'
|
||||
settings._values.newTaskPosition = 'front';
|
||||
store.addTask('工作', 'D');
|
||||
check('切回 front 后新插入的 D 落到最前,已存在的 [B, A, C] 不动',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['D', 'B', 'A', 'C']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 6. 非法值被 TaskStore 兜底为 'front'(settingsStore 校验已经兜过,
|
||||
// 这里再守一道防御:万一有人绕过 settingsStore 直接塞非法值进来)
|
||||
// ============================================
|
||||
console.log('\n[6] 非法 *Position 值被 TaskStore 兜底为 front');
|
||||
|
||||
{
|
||||
const settings = makeSettingsStub({ newTaskPosition: 'middle' /* 非法 */ });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
check('非法 newTaskPosition="middle" 兜底为 front(最新在最上)',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['B', 'A']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 7. SettingsStore 自身拒绝非法值(与 settings-store 的 VALID_VALUES 对齐)
|
||||
// ============================================
|
||||
console.log('\n[7] SettingsStore 拒绝非法 *Position 值');
|
||||
|
||||
{
|
||||
const s = new SettingsStore();
|
||||
await s.update({ newTaskPosition: 'middle' });
|
||||
if (s.get('newTaskPosition') === 'front') {
|
||||
ok('update({newTaskPosition: "middle"}) 被忽略,保持默认 front');
|
||||
} else {
|
||||
bad('newTaskPosition 被非法值污染', `当前 ${JSON.stringify(s.get('newTaskPosition'))}`);
|
||||
}
|
||||
|
||||
await s.update({ completedPosition: null });
|
||||
if (s.get('completedPosition') === 'front') {
|
||||
ok('update({completedPosition: null}) 被忽略');
|
||||
} else {
|
||||
bad('completedPosition 被 null 污染', `当前 ${JSON.stringify(s.get('completedPosition'))}`);
|
||||
}
|
||||
|
||||
await s.update({ trashPosition: 'front' });
|
||||
if (s.get('trashPosition') === 'front') {
|
||||
ok('update({trashPosition: "front"}) 合法值生效');
|
||||
} else {
|
||||
bad('trashPosition 未生效', `当前 ${JSON.stringify(s.get('trashPosition'))}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 8. SettingsStore.update 后 TaskStore 立即读到新值(无需重启)
|
||||
// ============================================
|
||||
console.log('\n[8] SettingsStore.update 后下一次插入立即按新值生效');
|
||||
|
||||
{
|
||||
const s = new SettingsStore();
|
||||
const store = new TaskStore({ settingsStore: s });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
await s.update({ newTaskPosition: 'back' });
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
check('update back 后插入按入参顺序追加',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['A', 'B']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
|
||||
await s.update({ newTaskPosition: 'front' });
|
||||
store.addTask('工作', 'C');
|
||||
check('再 update 回 front 后新插入落到最前',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['C', 'A', 'B']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 9. 批量路径同样遵守设置:importTasksToCategory 的 pending 部分用 newTaskPosition,
|
||||
// completed 部分用 completedPosition。
|
||||
// ============================================
|
||||
console.log('\n[9] importTasksToCategory 批量插入遵守 *Position 设置');
|
||||
|
||||
{
|
||||
const settings = makeSettingsStub({
|
||||
newTaskPosition: 'back',
|
||||
completedPosition: 'back'
|
||||
});
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
// 第一批:往「工作」导入 A、B、C(都是 pending)
|
||||
store.importTasksToCategory('工作', ['A', 'B', 'C']);
|
||||
const workCat = store.getCategory('工作');
|
||||
check('newTaskPosition=back 时批量 pending 按入参顺序落到末尾',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['A', 'B', 'C']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
|
||||
// 第二批:往「生活」导入 - [x] 已完成(completed 部分)
|
||||
store.importTasksToCategory('生活', ['- [x] X', '- [x] Y', '- [x] Z']);
|
||||
check('completedPosition=back 时批量 completed 按入参顺序落到末尾',
|
||||
JSON.stringify(completedTexts(store)) === JSON.stringify(['X', 'Y', 'Z']),
|
||||
`actual=${JSON.stringify(completedTexts(store))}`);
|
||||
}
|
||||
|
||||
console.log('\n[10] importTasksToCategory importPosition 默认模式:按入参顺序 + 插到最前');
|
||||
|
||||
{
|
||||
// 修复用户报告 bug 后:导入默认行为不再复用 newTaskPosition='front'(最后一行在最前),
|
||||
// 而是独立的 importPosition='order'(第一行在最前,按入参顺序)。这里验证新的默认。
|
||||
const settings = makeSettingsStub({});
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.importTasksToCategory('工作', ['A', 'B', 'C']);
|
||||
const workCat = store.getCategory('工作');
|
||||
// 入参顺序 A→B→C,order 模式(默认)→ unshift 整批 → A 在最前
|
||||
check('默认 importPosition=order:按入参顺序 + 插到最前(A 在最前)',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['A', 'B', 'C']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
|
||||
// 显式设 importPosition='front' 验证旧 front 语义仍可用
|
||||
const settingsFront = makeSettingsStub({ importPosition: 'front' });
|
||||
const storeFront = new TaskStore({ settingsStore: settingsFront });
|
||||
storeFront.loadDefault(null);
|
||||
storeFront._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
storeFront.importTasksToCategory('工作', ['A', 'B', 'C']);
|
||||
check('显式 importPosition=front:倒序 unshift(C 在最前)',
|
||||
JSON.stringify(taskTexts(storeFront.getCategory('工作'))) === JSON.stringify(['C', 'B', 'A']),
|
||||
`actual=${JSON.stringify(taskTexts(storeFront.getCategory('工作')))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 11. 批量删除(batchDeleteTasks)和 clearCompleted 也遵守 trashPosition
|
||||
// ============================================
|
||||
console.log('\n[11] clearCompleted / batchDeleteTasks 遵守 trashPosition');
|
||||
|
||||
{
|
||||
// front(默认)
|
||||
const settingsFront = makeSettingsStub({ trashPosition: 'front' });
|
||||
const store1 = new TaskStore({ settingsStore: settingsFront });
|
||||
store1.loadDefault(null);
|
||||
store1._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
store1.addTask('工作', 'A');
|
||||
store1.addTask('工作', 'B');
|
||||
let workCat = store1.getCategory('工作');
|
||||
// 勾两条 → 都进「已完成」
|
||||
store1.toggleTask('工作', workCat.tasks[0].id);
|
||||
store1.toggleTask('工作', workCat.tasks[0].id);
|
||||
// clearCompleted → 两条都已完成 → 全部进 trash
|
||||
store1.clearCompleted();
|
||||
check('trashPosition=front 时 clearCompleted 把最新清走的排在最前',
|
||||
JSON.stringify(trashTexts(store1)) === JSON.stringify(['B', 'A']),
|
||||
`actual=${JSON.stringify(trashTexts(store1))}`);
|
||||
}
|
||||
|
||||
{
|
||||
// back
|
||||
const settingsBack = makeSettingsStub({ trashPosition: 'back' });
|
||||
const store2 = new TaskStore({ settingsStore: settingsBack });
|
||||
store2.loadDefault(null);
|
||||
store2._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
store2.addTask('工作', 'A');
|
||||
store2.addTask('工作', 'B');
|
||||
let workCat = store2.getCategory('工作');
|
||||
store2.toggleTask('工作', workCat.tasks[0].id);
|
||||
store2.toggleTask('工作', workCat.tasks[0].id);
|
||||
store2.clearCompleted();
|
||||
check('trashPosition=back 时 clearCompleted 按清走顺序追加',
|
||||
JSON.stringify(trashTexts(store2)) === JSON.stringify(['A', 'B']),
|
||||
`actual=${JSON.stringify(trashTexts(store2))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 12. 恢复路径遵守 restorePosition / uncompletePosition
|
||||
// ============================================
|
||||
console.log('\n[12] restoreTask 遵守 restorePosition');
|
||||
|
||||
{
|
||||
// restorePosition=back:A 回到末尾
|
||||
const settings = makeSettingsStub({ restorePosition: 'back' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
// 工作当前顺序 [B, A](默认 front)。删 A(tasks[1])
|
||||
store.deleteTask('工作', workCat.tasks[1].id);
|
||||
const trashCat = store.getTrashCategory();
|
||||
store.restoreTask(trashCat.tasks[0].id);
|
||||
check('restorePosition=back 时 A 回到「工作」末尾',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['B', 'A']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
{
|
||||
// restorePosition=front(默认):A 回到最前
|
||||
const settings = makeSettingsStub({ restorePosition: 'front' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
// 工作当前顺序 [B, A](默认 front)。删 A(tasks[1])
|
||||
store.deleteTask('工作', workCat.tasks[1].id);
|
||||
const trashCat = store.getTrashCategory();
|
||||
store.restoreTask(trashCat.tasks[0].id);
|
||||
check('restorePosition=front 时 A 回到「工作」最前',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['A', 'B']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 13. 取消完成(toggleTask 取消勾选 / restoreCompletedTask)遵守 uncompletePosition
|
||||
// ============================================
|
||||
console.log('\n[13] toggleTask 取消勾选 遵守 uncompletePosition');
|
||||
|
||||
{
|
||||
// uncompletePosition=back:取消勾选后追加到末尾
|
||||
const settings = makeSettingsStub({ uncompletePosition: 'back' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
// 工作顺序 [B, A]。勾 A(tasks[1])→ 进 completed
|
||||
store.toggleTask('工作', workCat.tasks[1].id);
|
||||
// 取消勾选 → 回到工作,uncompletePosition=back → 追加到末尾 → [B, A]
|
||||
const completedTaskId = store.getCompletedCategory().tasks[0].id;
|
||||
store.toggleTask(COMPLETED_NAME, completedTaskId);
|
||||
check('uncompletePosition=back 时取消勾选追加到「工作」末尾',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['B', 'A']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
{
|
||||
// uncompletePosition=front(默认):取消勾选后落到最前
|
||||
const settings = makeSettingsStub({ uncompletePosition: 'front' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
// 工作顺序 [B, A]。勾 A → 进 completed
|
||||
store.toggleTask('工作', workCat.tasks[1].id);
|
||||
// 取消勾选 → 回到工作,front → unshift 到最前 → [A, B]
|
||||
const completedTaskId = store.getCompletedCategory().tasks[0].id;
|
||||
store.toggleTask(COMPLETED_NAME, completedTaskId);
|
||||
check('uncompletePosition=front 时取消勾选 unshift 到「工作」最前',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['A', 'B']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 14. restoreCompletedTask(picker 已选好目标分类)遵守 uncompletePosition
|
||||
// ============================================
|
||||
console.log('\n[14] restoreCompletedTask 遵守 uncompletePosition');
|
||||
|
||||
{
|
||||
// uncompletePosition=front:restoreCompletedTask 走「未分类」时落到最前
|
||||
const settings = makeSettingsStub({ uncompletePosition: 'front' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
// 工作顺序 [B, A]。勾 A → 进 completed
|
||||
store.toggleTask('工作', workCat.tasks[1].id);
|
||||
const completedTaskId = store.getCompletedCategory().tasks[0].id;
|
||||
// UI picker 选了「工作」—— restoreCompletedTask 直接落到「工作」最前
|
||||
store.restoreCompletedTask(completedTaskId, '工作');
|
||||
check('uncompletePosition=front 时 restoreCompletedTask 把 A 放到「工作」最前',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['A', 'B']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
{
|
||||
// uncompletePosition=back:restoreCompletedTask 落到末尾
|
||||
const settings = makeSettingsStub({ uncompletePosition: 'back' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
const workCat = store.getCategory('工作');
|
||||
// 工作顺序 [B, A]。勾 A → 进 completed
|
||||
store.toggleTask('工作', workCat.tasks[1].id);
|
||||
const completedTaskId = store.getCompletedCategory().tasks[0].id;
|
||||
// restoreCompletedTask 直接落到「工作」末尾
|
||||
store.restoreCompletedTask(completedTaskId, '工作');
|
||||
check('uncompletePosition=back 时 restoreCompletedTask 把 A 放到「工作」末尾',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['B', 'A']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 15. batchToggleCompleted(false) 遵守 uncompletePosition
|
||||
// ============================================
|
||||
console.log('\n[15] batchToggleCompleted(false) 遵守 uncompletePosition');
|
||||
|
||||
{
|
||||
// uncompletePosition=front:批量取消勾选按 parseOrder 倒序 unshift 到最前
|
||||
const settings = makeSettingsStub({ uncompletePosition: 'front' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
// 先在「工作」建 A、B、C(默认 front → tasks = [C, B, A])
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
store.addTask('工作', 'C');
|
||||
const workCat = store.getCategory('工作');
|
||||
// 全部勾到 completed —— taskRefs 形如 [{categoryName, taskId}, ...]
|
||||
const refs = workCat.tasks.map(t => ({ categoryName: '工作', taskId: t.id }));
|
||||
store.batchToggleCompleted(refs, true);
|
||||
// 把三条都取消勾选 → 它们按 parseOrder 倒序 unshift 到工作最前
|
||||
const completedCat = store.getCompletedCategory();
|
||||
const completedRefs = completedCat.tasks.map(t => ({ categoryName: '已完成任务', taskId: t.id }));
|
||||
store.batchToggleCompleted(completedRefs, false);
|
||||
check('uncompletePosition=front 时 batchToggleCompleted(false) 按 parseOrder 倒序 unshift',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['C', 'B', 'A']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
{
|
||||
// uncompletePosition=back:批量取消勾选按 parseOrder 顺序追加到末尾
|
||||
const settings = makeSettingsStub({ uncompletePosition: 'back' });
|
||||
const store = new TaskStore({ settingsStore: settings });
|
||||
store.loadDefault(null);
|
||||
store._autoSave = Object.assign(() => {}, { cancel() {}, flush() {} });
|
||||
|
||||
store.addTask('工作', 'A');
|
||||
store.addTask('工作', 'B');
|
||||
store.addTask('工作', 'C');
|
||||
const workCat = store.getCategory('工作');
|
||||
const refs = workCat.tasks.map(t => ({ categoryName: '工作', taskId: t.id }));
|
||||
store.batchToggleCompleted(refs, true);
|
||||
const completedCat = store.getCompletedCategory();
|
||||
const completedRefs = completedCat.tasks.map(t => ({ categoryName: '已完成任务', taskId: t.id }));
|
||||
store.batchToggleCompleted(completedRefs, false);
|
||||
check('uncompletePosition=back 时 batchToggleCompleted(false) 按 parseOrder 追加',
|
||||
JSON.stringify(taskTexts(workCat)) === JSON.stringify(['A', 'B', 'C']),
|
||||
`actual=${JSON.stringify(taskTexts(workCat))}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
console.log(`\n${'─'.repeat(48)}`);
|
||||
console.log(`通过 ${summary.pass} / 失败 ${summary.fail}`);
|
||||
process.exit(summary.fail === 0 ? 0 : 1);
|
||||
Reference in New Issue
Block a user