test(harmony): 判据按 pi 复核意见补强 —— 手写色登记表、玻璃叠用形状、按标题取小节、遮罩必须被用、废弃 API 清单从 SDK 生成

pi 逐行读了 `cross-client-theme.test.mjs` 与 `Theme.ets` 后指出五处(第一处是真缺口),
外加一条建议。全部处理,并且**每一处都用变异验证过**。

## 一(真缺口):枚举 11 个"必须是系统资源"的名字,挡不住第 12 个新写死的手写色

`static readonly brandSecondary: string = '#123456'` 这种新增**三条判据都碰不到**:
A(不在名单里)、B(六位、不是半透明)、裸色值那条(只管 `pages/`)。
原理与当初 14 处 Google 色逃出去是同一条 —— **枚举挡实例,类才挡漂移**,
只是这次枚举的单位是**名字**。补 `A2` 条:

- 枚举 `Theme.ets` 里所有 `static readonly X: string = '#……'`,未登记的 → 红;
- 名单里已不存在的名字 → 红(名单不能烂成化石);
- `Theme.ets` 里的「手写色登记表」段必须逐个列出这些名字(理由不能只存在于记忆里);
- 自检:把一个**新的**手写色塞进源码字符串,确认这条抓得到。
  `Theme.ets` 因此新增登记表(17 项,每项一行理由,按品牌 / 业务语义 / 档位胶囊分组)。

变异:Theme.ets 加 `brandSecondary` → 红。

## 二:C 的"玻璃只有一处"**说错了自己断言的东西**

`assert.equal(glassCalls.length, 1)` 断言的是"全仓共一处",**不是**"没有嵌套":
同页两个**并列**玻璃面(没问题)会让它红,真嵌套它没在判。P5 正是悬浮玻璃导航,
到时若把 1 改成 2 就等于不判。改成形状判据:

- 收集每个调用点所作用的**组件块**(按括号配对回溯;注意 ArkUI 修饰符是链式的,
  `X.blur(A).blur(B)` 前面是 `)` 不是 `}` —— 第一版只看一个字符,对这种写法**静默失效**,
  是判据自检抓出来的);
- 判两种叠法:同一组件上叠多次(同一块)、以及套在另一层玻璃的子树里;
- 每一处玻璃都要在 `GLASS_REGISTRY` 里登记(附一句为什么),名单里的位置若已不存在 → 红。

变异:链式叠两层 → 红;通信页多开一处未登记玻璃 → 红;导航条块内嵌玻璃 → 红。

## 三:文档小节用 `indexOf('有意差异')` 会**拿错段落**

别的段落正文里出现这四个字,切片就从那里开始,后面所有断言都在**别的段落**上判。
改成**按标题**定位(正则匹配 `^#{2,4}…有意差异…$`),并加了表头列名断言
(WebUI / 鸿蒙 / 为什么)—— 拿错段落时表头不会是这个形状,于是它自己会红。

顺带修掉一个**我自己的**同类毛病:品牌色那行原来用"关键词 + 80 字符窗口"判,
窗口宽度在赌表格单元格字符数(该行两格之和 > 80)。改成**按行取**那一行再断言。
另外把偏移算术的切片换成**按行**切片(偏移差一个字符就会把最后一行拦腰截断,
现象是"品牌色那行只剩 57 字符"这种看着像文案、其实像切片的怪事)。

变异:文件前面插入含「有意差异」的段落 → 仍绿(按标题定位生效);
再改坏真表里的品牌色行 → 红(确实读的是那一张表)。

## 四:`Theme.overlay` 只判了"存在",没判"被用"

没使用点的令牌是自证。补断言:它必须在 `Theme.ets` **之外**有真实使用点
(实测 `SettingsPage.ets` / `MailDetailPage.ets` 两处自绘弹层在用),
并把 `overlayColor`/`overlayAlpha` 消失的理由写进 §7.12 —— 否则下一个人会当成漏改补回来。

## 五:材质档次是这次替换里唯一"判据绿但可能观感错"的地方

`COMPONENT_THICK` 的依据(对 THIN / BACKGROUND_* / ULTRA_THICK 的取舍)写进 §7.12,
并把"材质档次在导航条上的实际观感"列为**模拟器起来后第一个要看的项**(间距是数字,材质是判断)。

## 六(建议):废弃 API 判据**类化** —— 清单从 SDK 生成

原来是手写"不得再用全局 `promptAction.showToast`"(只挡已踩过的那个)。
现在从 SDK 生成:顶层(花括号深度 0)被标 `@deprecated` 的 `declare function`
—— 实测 68 个名字(含 `animateTo` / `getContext` / `px2vp`),配一份**空**的 allow-list。
深度判定是必要的:`declare namespace fileIo { declare function open() }` 里的 `open`
是命名空间成员,算进来会造一堆假红。只算全局调用(排除 `x.name(`)。

变异:调 `px2vp(10)` → 红;`this.px2vp(10)`(成员调用)→ 绿(假阳性自检)。

## 验证

`hvigorw assembleHap` BUILD SUCCESSFUL;`npm test` 退出码 0
(11 个判据文件全绿 + vitest 258/258)。cross-client-theme 13 → 14 条,harmony-system-api 4 → 5 条。
This commit is contained in:
2026-09-14 14:26:45 +08:00
parent 43aca575c7
commit a6b5e52f45
4 changed files with 418 additions and 16 deletions

View File

@ -78,6 +78,48 @@ export class Theme {
/** 控件圆角:系统"按钮"圆角 */
static readonly radiusControl: Resource = $r('sys.float.ohos_id_corner_radius_button');
/*
* ─────────────── 手写色**登记表**(改这里要一起改判据) ───────────────
*
* 为什么需要这张表:本文件里"自己写的色值"是有理由的(品牌身份 + 系统没有对应物的
* 业务语义色),但**理由不能靠记忆**。判据(`cross-client-theme.test.mjs` 的
* A2 条)会枚举本文件里所有 `static readonly X: string = '#……'`
* 发现**未登记的名字就判红** —— 于是"新写死一个色"必须显式过一道:
* 要么登记在这里并写清理由,要么它本来就该走 `$r('sys.*')`。
*
* 这条是 pi 读出来的真缺口:只枚举 11 个"必须是系统资源"的名字,挡不住
* 第 12 个**新加的手写色**(它不在名单里,于是 A/B/裸色值三条都碰不到它)。
* 「枚举挡实例,类才挡漂移」—— 这次枚举的是**名字**,所以要有名单。
*
* 登记项17 个,名字与判据里的 SELF_OWNED_COLORS 逐字一致 —— 逐个列出而不是缩写,
* 这样 grep 一个名字就能找到它的理由):
*
* 品牌(跨客户端身份,系统给不了):
* · accent #2563EB 与 WebUI 的 --c-blue-600 逐字一致
* · accentFg #FFFFFF 品牌底上的文字
* · accentSoft #EFF6FF 品牌浅底(选中态背景)
* · accentStrong #1D4ED8 品牌深色变体(选中文字/边框)
*
* 业务语义(系统只有 list/warning/alert 这一档,没有"同意/拒绝"
* · approve #15803D 同意(绿)
* · approveBg #F0FDF4 同意底
* · approveFg #15803D 同意文字
* · danger #B91C1C 拒绝/危险(红)
* · dangerBg #FEF2F2 拒绝底
* · warnBg #FFFBEB 警示底(对应 WebUI --c-amber-50
* · warnFg #B45309 警示文字(对应 WebUI --c-amber-700
*
* 档位胶囊(档位是产品语义:系统不认识 plan / workspace / full
* · chipNeutralBg #F3F4F6 plan 档底(最宽档但不报警)
* · chipNeutralFg #5A6270 plan 档文字
* · chipSpentBg #FEE2E2 预算耗尽底
* · chipSpentFg #B91C1C 预算耗尽文字
* · chipWarnBg #FFEDD5 预算紧张底
* · chipWarnFg #C2410C 预算紧张文字
*
* 不在这里的手写色只有一种合法去处:`$r('sys.*')`(跟随系统/深色模式)。
*/
// ─────────────── 品牌色:跨客户端身份,必须自己写 ───────────────
/**