diff --git a/client/electron/test/harmony-calendar.test.mjs b/client/electron/test/harmony-calendar.test.mjs index 001b37c..d19578a 100644 --- a/client/electron/test/harmony-calendar.test.mjs +++ b/client/electron/test/harmony-calendar.test.mjs @@ -5,6 +5,23 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; import { readdirSync } from 'node:fs'; import { code } from './lib/read.mjs'; +/* + * `code()` 去的是**注释**,不是**字符串**(pi 2026-09-14 §2):任何只是"提到"被禁写法的 + * 字符串(UI 提示、dev 警告、甚至报错文案自己)都会把它自己判红 —— 那不是它要管的东西。 + * + * **口径现在就定下来**(两条路里选"只在表达式位置匹配"):先整段去掉字符串字面量,再匹配。 + * 于是"提到"不再误红,而**真的用它取日期键**照样红。 + * 副作用写在旁边,免得下一个人当成漏网:这是**正则近似、不是解析器** —— + * 转义引号、跨行模板、嵌套模板都处理不干净,它是采样而非完备性证明。 + * **出现同族误红时不许"再改一次扫描口径"把判据往宽里挪。** + */ +function stripStrings(src) { + return src + .replace(/`(?:[^`\\]|\\.)*`/g, '``') + .replace(/'(?:[^'\\\n]|\\.)*'/g, "''") + .replace(/"(?:[^"\\\n]|\\.)*"/g, '""'); +} + /* ★ 日历的纯逻辑(P6 第 1 步的逻辑那一半)—— **不需要设备**,所以它不该进静态欠账。 @@ -152,7 +169,7 @@ test('★ 鸿蒙树里不许用 toISOString 取日期键(今天标错格的直 const f = join(dir, e.name); if (e.isDirectory()) { scan(f); continue; } if (!/\.(ets|ts)$/.test(e.name)) continue; - code(f).split('\n').forEach((line, i) => { + stripStrings(code(f)).split('\n').forEach((line, i) => { if (/toISOString\s*\(\s*\)\s*\.\s*slice\(\s*0\s*,\s*10\s*\)/.test(line)) { hits.push(`${f.slice(ETS.length + 1)}:${i + 1} ${line.trim()}`); } @@ -165,3 +182,31 @@ test('★ 鸿蒙树里不许用 toISOString 取日期键(今天标错格的直 `"今天"就标到上一格。**正确修法**:页面侧用 model/Calendar.ts 的 isoOfLocal(new Date())。` + `**最常见的错误修法**:把断言里的期望值改成 UTC 日期(那是把判据废掉)。\n ${hits.join('\n ')}`); }); + +/* + * ★ "今天"必须在显示时求值 —— 日历是**常驻 pane**(pi 2026-09-14 §1)。 + * + * 顶层导航是「通信 / 日历 / 联系人」三个平级 pane,日历长时间挂着;`today` 是**唯一随时间变** + * 的输入。建页时取一次存进状态 ⇒ 跨过午夜后标记停在昨天,而**所有判据全绿** + * (模型没错,是喂进去的值过期了)。这与 `Calendar.ts` 里防的"上次渲染残留"是同一件事, + * 只是残留发生在**调用侧**。 + * + * 纯逻辑侧能钉的是:同一次输入给同一结果、不同输入各自独立、**不许跨调用残留**。 + * 调用侧义务(pane 变可见时重算)登记在 docs/DEBTS.json 的 `calendar-today-recompute`。 + */ +test('★ today 不许跨调用残留:连续两次不同输入各自独立,且推进一天后值必须变', () => { + const mark = g => g.flat().filter(c => c.isToday).map(c => c.iso); + assert.deepEqual(mark(C.monthGrid(2026, 9, 1, '2026-09-14')), ['2026-09-14']); + assert.deepEqual(mark(C.monthGrid(2026, 9, 1, '2026-09-15')), ['2026-09-15'], + '第二次调用必须标 15 日 —— 不许留着上一次的值'); + + const t0 = new Date('2026-09-14T23:59:00Z'); + const t1 = new Date(t0.getTime() + 86400000); + const d0 = C.isoAtOffset(t0, 480); + const d1 = C.isoAtOffset(t1, 480); + assert.notEqual(d0, d1, '跨过零点的两个时刻必须给出不同的今天(否则页面重算也没用)'); + assert.deepEqual(mark(C.monthGrid(2026, 9, 1, d0)), [d0]); + assert.deepEqual(mark(C.monthGrid(2026, 9, 1, d1)), [d1]); + assert.equal(mark(C.monthGrid(2026, 9, 1)).length, 0, + '不传就不许有今天(不许内部兜底取系统时间 —— 那正是"取一次"的写法)'); +}); diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index 4b82cc9..a6cc36d 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -77,7 +77,7 @@ const SUITE = [ ['test/appearance-defaults.test.mjs', [], 4], ['test/build-stamp.test.mjs', [], 7], ['test/packaging.test.mjs', [], 5], - ['test/harmony-calendar.test.mjs', ['--experimental-strip-types', '--no-warnings'], 9], + ['test/harmony-calendar.test.mjs', ['--experimental-strip-types', '--no-warnings'], 10], ['test/debt-visibility.test.mjs', [], 1], ['test/commit-hygiene.test.mjs', ['--experimental-strip-types', '--no-warnings'], 2], // 判据目录自身的卫生:读文本必须走 test/lib/read.mjs 的具名入口 diff --git a/docs/DEBTS.json b/docs/DEBTS.json index 6599090..08cba1f 100644 --- a/docs/DEBTS.json +++ b/docs/DEBTS.json @@ -69,6 +69,13 @@ "due": "**由外部读者报告时**(自查机制对这一类结构性失明 —— 发现词表外说法的机制,正是看不见它的那个机制)。收到报告后:扩词表 + 登记该处 + 保留\"上一次是谁发现的\"。**没有内部触发器,这是这条递归的不动点**:无论词表多长、判据多严,总有一类盲区只能靠\"外面有人读了一遍\"", "where": "client/electron/test/debt-visibility.test.mjs(词表键控的盲区:**已知未覆盖**——词表是采样、不是完备)", "kind": "env" + }, + { + "id": "calendar-today-recompute", + "count": 1, + "kind": "scope", + "due": "**日历页面骨架写完后立刻**:`today` 必须在 pane **变为可见时**重算(Tabs 切换回调 / 页面可见回调,API 以 ArkTS 规范为准),**不许**只在 aboutToAppear 取一次。判据形式:同一页面实例推进一天后重新求值,`today` 必须跟着变", + "where": "client/harmony/entry/src/main/ets/model/Calendar.ts 的纯逻辑判据(test/harmony-calendar.test.mjs)只钉到「喂进去的值对」;**喂进去的值会不会过期是调用侧的事**" } ] }