/* * 预设的**行为**判据:每一档都真的画得出来。 * * 为什么需要它(pi 2026-09-14 指出):`appearance-defaults.test.mjs` 那条 * "鸿蒙 `PRESET_IDS` 与 WebUI `PRESETS` 的 id 与顺序逐项一致" 只挡得住**一半** —— * 它挡"清单漏一档",**挡不住"清单有、渲染器没有"**:将来两边都加了第 7 档、 * 却忘了给 `layersFor` 加分支,那条判据**全绿**,而用户看到的是"无背景" * ——正是"信息对等缺口"的同一个症状,只是路径换了。 * * 所以这里是**两条配对**:值判据管"清单对不对",行为判据管"每一档真的画得出来"。 * 这与命中区那处(数值在 `NavItems.ts`、判据直接 import)是同一个配对方式。 * * **能真跑**,不是形态判据:`model/Wallpaper.ts` 是纯逻辑(无 `@ohos` 依赖、 * 类型可擦除),用 node 的 `--experimental-strip-types` 直接执行它 —— 与 * `harmony-logic.test.mjs` 同一个先例。因此它**不需要设备**, * 也**不该**登记进 `static` 欠账(那会虚增余额)。 */ import { join } from 'node:path'; import { test } from 'node:test'; import assert from 'node:assert/strict'; import { pathToFileURL } from 'node:url'; const ROOT = '/home/program/agentmail'; const WALLPAPER_TS = join(ROOT, 'client/harmony/entry/src/main/ets/model/Wallpaper.ts'); // 直接执行 `.ets` 树里的**纯逻辑** `.ts`(有 `@ohos` 依赖的文件不能这样跑) const W = await import(pathToFileURL(WALLPAPER_TS).href); const IDS = W.PRESET_IDS; const THEMES = [false, true]; const themeName = d => (d ? '深色' : '浅色'); /** 层栈的稳定序列化(只取有意义字段,便于比较与报错) */ const shapeOf = layers => JSON.stringify(layers.map(l => ({ kind: l.kind, cx: l.cx, cy: l.cy, radius: l.radius, angle: l.angle, colors: l.colors, stops: l.stops, lineColor: l.lineColor, lineAlpha: l.lineAlpha, step: l.step }))); test('六档 × 两种主题:都画得出来(不抛异常、非空、每层字段有效)', () => { assert.ok(Array.isArray(IDS) && IDS.length >= 6, `预设清单要能取到(实际 ${IDS?.length})`); for (const id of IDS) { for (const dark of THEMES) { let layers; assert.doesNotThrow(() => { layers = W.layersFor(id, dark); }, `${id}(${themeName(dark)})调用就抛了 —— 用户看到的是"无背景"`); assert.ok(Array.isArray(layers) && layers.length >= 1, `${id}(${themeName(dark)})返回空层栈 ⇒ 这一档画出来是空白`); for (const [i, l] of layers.entries()) { assert.ok(['radial', 'linear', 'grid'].includes(l.kind), `${id}(${themeName(dark)})第 ${i} 层的 kind 认不出:${l.kind}`); if (l.kind === 'grid') { assert.ok(typeof l.step === 'number' && l.step > 0, `${id} 网格档缺 step`); assert.ok(typeof l.lineColor === 'string' && l.lineColor.length > 0, `${id} 网格档缺线色`); } else { assert.ok(Array.isArray(l.colors) && l.colors.length >= 2 && l.colors.every(c => typeof c === 'string' && c.length > 0), `${id}(${themeName(dark)})第 ${i} 层的色标不完整:${JSON.stringify(l.colors)}`); } } } } }); /* * ★ 这条是 pi 那半边的直接对应物:**"清单里有、渲染器里没有"必须红。** * * 手法:拿一个**不存在的 id** 调一次 —— `normalizePreset` 会把它落到默认档 * (aurora),于是"兜底长什么样"就有了可比较的对象。任何一档如果**丢了分支**, * 它会静默落到同一个兜底上 ⇒ 与兜底逐字节相同 ⇒ 红。 * * **已知边界(写出来,不假装全覆盖)**:`aurora` 自己**就是**那个兜底档, * 所以它"丢了分支"在行为上不可观测(结果一样)。这条判据能测到的是 * **另外五档**;aurora 的存在性由值判据(清单相等)与下面那条"六档两两不同"兜住。 */ test('★ 清单里有、渲染器里没有 → 红(每一档都必须与"兜底档"不同)', () => { for (const dark of THEMES) { const fallback = shapeOf(W.layersFor('__这个 id 不存在__', dark)); const aurora = shapeOf(W.layersFor('aurora', dark)); assert.equal(fallback, aurora, `自检:认不出的 id 应当落到默认档 aurora(${themeName(dark)})`); for (const id of IDS) { if (id === 'aurora') continue; // 见上面的"已知边界" assert.notEqual(shapeOf(W.layersFor(id, dark)), fallback, `${id}(${themeName(dark)})画出来的和"兜底档"一模一样 ⇒ 它没有自己的渲染分支` + `(清单里有、渲染器没有:用户看到的是 aurora,不是他选的那档)`); } } }); test('六档两两不同(复制粘贴分支没改参数 → 红)', () => { for (const dark of THEMES) { const seen = new Map(); for (const id of IDS) { const s = shapeOf(W.layersFor(id, dark)); assert.ok(!seen.has(s), `${id} 与 ${seen.get(s)} 在${themeName(dark)}下画出来完全相同 —— 复制分支后忘了改参数`); seen.set(s, id); } } }); test('深色档必须与浅色档不同("深色没换色" → 红)', () => { for (const id of IDS) { assert.notEqual(shapeOf(W.layersFor(id, true)), shapeOf(W.layersFor(id, false)), `${id} 的深色档与浅色档完全一样 ⇒ 深色模式下这一档没有换色`); } }); /* * ★ 静默兜底必须留下**可观测的痕迹**(pi 2026-09-14)。 * * §10 登记了"未知 id 静默换成 aurora"这条缺省语义,但**登记只防"被误报成 bug"**, * 防不住"没人知道它正在发生" —— 真实环境里它不留任何痕迹。所以返回值里带 * `presetSubstitutedFrom`:换过就是原来那个 id,没换过是空串,页面据此打一行日志。 * * 这条判据同时钉住两件事:① 替换**必须**留痕;② 没替换时**不许**留痕 * (否则痕迹会退化成噪声,等于没有)。 */ test('★ 静默兜底留痕:换过要带出原 id,没换过必须是空串', () => { const unknown = 'ocean-v2-这个档还不存在'; const bad = W.resolveBackground('preset', unknown, 0.2, false, false); assert.equal(bad.presetId, 'aurora', `自检:未知 id 应当被换成默认档(实际 ${bad.presetId})`); assert.equal(bad.presetSubstitutedFrom, unknown, '认不出的 id 被静默替换了,却没留下痕迹 ⇒ 版本偏移时无法确认它在发生(只能猜)'); for (const id of IDS) { for (const dark of THEMES) { const plan = W.resolveBackground('preset', id, 0.2, false, dark); assert.equal(plan.presetSubstitutedFrom, '', `${id}(${themeName(dark)})是已知档,不该留下"替换过"的痕迹(痕迹变成噪声就等于没有)`); } } });