Files
Literature-Management/tests/unit/renderer/store/self-write-window-parity.test.ts
2026-10-21 14:42:59 +08:00

101 lines
5.1 KiB
TypeScript

// tests/unit/renderer/store/self-write-window-parity.test.ts
//
// 守 r221「自写抑制窗口两域数值刻意不同」这条文档-代码一致性契约。
//
// ── 背景(peer r217 登记的未修项 ③)────────────────────────────────
// 自写抑制窗口(写操作后跳过自己引发的 CHANGED 推送)在两域取值不同:
// src/renderer/src/store/notes.ts SELF_WRITE_SUPPRESS_WINDOW_MS = 500
// src/renderer/src/store/literature.ts SELF_WRITE_SUPPRESS_WINDOW_MS = 200
// 而注释互相矛盾:notes 注释说「500ms 比 IPC roundtrip 略长」(真话是
// 要盖 watcher echo);ARCHITECTURE §8② 说两域都是 ~200ms;main/index.ts
// 的 literature 广播注释还停在 r136 M3 收紧前的「500ms self-write」。
//
// ── 为什么不是「统一成一个值」(r221 对账结论)─────────────────────
// notes 500ms:save() 走乐观 patch、**不自己 load**,窗口必须盖过
// watcher echo(chokidar awaitWriteFinish 50ms + reload 去抖 100ms +
// scan/广播 ≈ 写后 150~400ms);收 200ms 会让 echo 漏出窗口 ⇒ 每秒
// autosave 多一次全量拉 + 全列表重渲(perf 2026-09-04 的优化失效)。
// literature 200ms:writer 自己 force load,窗口只需盖主进程同步 echo
// (≈130ms);它的 watcher echo(awf 200ms + 去抖 100ms ≥ 300ms)
// 本来就盖不住,漏出的那次 force load 当 no-op 兜底接受(r136 注释
// 明示)。窗口越宽,窗口内的**外部**变更被当自写吞掉越久 ⇒
// literature 坚持 200ms 是 r136 的刻意收紧,notes 保留 500ms 是
// 上述时序的必然。两值不同是设计,不是漂移。
//
// ── 本测锁的契约 ───────────────────────────────────────────────────
// A. 两个常量保持 500 / 200(回归锚 —— 数值本身是设计决策);
// B. main/index.ts 的 literature 广播注释不再谎称 500ms(r136 旧值);
// D. notes 的常量注释块必须说明 500ms 的真实原因(watcher echo);
// E. 两侧注释块互相交叉引用对方数值(防未来「好心统一」时不读理由)。
//
// (原契约 C 锁 ARCHITECTURE.md §8② 同时写出两个值;2026-10-20 文档
// 三合一并入 README 后该文件删除,设计理由的单一真相源收编进两侧
// 代码注释 —— 即契约 D/E,ARCH 守卫随之退役。)
//
// 纯静态扫源码文本,不起进程。
import { readFileSync } from 'fs';
import { join } from 'path';
import { describe, expect, it } from 'vitest';
const ROOT = process.cwd();
const read = (rel: string): string => readFileSync(join(ROOT, rel), 'utf-8');
const NOTES_STORE = 'src/renderer/src/store/notes.ts';
const LIT_STORE = 'src/renderer/src/store/literature.ts';
const MAIN_INDEX = 'src/main/index.ts';
/** 取常量定义行之前的注释块(理由就写在那里)。 */
function commentBlockBefore(src: string, anchor: string, span = 1800): string {
const i = src.indexOf(anchor);
expect(i, `锚点缺失:${anchor}`).toBeGreaterThanOrEqual(0);
return src.slice(Math.max(0, i - span), i);
}
describe('自写抑制窗口两域刻意不同(r221)', () => {
it('A1 notes 窗口 = 500ms(回归锚:盖 watcher echo 的必然值)', () => {
expect(read(NOTES_STORE)).toContain('SELF_WRITE_SUPPRESS_WINDOW_MS = 500');
});
it('A2 literature 窗口 = 200ms(回归锚:r136 M3 刻意收紧值)', () => {
expect(read(LIT_STORE)).toContain('SELF_WRITE_SUPPRESS_WINDOW_MS = 200');
});
it('B main/index.ts 的 literature 广播注释不再谎称窗口是 500ms', () => {
const src = read(MAIN_INDEX);
const block = commentBlockBefore(src, "literatureService.on('changed'");
// 谎的具体形态 = 「订阅 + 500ms self-write 抑制窗口」(r136 M3 收紧前的
// 旧值);注释里交叉引用 notes 的 500ms 是允许的。
expect(block, 'literature 广播注释还停在 r136 M3 收紧前的 500ms 旧值').not.toMatch(
/订阅 \+ 500ms/,
);
expect(block).toMatch(/订阅 \+ 200ms/);
});
it('D notes 的 500ms 注释块说明真实原因(watcher echo),不是「比 IPC roundtrip 略长」', () => {
const block = commentBlockBefore(
read(NOTES_STORE),
'SELF_WRITE_SUPPRESS_WINDOW_MS = 500',
);
expect(block, '500ms 的真实原因是盖 watcher echo,注释必须写明').toContain(
'watcher echo',
);
});
it('E 两侧注释块交叉引用对方数值(防未来「好心统一」时不读理由)', () => {
const notesBlock = commentBlockBefore(
read(NOTES_STORE),
'SELF_WRITE_SUPPRESS_WINDOW_MS = 500',
);
const litBlock = commentBlockBefore(
read(LIT_STORE),
'SELF_WRITE_SUPPRESS_WINDOW_MS = 200',
);
expect(notesBlock, 'notes 注释必须点名 literature 的 200ms 及差异原因').toContain(
'literature 的 200ms',
);
expect(litBlock, 'literature 注释必须点名 notes 的 500ms 及差异原因').toContain(
'notes 域是 500ms',
);
});
});