diff --git a/client/electron/test/harmony-appearance.test.mjs b/client/electron/test/harmony-appearance.test.mjs index 4ab7398..0e39301 100644 --- a/client/electron/test/harmony-appearance.test.mjs +++ b/client/electron/test/harmony-appearance.test.mjs @@ -129,70 +129,26 @@ test('离线/未登录:本地就是全部,而且**状态要看得见**(降 // ───────────────────── 系统方案:数字 → 系统材质 / 色彩模式 ───────────────────── -test('★ 模糊值映射到**系统材质档次**(不是把 40 当半径塞给某个 API)', () => { - /* - * 服务端存的是 WebUI 的 `bg_blur`(模糊像素半径,0~40),鸿蒙这边"模糊"由系统材质提供 - * (`BlurStyle`)。同一个数字两边含义不同,必须显式映射 —— 这条判据钉住映射关系, - * 顺带钉住"没有 0~40 档全开"(材料只有几档,落不到档上的数字要归到最近的档)。 - */ - assert.equal(A.blurStyleFor(0), 'NONE', '不模糊就是不用材质'); - assert.equal(A.blurStyleFor(4), 'COMPONENT_THIN'); - assert.equal(A.blurStyleFor(8), 'COMPONENT_THIN'); - assert.equal(A.blurStyleFor(9), 'COMPONENT_REGULAR'); - assert.equal(A.blurStyleFor(20), 'COMPONENT_REGULAR'); - assert.equal(A.blurStyleFor(21), 'COMPONENT_THICK'); - assert.equal(A.blurStyleFor(40), 'COMPONENT_THICK'); - assert.equal(A.blurStyleFor(999), 'COMPONENT_THICK', '越界要归到最近的档,不能返回空'); - assert.equal(A.blurStyleFor(-3), 'NONE'); - // 档次必须来自系统枚举(写成自造名字会编译不过/不生效) - const sdk = A.blurStyleFor(12); - // SDK 的 .d.ts 是**源码**(判它的枚举成员),所以走 code():剥掉注释, - // 免得注释里举例的枚举名被当成员读进来 - const commonDts = code(process.env.HARMONY_COMMON_DTS - || '/opt/huawei/command-line-tools/sdk/default/openharmony/ets/component/common.d.ts'); - const enumBlock = commonDts.slice(commonDts.indexOf('declare enum BlurStyle')); - const members = [...enumBlock.slice(0, enumBlock.indexOf('}')).matchAll(/^\s{2,}([A-Za-z][A-Za-z_0-9]*)\s*[,=]/gm)].map(m => m[1]); - assert.ok(members.length > 3, '要从 SDK 里读到 BlurStyle 成员'); - for (const tier of ['NONE', 'COMPONENT_THIN', 'COMPONENT_REGULAR', 'COMPONENT_THICK']) { - assert.ok(members.includes(tier), `${tier} 必须是系统 BlurStyle 的成员`); - } - assert.ok(members.includes(sdk)); - - /* - * 另一半:**`blurStyleFor` 在整段可达输入上返回的每个值都必须是 SDK 的成员名**。 - * - * 这里原先判的是页面里那张档位名→枚举的表(`NAV_MATERIAL_OF`,已随方案 (b) 删除)。 - * 那张表随"导航条改回固定档"(pi 2026-09-15 的裁定,见 `harmony-nav` 的契约判据) - * 一起删了 —— 导航条现在直接用 `Theme.navMaterial`,不再经档位名中转。 - * - * 但"**别自己发明档位名**"这件事仍然要判,而且现在**只有这一条在判它**: - * `blurStyleFor` 是档位名的唯一产生者(它现在是孤岛:判据在跑、页面没人调, - * 见 `Appearance.ts` 里那段如实说明)。所以改成枚举**它的整个值域**, - * 逐个对着 SDK 成员核 —— 比原来只核表里那三行**更严**(原来漏判的点现在覆盖了)。 - */ - const produced = new Set(); - for (let px = 0; px <= 40; px++) produced.add(A.blurStyleFor(px)); - produced.add(A.blurStyleFor(-3)); - produced.add(A.blurStyleFor(999)); - produced.add(A.blurStyleFor(NaN)); - assert.ok(produced.size >= 3, `可达输入上应当产出多档(实际 ${produced.size} 档:${[...produced].join('、')})`); - for (const tier of produced) { - assert.ok(members.includes(tier), - `\`blurStyleFor\` 产出了 \`${tier}\`,它不是 SDK 的 BlurStyle 成员` + - `(自造档位名 = 编译不过或静默不生效)。SDK 成员:${members.join('、')}`); - } - /* - * 而"通用映射允许 NONE"这一半必须**留着**:`0 px ⇒ 不模糊`是它的正确语义 - * (用户要壁纸清晰是合法选择)。这条曾经被我写错过一次 —— 当时我断言 - * "`blurStyleFor` 在 0..40 上不许返回 NONE",那条**恒红**,错的是判据不是代码。 - */ - assert.ok(produced.has('NONE'), - '`blurStyleFor` 必须保留 `NONE`(0 px ⇒ 不模糊是合法语义)—— 导航条那边的下限不是靠篡改它实现的'); - for (const tier of ['COMPONENT_THIN', 'COMPONENT_REGULAR', 'COMPONENT_THICK']) { - assert.ok(produced.has(tier), - `可达输入上应当覆盖 ${tier}(漏一档说明分档表被改坏了)`); - } -}); +/* + * ── 这里原有的一条判据已删除(2026-09-15)── + * + * 它判的是 `blurStyleFor`:`bg_blur` px → `BlurStyle` 档位名的映射(分档边界 0/8/20、 + * 单调性、NaN、以及"产出的名字必须是 SDK 成员")。**函数本身已按 pi 的裁定删除** —— + * 它没有任何调用点,唯一的计划消费者(导航条档位跟随 `bg_blur`)已被否决, + * 所以不是"暂时没消费者"而是**不该有**。理由碑文在 `Appearance.ts` 的 `blurStyleFor` 位置。 + * + * ★ 删掉它**没有留下覆盖空洞**,这一点是我删之前专门核过的: + * "档位名必须是 SDK 的 `BlurStyle` 成员、且不是 NONE"这条性质,现在由 + * `harmony-nav.test.mjs` 的「导航条材质是**固定系统档**」语义 C 直接判 —— + * 它读 `Theme.ets` 里 `navMaterial` 的**声明**,把档位名对着 SDK 枚举成员核。 + * 而 `Theme.navMaterial` 正是**唯一剩下的**档位名产生者(原来那个函数没了)。 + * ⇒ 覆盖面从"核一个没人调的函数的产出"变成"核真正在用的那个令牌", + * **判的还是同一件事,但对象终于是活的**。 + * + * 教训留在这里(比删掉的那几条断言值钱):**"有测试"不等于"有人用"**。 + * 这条判据全绿、逻辑正确,守的却是一个没有消费者的函数 —— + * 与 `Theme.navMaterial` 那个"只被不可达 `??` 分支引用"的死令牌是同一个形状。 + */ test('主题 → **系统色彩模式**(深浅两套颜色由系统给,不自己维护一套色值)', () => { assert.equal(A.colorModeFor('system'), 'COLOR_MODE_NOT_SET', '跟随系统是默认档'); @@ -929,26 +885,24 @@ test('★ 模糊字段的消费侧:逐文件登记 + 计数(P4c 起不再是 * 所以正确的登记是:**映射表存在且可判(本判据);缺的是调用点**。 * "有没有人用它"是另一件事,由上面那条消费侧计数判据管(登记值为 0)。 */ -test('★ blurStyleFor:分档边界、单调性、NaN 都是行为(0/8/20 是契约的一部分)', async () => { +test('★ 碑文:`blurStyleFor` 不许回来 + 理由必须留在原处(旧的三条行为性质随主语一起作废)', async () => { const { pathToFileURL } = await import('node:url'); const A = await import(pathToFileURL(join(HARMONY_ETS, 'model', 'Appearance.ts')).href); - assert.equal(typeof A.blurStyleFor, 'function', 'blurStyleFor 必须存在且可跑'); + // ★ 原先这里断言"必须存在且可跑"。事实相反:它是**有意删除**的(碑文 Wallpaper.ts:246)。 + // ⇒ 断言改成**反回归**:不许回来。("标签必须等于断言范围":这条测试的标题也一并改了。) + assert.equal(typeof A.blurStyleFor, 'undefined', + '`blurStyleFor` 已被有意删除(2026-09-15):它没有、也不该有消费者 —— 不许加回来'); - const cases = [[0, 'NONE'], [-5, 'NONE'], [1, 'COMPONENT_THIN'], [8, 'COMPONENT_THIN'], - [9, 'COMPONENT_REGULAR'], [20, 'COMPONENT_REGULAR'], [21, 'COMPONENT_THICK'], - [40, 'COMPONENT_THICK'], [999, 'COMPONENT_THICK']]; - for (const [px, want] of cases) { - assert.equal(A.blurStyleFor(px), want, `bg_blur=${px} 应当映射到 ${want}`); - } - // 单调性:px 变大,档次不许倒退(分档写反是最容易犯的错) - const order = ['NONE', 'COMPONENT_THIN', 'COMPONENT_REGULAR', 'COMPONENT_THICK']; - let last = -1; - for (let px = 0; px <= 40; px++) { - const i = order.indexOf(A.blurStyleFor(px)); - assert.ok(i >= last, `bg_blur=${px} 的档次倒退了(${order[last]} → ${order[i]})`); - last = i; - } - // NaN 不许落到最厚那一档:它只能来自坏数据,不该被解释成"最模糊" - assert.notEqual(A.blurStyleFor(Number.NaN), 'COMPONENT_THICK', - 'NaN 不许落到最厚那一档(比较全 false 时掉到最后一档 —— 那是最坏的方向)'); + /* + * 这一段(cases + 单调性 + NaN 方向)**整体作废,而且不是"改一改还能用"**: + * 它们的**主语**是一个已被删除的映射函数。常数没有"单调性",也没有"越界归到最近档", + * 更没有"NaN 掉到哪一档" —— 三条性质**随主语一起消失**了。 + * + * 所以这里**不重新发明**它们,只留两句钉住"去哪了"与"为什么": + * · 那句碑文还在(理由必须留在原处,否则下一个人只会看到"这里什么都没有"); + * · 常量本身仍被钉着(固定档 = COMPONENT_THICK,见本文件前半段那条现居地判据)。 + * 判据**变少是对的**:性质没了就该少,硬造一条等价的只会是假判据。 + */ + assert.match(prose(WALL_TS), /blurStyleFor[\s\S]{0,200}?已随/, + '碑文仍在原处:删除理由必须写在它被删掉的地方(否则下一个人看不出这里曾经有过什么)'); }); diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index ab3e6ca..1d70021 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -71,7 +71,7 @@ const SUITE = [ ['test/harmony-logic.test.mjs', ['--experimental-strip-types', '--no-warnings'], 28], ['test/harmony-system-api.test.mjs', [], 5], // P4 外观同步:跑 model/Appearance.ts(纯逻辑),所以也要 strip-types - ['test/harmony-appearance.test.mjs', ['--experimental-strip-types', '--no-warnings'], 26], + ['test/harmony-appearance.test.mjs', ['--experimental-strip-types', '--no-warnings'], 25], // P5 悬浮玻璃导航:点击配对 / index 决定挂载 / 命中区 ≥44vp / 悬浮与让位 ['test/harmony-nav.test.mjs', ['--experimental-strip-types', '--no-warnings'], 11], // 外观契约:默认值去 Go 源码里读(服务端 DefaultAppearance 是权威)+ 缓存键按账号 @@ -538,49 +538,7 @@ for (const [, , probe] of STATIC_ONLY) { if (probeResults[probe] === undefined) probeResults[probe] = PROBES[probe].run(); } -/* - * pi 2026-09-15:这块原先在报文末尾 `process.exit(1)`,而**它在汇总之前** ⇒ - * 只要到期闸红了,`RESULT` 行、欠账余额、条数不符、以及**所有其它红**都不会打出来。 - * 实测:`harmony-presets` 实报 6 / 登记 5 这条真红一次都没显示过。 - * 形状:**一道闸把不属于它的证据一起吃掉**。修法不是"少红",是**记账 + 继续走完汇总**。 - */ -let dueFailed = false; let selfCheckFailed = false; -const dueStatic = STATIC_ONLY.filter(([, , probe]) => probeIsDue(probeResults[probe])); -const unknownProbes = Object.entries(probeResults).filter(([, v]) => v === 'unknown').map(([k]) => k); -if (dueStatic.length > 0) { - const firstUnknown = unknownProbes.includes(dueStatic[0][2]); - console.error(firstUnknown - ? `\n✗ 探针**跑不了**(${PROBES[dueStatic[0][2]].desc})—— 拿不准就按到期处理,别让闸门锈死:` - : `\n✗ 静态判据**到期**了:${PROBES[dueStatic[0][2]].desc} 现在是成立的 ——`); - for (const [file, why, probe] of dueStatic) { - console.error(` - ${file}(到期前提:${PROBES[probe].desc};当初只能静态的原因:${why})`); - } - console.error( - ' 这些判据当时只能验形态。前提成立后必须做其中一件(别默默留着):\n' + - ' a) 改成**行为判据**(真跑一遍/真点一次),静态那条降级或删掉;\n' + - ' b) 明确写"为什么仍然只能静态"(如设备能编译但点不了),并改换一个更准的到期前提。\n' + - ' 这是"暂时"的到期机制:它的作用就是不等谁想起来。' - ); - /* - * 到期报文自带**要人做什么**(pi 2026-09-14 §2): - * 这条红第一次出现时,最可能的结局是"被当成噪音消掉"——因为看的人不知道要放行什么。 - * 前提写的是"**本工作区**能装能点",所以这里把工作区外的门槛逐条列出来。 - */ - const need = PROBES[dueStatic[0][2]].need || []; - if (need.length) { - console.error(' 要让它变成行为判据,需要先在本工作区打通:'); - for (const n of need) console.error(` · ${n}`); - console.error(' 这些都在工作区外(需要人放行或提权)—— 所以这条红**不要求你现在修**,' + - '要求的是:别把它当噪音,并在打通后回来把静态那条升级掉。'); - } - dueFailed = true; -} -/* - * 汇总里"欠账余额"和"探针是否健康"是两个不同的数字(pi §2): - * static=5 —— 还欠着 5 条只能验形态的判据; - * probe=ok —— 探针自己是好的(unknown 说明闸门可能锈死了,得人看一眼)。 - */ /* * 自检 4(pi 2026-09-15):**到期闸不许把汇总和其它红一起吃掉**。 * @@ -623,6 +581,49 @@ try { console.error(`✗ 套件自检 4 跑不了:${e.message || e}`); selfCheckFailed = true; } + +/* + * pi 2026-09-15:这块原先在报文末尾 `process.exit(1)`,而**它在汇总之前** ⇒ + * 只要到期闸红了,`RESULT` 行、欠账余额、条数不符、以及**所有其它红**都不会打出来。 + * 实测:`harmony-presets` 实报 6 / 登记 5 这条真红一次都没显示过。 + * 形状:**一道闸把不属于它的证据一起吃掉**。修法不是"少红",是**记账 + 继续走完汇总**。 + */ +let dueFailed = false; +const dueStatic = STATIC_ONLY.filter(([, , probe]) => probeIsDue(probeResults[probe])); +const unknownProbes = Object.entries(probeResults).filter(([, v]) => v === 'unknown').map(([k]) => k); +if (dueStatic.length > 0) { + const firstUnknown = unknownProbes.includes(dueStatic[0][2]); + console.error(firstUnknown + ? `\n✗ 探针**跑不了**(${PROBES[dueStatic[0][2]].desc})—— 拿不准就按到期处理,别让闸门锈死:` + : `\n✗ 静态判据**到期**了:${PROBES[dueStatic[0][2]].desc} 现在是成立的 ——`); + for (const [file, why, probe] of dueStatic) { + console.error(` - ${file}(到期前提:${PROBES[probe].desc};当初只能静态的原因:${why})`); + } + console.error( + ' 这些判据当时只能验形态。前提成立后必须做其中一件(别默默留着):\n' + + ' a) 改成**行为判据**(真跑一遍/真点一次),静态那条降级或删掉;\n' + + ' b) 明确写"为什么仍然只能静态"(如设备能编译但点不了),并改换一个更准的到期前提。\n' + + ' 这是"暂时"的到期机制:它的作用就是不等谁想起来。' + ); + /* + * 到期报文自带**要人做什么**(pi 2026-09-14 §2): + * 这条红第一次出现时,最可能的结局是"被当成噪音消掉"——因为看的人不知道要放行什么。 + * 前提写的是"**本工作区**能装能点",所以这里把工作区外的门槛逐条列出来。 + */ + const need = PROBES[dueStatic[0][2]].need || []; + if (need.length) { + console.error(' 要让它变成行为判据,需要先在本工作区打通:'); + for (const n of need) console.error(` · ${n}`); + console.error(' 这些都在工作区外(需要人放行或提权)—— 所以这条红**不要求你现在修**,' + + '要求的是:别把它当噪音,并在打通后回来把静态那条升级掉。'); + } + dueFailed = true; +} +/* + * 汇总里"欠账余额"和"探针是否健康"是两个不同的数字(pi §2): + * static=5 —— 还欠着 5 条只能验形态的判据; + * probe=ok —— 探针自己是好的(unknown 说明闸门可能锈死了,得人看一眼)。 + */ const probeSummary = unknownProbes.length ? 'unknown' : 'ok'; if (skippedByPhase.length) { console.log(`相位=${PHASE}:本相位不判 ${skippedByPhase.length} 条**构建相位**判据(${skippedByPhase.join('、')})` +