Files
MailUI4Agents/client/electron/test/CRITERIA.md
JianFeeeee 0ce8b29546 test(criteria): 判据规范 CRITERIA.md(邻接不是结构·第三次露头)+ 废弃 API allow-list 结构化 + 扫描范围自检
pi 的两条增量,都不需要他再确认。

## 1(pi 建议):生成清单 + 空 allow-list 的结构性风险

他的推演:SDK 升版会往清单里加新条目 → 某天早上套件**突然红**,且红在与本次改动无关的
代码上;这时人的第一反应是把名字塞进 allow-list —— 而 allow-list 一旦这么用,
就不再是"研究过的例外",只是"红的止痛药"。所以条目结构化:

```js
const ALLOW = [ /* { name, replacement, why } */ ];
```

`replacement` 非空是硬断言("暂时不想改"不是放行理由,"替代品要求的 API level 高于基线"才是)。
**刻意不断言"名单必须为空"** —— 那会挡住合理放行;断言的是"有名字、没替代品 → 红",
于是侵蚀发生时红的是**放行这件事本身**,而不是某天的新 SDK。
变异:塞一条 `{ name:'px2vp', replacement:'', why:'暂时不想改' }` → 红。

## 2(pi 建议):扫目录的判据要防"空判据"

他问废弃 API 判据扫哪些目录(怕只扫 `pages/`,`common/` 里的旧写法逃掉)。
答案:扫的是**整个 ets 目录递归**(实测 25 个文件,含 `pages/ common/ model/ api/ entryability/`)。
顺手加了防退化的自检:文件数 ≥ 20,且 `pages/ common/ model/ api/` 四个目录都必须扫到
—— 目录改名/只扫一个子目录会让这条变成空判据而依然全绿。
变异:把扫描范围改成只扫 `pages/` → 红。

## 3(pi 建议):把"邻接不是结构"写进判据规范

同一个坑在本仓露头三次:① 窗口式正则被一行注释挤爆(原注释自嘲过);
② 括号配对取代窗口;③ 链式修饰符让"看前一个字符是不是 `}`"静默失效。
共同形式值得升格成规则,于是新建 `client/electron/test/CRITERIA.md`(七条),
并在 `run-all.mjs` 加**自检 3**:规范文件必须在、且必须点到关键条目。
WebUI 侧 `background.test.mjs` 的窗口式存量按 pi 的说明**记着不动**(那是他的地盘)。

规范里另外两条是本仓自己踩出来的:剥注释读代码 vs 读原文读理由(混用必红);
以及**验证要按真实入口跑** —— 我用 `node --test test/run-all.mjs` 验自检 3 时它"依然绿",
其实是 runner 把内部的 `process.exit(1)` 吞了;换成 `npm test` 走的那一行就红对了。
变异:删掉规范里的一条关键规则 → `npm test` 那条路 exit 1。

## 验证

`npm test` 退出码 0(11 个判据文件全绿 + vitest 258/258);`hvigorw assembleHap` 未受影响。
2026-09-14 14:51:30 +08:00

92 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 判据规范(写判据前先读这份)
这份文件记的是**判据本身的写法**:什么样的判据能红、能红在对的地方、以及不会在"代码完全正确"时乱红。
不是"怎么用 node:test",而是这个仓库里踩出来的形状。
一句话版本:**判据要钉结构与行为,不要钉字面与邻接。**
---
## 1. 判结构必须配对/解析;看邻接或固定宽度都会被合法写法绕过
统一形式(这是同一个坑在本仓露头的**第三次**,所以升格成规则):
> **判结构要配对/解析括号、块、AST看"前一个字符"或"固定宽度窗口"都会被合法写法绕过。**
三次露头:
| # | 当时的写法 | 被什么合法写法绕过 |
|---|---|---|
| ① | 固定宽度窗口正则 | 往规则里加一行注释就把 `backdrop-filter` 挤出窗口 —— 断言变红而 CSS 完全正确(`background.test.mjs` 的注释里记着这件事) |
| ② | 括号配对取代窗口 | 修掉了 ①,方向对 |
| ③ | "调用点往前看一个字符是不是 `}`" | ArkUI 的**链式修饰符**`Row(){…}.backgroundColor(x).backgroundBlurStyle(A)` 里调用点前面是 `)` 不是 `}` → 判据**静默失效**(拿到 null 就放过去了) |
③ 的修法:往回扫时**跳过成对的括号组**再找 `}`。它同时暴露了"叠"有两种形态这件事
(同一组件叠两次 / 套在另一层玻璃的子树里),所以 §2 的形状在这些规则里反复出现。
**推论**:同一条规则里出现 `[\s\S]{0,N}` 时先问一句"N 是怎么来的"。答不出数字来源的,
就是窗口式判据,换成"取出这条规则的 `{...}` 体再断言"。
**已知存量**pi 2026-09-14 点名,**WebUI 侧归他**,别人不要顺手改):
`client/electron/test/background.test.mjs` 里还有一批 `\{[\s\S]{0,80}…``{0,160}` 窗口。
下次碰那个文件时按本节形状改成"取规则体",不要在那里再加一条注释算了。
## 2. 合成的清单 + 空 allow-list条目要结构化
"清单从 SDK/参考实现生成 + allow-list 目前为空"是个好形状,但它**迟早被侵蚀**
上游升版会往清单里加新条目 → 某天早上套件突然红,且红在与本次改动无关的代码上。
这时人的第一反应是把名字塞进 allow-list —— 而 allow-list 一旦这么用,
就不再是"研究过的例外",只是"红的止痛药"。
所以条目写成三元组,并断言后两项非空:
```js
const ALLOW = [ /* { name, replacement, why } */ ];
```
- `replacement` 必须非空:**"暂时不想改"不是放行理由**"替代品要求的 API level 高于本机基线"才是;
- **不要断言"名单必须为空"**(那会挡住合理放行);要断言的是"有名字、没替代品 → 红"。
这样侵蚀发生时红的是**放行这件事本身**,而不是某天的新 SDK。
同一个形状的实例:`cross-client-theme.test.mjs``SELF_OWNED_COLORS`(手写色登记表,
每条带理由)、`GLASS_REGISTRY`(玻璃面登记,每条带"为什么这里要玻璃")。
## 3. 判据必须能红,而且红的地方要对
每条新判据配一次**变异验证**:把源码改成"错的样子",确认它红,并且红在那条上。
变异没红有两种可能,都要查清:一是判据没覆盖,二是**变异没真的生效**
(本仓真发生过:变异脚本的锚点不匹配、缩进不对,于是"变异后依然全绿"被当成判据有效)。
顺带:变异后**不要用 `git checkout` 还原**(会连同未提交的改动一起抹掉)。
`cp` 到备份,再从事先的备份还原。
## 4. 读源码断言的两种模式,别混用
- 断**代码行为**:读**剥掉注释**的源码(注释里出现的调用不是调用);
- 断**"理由写清了没"**:读**原文**(理由就在注释里)。
本仓真踩过:用剥离注释的读取去断"注释里写了为什么用 Canvas",永远红。
## 5. 取片段按行/按块,别用偏移算术两头夹
`src.indexOf('build() {')` 会撞上文件里更早的同名成员;偏移差一个字符会把最后一行拦腰截断
(现象是"这行只剩 57 个字符"这种看着像文案、其实像切片的怪事)。
取"某个成员的正文"要按行扫到下一个同级成员,并加一条自检断言切片没跨到别的成员上。
## 6. 判据要接线,扫描范围要有下限
- 新增判据文件必须进 `test/run-all.mjs``SUITE`run-all 的**自检 2** 会直接红);
- 扫目录的判据要断言"**至少扫到 N 个文件**",并列出必须包含的子目录
`pages/` `common/` `model/` `api/` …)—— 目录改名/只扫一个子目录会让判据变成
**空判据而依然全绿**
- **验证要按真实入口跑**`npm test` 走的是 `node test/run-all.mjs && vitest run`
`node --test test/run-all.mjs` 去验时runner 只报"0 个测试"、**把 runner 内部的
`process.exit(1)` 吞掉**,于是"变异后依然 exit 0"看起来像判据失效(本仓刚踩过这一次:
自检 3 其实是好的,是我用错了入口去验它)。**验判据要模拟用户/CI 真正跑的那一行。**
## 7. 判据要钉用户真正会点的那一层
(移交信里交代的头号纪律)判据通过了但用户点不到,等于没做。
所以断言尽量落在"用户会触发的那个入口/那条路径"上:
例如"点同意/拒绝后列表要变"要钉到那条链路上,而不是钉"某函数存在"。