jianf:「鸿蒙也同步,但是鸿蒙要求用系统方案」。按 pi 对齐的形状(A/B/C + 品牌色防线)落地。
## 鸿蒙侧改了什么
- `Theme.ets` 的表面/文字/分隔/遮罩/圆角**来源换成系统**:
`pageBg→sys.color.ohos_id_color_background`、`surface→…_list_card_bg`、
`surfaceMuted→…_sub_background`、`border→…_list_separator`、三级文字 `→…_text_primary/secondary/tertiary`、
`overlay→…_mask_regular`、`radiusCard/Control→sys.float.ohos_id_corner_radius_card/button`。
于是这些维度自动跟随深色模式与无障碍设置 —— 这正是"手抄 WebUI 色值"做不到的事。
- **删掉手写玻璃** `#B8FFFFFF`/`#B80F172A`:那两个值等于"我们替系统猜了深色该怎么做"。
换成一个**档次**声明 `navMaterial: BlurStyle = BlurStyle.COMPONENT_THICK` + 导航条上的
`.backgroundBlurStyle(...)`;深浅两套颜色与模糊半径由系统按主题给。
遮罩的两段式(色 + 透明度)同样删掉:拆两段本就是为了"随主题换向",而这件事系统已经做了。
- **每项一张卡/气泡**:收件箱行、会话组头、联系人列表行改成卡片(圆角 + 卡片底色 + 行间距),
联系人列表那条贯通分隔线删除。
- 仍然自己写的只有两类:**品牌色**(`accent = #2563EB`,跨客户端身份)与**业务语义色**
(权限三档、预算三档 —— 系统只有 warning/alert 两个情绪色,凑不出三档,硬套会丢语义)。
## 判据:从"取值相同"改"意图相同"(两侧一起改)
- 品牌蓝**唯一保留取值钉**,并新增防线:不得退化成 `$r('sys.color.*')`
(系统强调色随主题/厂商皮肤变,"两个客户端是同一个产品"就靠不住了)。
- 圆角/材质/遮罩:改成"WebUI 自声明令牌 + 鸿蒙来自系统 + 差异被记录"(§7.12 有意差异表)。
- 新增 A(系统拥有的维度唯一来源是 `$r('sys.*')`,且不得退回 string/number)、
B(旧机制不得回来:`navBg*`、8 位半透明色、`rgb(`/`rgba(`、写死的 14/8)、
C(玻璃位置必须调 `backgroundBlurStyle` 且**只许一层**)。
- pi 指出的洞已补:裸色值判据原来只扫 `#RRGGBB(AA)`,抓不到 `rgba(`/`0xRRGGBBAA` ——
而这几种恰是"改用系统材质"时最容易混进来的形态。现在四种一起扫,且**先剥注释**
(注释里正当地引用旧写法不该被judged红)。
- pi 的 §5 建议也已落地:新增"版本库不得跟踪缓存/构建产物"判据(`.tmp/` 那次 554 个文件的事故判据化),
并放行 `server/internal/static/static/placeholder.html`(go:embed 落点的有意占位,删了 Go 侧编不过)。
## 变异验证(能红,且红在对的地方)
| 变异 | 结果 |
|---|---|
| 品牌蓝 → 系统强调色 | 红 3 条 |
| 手写玻璃 `navBgLight` 回来 | 红 2 条 |
| 导航改用写死半透明色、不调 `backgroundBlurStyle` | 红 2 条 |
| 卡片上再开一层模糊 | 红 1 条("玻璃应只出现在一处,实际 2 处") |
## 验证 / 未验
`hvigorw assembleHap` BUILD SUCCESSFUL;`npm test` 退出码 0
(10 个判据文件全绿:跨端 13 条、系统资源名 4 条、harmony-logic 19 条… + vitest 258/258)。
**未验**:观感(卡片间距、系统材质在导航条上的实际效果、深色模式)—— 需真机/模拟器;
模拟器要人在命令行跑一次 `harmony-emu start`。`sys.*` 名字全部对着 SDK 名表核过,且判据持续盯着。
337 lines
20 KiB
JavaScript
337 lines
20 KiB
JavaScript
/**
|
||
* 两个客户端必须用**同一份设计词表**。
|
||
*
|
||
* 用户下一步要求:「同步 ui 设计到客户端」。同步的第一件事不是把每个页面重画一遍,
|
||
* 而是两边共用同一套令牌(颜色/圆角/玻璃透明度)—— 否则每加一个页面就重抄一遍色值,
|
||
* 两个客户端会越走越远,而且这种漂移**没有任何判据会红**。
|
||
*
|
||
* 这里只断言"两边对同一件事的取值一致",不断言实现方式(WebUI 用 CSS 变量、
|
||
* 鸿蒙用 ArkTS 常量,本来就该不同)。
|
||
*/
|
||
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { readFileSync, readdirSync } from 'node:fs';
|
||
import { dirname, join } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||
const ROOT = join(HERE, '..', '..', '..');
|
||
const HARMONY_ETS = join(ROOT, 'client/harmony/entry/src/main/ets');
|
||
const web = readFileSync(join(ROOT, 'client/electron/src/index.css'), 'utf8');
|
||
const harmony = readFileSync(join(HARMONY_ETS, 'common/Theme.ets'), 'utf8');
|
||
|
||
/**
|
||
* 裸色值的**类**(不只 `#RRGGBB`):四种写法一起扫 ——
|
||
* `#RRGGBB(AA)`、`rgba(...)`、`0xRRGGBBAA`(`Color(0x…)` 与渐变数组都用它)。
|
||
* 注释先剥掉:注释里引用旧写法是常有的事,而"诚实的注释"不该把判据判红。
|
||
*/
|
||
const RAW_COLOR = /#[0-9A-Fa-f]{6,8}\b|\brgba?\s*\(|\b0x[0-9A-Fa-f]{6,8}\b/g;
|
||
const code = src => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
||
const rawColors = src => code(src).match(RAW_COLOR) || [];
|
||
|
||
/**
|
||
* 文档里的「有意差异」表(§7.12)。
|
||
*
|
||
* 这是**弱判据**用的材料:它防的是"改了做法没改记录"(下一个人会当成漏改),
|
||
* 不是"证明做法对"。所以跨端那几条判据的主体仍落在代码上
|
||
* (来源必须是系统资源 / 品牌色必须是那个值),文档只做第三只手。
|
||
*/
|
||
const plan = readFileSync(join(ROOT, 'docs/HARMONY-ALIGN-PLAN.md'), 'utf8');
|
||
const diffSection = () => {
|
||
const at = plan.indexOf('有意差异');
|
||
assert.ok(at > 0, '文档里应有「有意差异」表(§7.12)');
|
||
const rest = plan.slice(at);
|
||
const end = rest.search(/\n#{2,3} /);
|
||
return end === -1 ? rest : rest.slice(0, end);
|
||
};
|
||
|
||
/** 递归收集鸿蒙源码(.ets / .ts) */
|
||
const collectEts = (dir, acc = []) => {
|
||
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||
const full = join(dir, e.name);
|
||
if (e.isDirectory()) collectEts(full, acc);
|
||
else if (/\.(ets|ts)$/.test(e.name)) acc.push(full);
|
||
}
|
||
return acc;
|
||
};
|
||
|
||
const hex = h => h.toUpperCase();
|
||
|
||
test('品牌蓝一致', () => {
|
||
// WebUI: --c-blue-600 应该是 37 99 235 = #2563EB
|
||
const webBlue = web.match(/--c-blue-600:\s*(\d+)\s+(\d+)\s+(\d+)/);
|
||
assert.ok(webBlue, 'WebUI 要有 --c-blue-600');
|
||
const asHex = hex(
|
||
'#' +
|
||
[webBlue[1], webBlue[2], webBlue[3]]
|
||
.map(n => Number(n).toString(16).padStart(2, '0'))
|
||
.join('')
|
||
);
|
||
const harmonyBlue = harmony.match(/accent: string = '(#[0-9A-Fa-f]{6})'/);
|
||
assert.ok(harmonyBlue, '鸿蒙要有 accent');
|
||
assert.equal(hex(harmonyBlue[1]), asHex, `品牌蓝不一致:WebUI ${asHex} vs 鸿蒙 ${harmonyBlue[1]}`);
|
||
});
|
||
|
||
test('圆角:WebUI 有它自己的令牌,鸿蒙跟随系统 —— 差异被记录(不再钉"取值相同")', () => {
|
||
/*
|
||
* 这一条**改过口径**(2026-09-14,jianf 要求「鸿蒙用系统方案」,与 pi 对齐后改)。
|
||
*
|
||
* 旧口径钉的是"两边都 14px"。改用系统资源后它必然红 —— 而这次红是**预期**的:
|
||
* 圆角交给系统就意味着它**允许**与 WebUI 不同(系统圆角会随设备/主题/无障碍设置变)。
|
||
* 所以口径从"取值相同"换成"**意图相同**":
|
||
* ① WebUI 侧仍须自己声明一个圆角令牌(它没有系统可跟随);
|
||
* ② 鸿蒙侧圆角来自系统(`sys.float.*`);
|
||
* ③ 这条差异被**记录**在文档的"有意差异"表里 —— 否则下一个人会以为是漏改。
|
||
* 注意 ③ 是**弱判据**(防遗忘),不是"证明做法对"。主体是 ①②,它们落在代码上。
|
||
*/
|
||
assert.match(web, /--radius-card:\s*0\.875rem/, 'WebUI 侧要保留自己的圆角令牌');
|
||
assert.match(code(harmony), /radiusCard: Resource = \$r\('sys\.float\./, '鸿蒙卡片圆角要来自系统');
|
||
assert.match(diffSection(), /圆角/, '圆角是"有意差异",要写进文档的差异表(否则会被当成漏改)');
|
||
});
|
||
|
||
test('材质:WebUI 声明透明度令牌,鸿蒙用系统材质 —— 差异被记录(不再钉 0.72)', () => {
|
||
// 同上:旧口径钉"两边都 0.72"。鸿蒙改用 `BlurStyle` 后,"0.72 的白色"这件事
|
||
// 由系统按主题决定(深浅各一套),所以取值**允许**不同,只要求"材质来自系统"且差异被记录。
|
||
assert.match(web, /--nav-bg:\s*255 255 255 \/ 0\.72/, 'WebUI 侧要保留自己的导航底令牌');
|
||
assert.match(code(harmony), /navMaterial: BlurStyle = BlurStyle\./, '鸿蒙导航材质要来自系统');
|
||
assert.match(diffSection(), /材质/, '材质是"有意差异",要写进文档的差异表');
|
||
});
|
||
|
||
test('★ 判据自检:把鸿蒙的品牌蓝改成别的必须判红', () => {
|
||
const mutated = harmony.replace(/accent: string = '#2563EB'/, "accent: string = '#FF0000'");
|
||
const m = mutated.match(/accent: string = '(#[0-9A-Fa-f]{6})'/);
|
||
assert.notEqual(hex(m[1]), '#2563EB');
|
||
});
|
||
|
||
test('鸿蒙的令牌文件说明了与 WebUI 的对应关系(不是凭空一套)', () => {
|
||
assert.match(harmony, /与 WebUI 的令牌\*\*一一对应|对应 WebUI/);
|
||
});
|
||
|
||
test('★ 鸿蒙页面里不得出现任何裸色值(枚举挡不住漂移,"类"才能挡)', () => {
|
||
/*
|
||
* 上一版这条判据只列了 8 个旧色值(#1A73E8 / #333333…),于是判据全绿的
|
||
* 同时,pages/ 里还留着 14 处**另一套**写死的色:Google/Material 的
|
||
* #E8F0FE、#E8F5E9、#FFF3E0、#D93025 与 #777777/#555555/#444444/
|
||
* #cccccc/#aaaaaa、遮罩 #80000000、透明 #00000000。
|
||
* 枚举只能挡住"列出来的实例",挡不住漂移本身 —— 改成按类挡。
|
||
*
|
||
* ## 2026-09-14:按类挡得**挡全**(pi 指出的洞)
|
||
*
|
||
* 原来只扫 `#RRGGBB(AA)` —— 抓不到 `rgba(...)`、`Color(0x…)`、`0xRRGGBBAA` 这几种写法,
|
||
* 而"改用系统材质/颜色"的过程恰恰最容易混进这几种形态(`0xB8FFFFFF` 就是手写玻璃)。
|
||
* 现在四种形态一起扫,并且**先剥注释**再扫:注释里正当地引用旧写法是常有的事
|
||
* (本文件上面两段注释里就各有一个 #B8FFFFFF),不剥的话判据会被"诚实的注释"判红。
|
||
*/
|
||
const dir = join(ROOT, 'client/harmony/entry/src/main/ets/pages');
|
||
const files = readdirSync(dir).filter(f => f.endsWith('.ets')).sort();
|
||
assert.ok(files.length >= 8, `pages/ 下只扫到 ${files.length} 个 .ets,判据大概扫错了目录`);
|
||
for (const f of files) {
|
||
const hits = rawColors(readFileSync(join(dir, f), 'utf8'));
|
||
assert.equal(hits.length, 0, `${f} 里有裸色值 ${hits.join('、')}(应改用 Theme 令牌或系统资源)`);
|
||
}
|
||
// 反向对照:四种形态都要抓得到(否则写错了正则也是一片绿)
|
||
assert.deepEqual(rawColors("fontColor('#E8F0FE')"), ['#E8F0FE']);
|
||
assert.deepEqual(rawColors("color('#80000000')"), ['#80000000']);
|
||
assert.deepEqual(rawColors('linearGradient({ colors: [[0xB8FFFFFF, 0]] })'), ['0xB8FFFFFF']);
|
||
assert.deepEqual(rawColors("backgroundColor('rgba(255,255,255,0.72)')"), ['rgba(']);
|
||
// 注释里的旧写法不算(否则这条判据会逼着人删掉"为什么不能这么写"的解释)
|
||
assert.deepEqual(rawColors("// 旧写法是 '#B8FFFFFF'\ncolor(Theme.surface)"), []);
|
||
// 令牌文件本身是**唯一**的颜色来源,否则上面那条会因为"哪儿都没有色值"而空转
|
||
assert.ok(/#[0-9A-Fa-f]{6,8}/.test(harmony), 'Theme.ets 里应该有真正的色值');
|
||
});
|
||
|
||
// ─────────────── 「用系统方案」的意图判据(A/B/C + 品牌色防线) ───────────────
|
||
|
||
test('A|系统拥有的维度,鸿蒙侧的唯一来源是系统资源(不是手抄的值)', () => {
|
||
/*
|
||
* 「一个维度只允许一个机制来源」。表面/文字/分隔/遮罩/圆角这些**系统有语义**的维度
|
||
* 只能来自 `$r('sys.*')` —— 于是它们自动跟随深色模式,也不会再和系统打架。
|
||
*
|
||
* 说明一处**与 pi 原话的有意差异**:他写的是"页面不再引用 Theme.pageBg/surface/…",
|
||
* 我保留了 `Theme.` 这层名字,但把它的**值**换成了系统资源。理由是"唯一来源"这条性质
|
||
* 靠这个也能拿到(值只有一处、且那一处指向系统),而 149 处调用点不用动 ——
|
||
* 少动 149 处就少 149 次改错的机会。真正的防线是下面这条判据本身:
|
||
* 只要有人把这些格子改回手写色值,它就红。
|
||
*/
|
||
const sysOwned = {
|
||
pageBg: 'color', surface: 'color', surfaceMuted: 'color', border: 'color',
|
||
textPrimary: 'color', textMuted: 'color', textSubtle: 'color',
|
||
overlay: 'color', navFg: 'color',
|
||
radiusCard: 'float', radiusControl: 'float'
|
||
};
|
||
for (const name of Object.keys(sysOwned)) {
|
||
const kind = sysOwned[name];
|
||
assert.match(
|
||
harmony,
|
||
new RegExp(`static readonly ${name}: Resource = \\$r\\('sys\\.${kind}\\.[A-Za-z0-9_]+'\\)`),
|
||
`${name} 必须来自系统资源($r('sys.${kind}.*')),而不是手写的值`
|
||
);
|
||
}
|
||
// 反向断言:这些格子不得退回 string/number 类型(退回 = 又开始自己定值了)
|
||
for (const name of Object.keys(sysOwned)) {
|
||
assert.ok(
|
||
!new RegExp(`${name}: (string|number) =`).test(code(harmony)),
|
||
`${name} 不再是系统资源了 —— 这是"用系统方案"被回退的信号`
|
||
);
|
||
}
|
||
});
|
||
|
||
test('B|旧机制不得回来:手写玻璃 alpha、替系统猜深色、与 WebUI 绑死的圆角数字', () => {
|
||
const codeOnly = code(harmony);
|
||
/*
|
||
* `navBgLight` / `navBgDark` 这两个名字本身就是罪证:浅色一个、深色一个手写玻璃,
|
||
* 等于"我们替系统猜了深色该怎么做"。pi 在 WebUI 侧撤掉 `.dark` 导航令牌、
|
||
* 我在这侧撤掉这两个常量,是**同一个判断**,只是答案相反:
|
||
* WebUI 没有深色主题所以不该猜,鸿蒙有系统主题所以**不该手写**。
|
||
*/
|
||
assert.ok(!/navBg(Light|Dark)/.test(codeOnly), 'navBgLight/navBgDark 不该再出现(玻璃交给系统材质)');
|
||
|
||
// 半透明色(8 位 #AARRGGBB)= 手写玻璃/手写遮罩那一类,一律不许
|
||
const translucent = rawColors(codeOnly).filter(h => h.startsWith('#') && h.length === 9);
|
||
assert.deepEqual(translucent, [], '源码里出现 8 位半透明色 —— 半透明属于系统材质/语义色的职责');
|
||
|
||
// 鸿蒙侧不写 CSS 式颜色函数
|
||
assert.ok(!/\brgba?\s*\(/.test(codeOnly), '鸿蒙源码里不该出现 rgb()/rgba()(那是 WebUI 的写法)');
|
||
|
||
// 圆角不得再与 WebUI 的 14/8 绑死
|
||
assert.ok(!/radiusCard: number = 14/.test(codeOnly), 'radiusCard 又变回写死的 14 了');
|
||
assert.ok(!/radiusControl: number = 8/.test(codeOnly), 'radiusControl 又变回写死的 8 了');
|
||
});
|
||
|
||
test('C|玻璃位置必须用系统材质,而且只出现在一层', () => {
|
||
/*
|
||
* 「用系统方案」里最容易被写歪的一处:`#B8FFFFFF` 看着也能出玻璃效果,
|
||
* 但它不跟随深色模式、也不跟随系统的模糊半径。所以钉两件事:
|
||
* ① 材质档次来自 `BlurStyle`(系统枚举),② 应玻璃化的位置真的调了 `backgroundBlurStyle`。
|
||
* 另外**只许一层** —— 嵌套各加一层模糊是 pi 点名要避免的(视觉上会互相打架)。
|
||
*/
|
||
const codeOnly = code(harmony);
|
||
assert.match(codeOnly, /static readonly navMaterial: BlurStyle = BlurStyle\.[A-Z_]+/, '导航材质要声明成系统材质档次');
|
||
const navMaterial = codeOnly.match(/navMaterial: BlurStyle = BlurStyle\.([A-Z_]+)/)[1];
|
||
assert.notEqual(navMaterial, 'NONE', 'NONE 等于没有材质,"玻璃"就名存实亡');
|
||
|
||
const ets = collectEts(join(ROOT, 'client/harmony/entry/src/main/ets'));
|
||
const glassCalls = [];
|
||
for (const f of ets) {
|
||
const src = code(readFileSync(f, 'utf8'));
|
||
for (const m of src.matchAll(/backgroundBlurStyle\(/g)) glassCalls.push(f);
|
||
}
|
||
assert.equal(glassCalls.length, 1, `玻璃应只出现在一处,实际 ${glassCalls.length} 处:${glassCalls.join('、')}`);
|
||
const main = code(readFileSync(join(ROOT, 'client/harmony/entry/src/main/ets/pages/MainPage.ets'), 'utf8'));
|
||
assert.match(main, /\.backgroundBlurStyle\(Theme\.navMaterial\)/, '导航条要用系统材质(不是手写 alpha)');
|
||
});
|
||
|
||
test('★ 品牌色防线:主操作色不得退化成系统强调色', () => {
|
||
/*
|
||
* 这是 pi 最担心的语义事故,我同意:改用系统方案时**顺手**把 `accent` 换成
|
||
* 系统的 emphasize 色,界面看着还挺协调 —— 但品牌蓝是**跨客户端身份**
|
||
* ("两个客户端是同一个产品"),系统强调色会随主题/厂商皮肤变,换过去这件事就靠不住了。
|
||
* 所以这条不是"取值好看",是钉住唯一必须与 WebUI 逐字一致的那一个值。
|
||
*/
|
||
assert.match(harmony, /accent: string = '#2563EB'/, '品牌蓝必须仍是 WebUI 的那个值');
|
||
assert.ok(
|
||
!/accent: Resource/.test(code(harmony)),
|
||
'accent 变成系统资源了 —— 品牌色跟着系统变就不再是同一个产品的标识'
|
||
);
|
||
// 主操作/选中态仍引用它(否则"没退化"只是因为它没被用 —— 值留着也没意义)
|
||
const login = code(readFileSync(join(ROOT, 'client/harmony/entry/src/main/ets/pages/LoginPage.ets'), 'utf8'));
|
||
assert.match(login, /backgroundColor\(Theme\.accent\)/, '登录按钮仍是品牌色');
|
||
const main = code(readFileSync(join(ROOT, 'client/harmony/entry/src/main/ets/pages/MainPage.ets'), 'utf8'));
|
||
assert.match(main, /backgroundColor\(Theme\.accent\)/, '主操作按钮/选中态仍是品牌色');
|
||
});
|
||
|
||
test('权限档位徽标两边同一套色(plan=蓝 / workspace=绿 / full=琥珀)', () => {
|
||
// WebUI 的映射写在 PermissionChip.tsx 的类名里;鸿蒙的映射是 Theme.permBg/permFg。
|
||
const chip = readFileSync(
|
||
join(ROOT, 'client/electron/src/components/PermissionChip.tsx'),
|
||
'utf8'
|
||
);
|
||
assert.match(chip, /plan'[\s\S]{0,80}bg-blue-50 text-blue-700/, 'WebUI plan 档应是蓝');
|
||
assert.match(chip, /full'[\s\S]{0,80}bg-amber-50 text-amber-700/, 'WebUI full 档应是琥珀');
|
||
assert.match(chip, /bg-green-50 text-green-700/, 'WebUI workspace 档应是绿');
|
||
|
||
// 鸿蒙侧取的是同一批值 —— 直接和 WebUI 的 :root 变量比,防的是"看起来差不多"
|
||
const cssVar = (name, src) => {
|
||
const m = src.match(new RegExp(`--${name}:\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)`));
|
||
assert.ok(m, `WebUI 要有 --${name}`);
|
||
return hex('#' + [m[1], m[2], m[3]].map(n => Number(n).toString(16).padStart(2, '0')).join(''));
|
||
};
|
||
const token = name => {
|
||
const m = harmony.match(new RegExp(`${name}: string = '(#[0-9A-Fa-f]{6})'`));
|
||
assert.ok(m, `鸿蒙要有令牌 ${name}`);
|
||
return hex(m[1]);
|
||
};
|
||
assert.equal(token('accentSoft'), cssVar('c-blue-50', web), 'plan 底色应与 blue-50 一致');
|
||
assert.equal(token('accentStrong'), cssVar('c-blue-700', web), 'plan 字色应与 blue-700 一致');
|
||
assert.equal(token('approveBg'), cssVar('c-green-50', web), 'workspace 底色应与 green-50 一致');
|
||
assert.equal(token('approveFg'), cssVar('c-green-700', web), 'workspace 字色应与 green-700 一致');
|
||
assert.equal(token('warnBg'), cssVar('c-amber-50', web), 'full 底色应与 amber-50 一致');
|
||
assert.equal(token('warnFg'), cssVar('c-amber-700', web), 'full 字色应与 amber-700 一致');
|
||
|
||
/*
|
||
* 往返预算条的三个档位(P2a 新加)。
|
||
*
|
||
* WebUI 的 `BudgetChip` 用的是 gray-100/gray-500(普通)、red-100/red-700(用尽)、
|
||
* orange-100/orange-700(将尽);鸿蒙的卡片视图要显示同一条预算,
|
||
* 就得取**同一批值** —— 而且这批值只在这条判据里和 WebUI 对齐,
|
||
* 否则"鸿蒙那边自己挑了个接近的红"没人会发现。
|
||
*
|
||
* ⚠️ 注意:index.css 里 `--c-gray-100` 等变量在 `.dark` 段里还有第二处定义,
|
||
* 上面的 cssVar 取的是**第一处**(`:root`,浅色主题)—— 与 `Theme.ets` 是浅色一套对应。
|
||
*/
|
||
assert.equal(token('chipNeutralBg'), cssVar('c-gray-100', web), '预算普通档底色应与 gray-100 一致');
|
||
assert.equal(token('chipNeutralFg'), cssVar('c-gray-500', web), '预算普通档字色应与 gray-500 一致');
|
||
assert.equal(token('chipSpentBg'), cssVar('c-red-100', web), '预算用尽底色应与 red-100 一致');
|
||
assert.equal(token('chipSpentFg'), cssVar('c-red-700', web), '预算用尽字色应与 red-700 一致');
|
||
assert.equal(token('chipWarnBg'), cssVar('c-orange-100', web), '预算将尽底色应与 orange-100 一致');
|
||
assert.equal(token('chipWarnFg'), cssVar('c-orange-700', web), '预算将尽字色应与 orange-700 一致');
|
||
// 反向对照:判据要真能抓到"自己挑了个接近的颜色"
|
||
const off = harmony.replace("chipSpentBg: string = '#FEE2E2'", "chipSpentBg: string = '#FEE3E3'");
|
||
assert.notEqual(
|
||
hex(off.match(/chipSpentBg: string = '(#[0-9A-Fa-f]{6})'/)[1]),
|
||
cssVar('c-red-100', web),
|
||
'自检:差一个色阶判据却还是绿的'
|
||
);
|
||
});
|
||
|
||
test('遮罩:交给系统的遮罩语义色("随主题换向"这件事现在由系统做)', () => {
|
||
/*
|
||
* 这一条也**改过口径**。旧口径要求鸿蒙照 WebUI 那样把遮罩拆成"色 + 透明度"两个令牌 ——
|
||
* 理由是遮罩色必须**随主题换向**:浅色主题用白把图案洗淡,深色主题必须换黑,
|
||
* 否则浅色照片在深色界面里糊成一块亮斑、正文读不动(WebUI 侧实测踩过)。
|
||
*
|
||
* 但"随主题换向"正是一个**系统语义色**能表达的东西:`ohos_id_color_mask_regular`
|
||
* 深浅两套值由系统给,而且不会再漏配一边 —— 比我们自己维护两个常量更不容易错。
|
||
* 所以口径改成"遮罩来自系统"(这条是硬的),WebUI 侧仍然两段式(它没有系统可跟随)。
|
||
*/
|
||
assert.match(code(harmony), /overlay: Resource = \$r\('sys\.color\.ohos_id_color_mask_regular'\)/, '鸿蒙遮罩要用系统遮罩色');
|
||
// 不许再自己维护"色 + 透明度"两个常量(那正是系统已经替我们做掉的事)
|
||
assert.ok(!/overlayColor: string/.test(code(harmony)), '遮罩色不该再由我们自己定');
|
||
assert.ok(!/overlayAlpha: number/.test(code(harmony)), '遮罩透明度不该再由我们自己定');
|
||
assert.ok(!/#[0-9A-Fa-f]{8}/.test(code(harmony)), '遮罩不该再写成色与透明度焊死的 #AARRGGBB 单值');
|
||
// WebUI 侧同构:颜色两套(浅/深,同一个变量名换向)+ 透明度独立
|
||
assert.match(web, /--bg-scrim: 255 255 255/, 'WebUI 浅色遮罩色');
|
||
assert.match(web, /--bg-scrim: 0 0 0/, 'WebUI 深色遮罩色');
|
||
assert.match(web, /--bg-dim:/, 'WebUI 的透明度是独立变量');
|
||
assert.match(diffSection(), /遮罩/, '遮罩是"有意差异",要写进文档的差异表');
|
||
// 反向对照:判据要真能抓到"焊死单值"
|
||
assert.ok(rawColors("backgroundColor('#80000000')").includes('#80000000'), '自检:正则抓不到焊死的单值');
|
||
});
|
||
|
||
test('C|「有意差异」表列全了允许不同的维度,并点名品牌色**不在内**(弱判据,防遗忘)', () => {
|
||
/*
|
||
* pi 把这条定为**辅助**:文档表会过时,而过时的表照样能判绿 —— 所以它防的是
|
||
* "改了做法没改记录"(下一个人会当成漏改),不是"证明做法对"。
|
||
* 主体在代码上(A/B + 品牌色防线 + 材质位置),这张表是第三只手。
|
||
*/
|
||
const section = diffSection();
|
||
for (const dim of ['圆角', '材质', '动效', '遮罩']) {
|
||
assert.ok(section.includes(dim), `「有意差异」表里要列 ${dim}(它现在是"允许不同"的维度)`);
|
||
}
|
||
assert.match(section, /品牌色[\s\S]{0,80}不允许差异/, '品牌色必须被点名"不允许差异",否则下一个人会以为它也在表内');
|
||
// 反向对照:表里必须真的写了"为什么允许不同",不是只列个名字
|
||
const rows = section.split('\n').filter(l => l.startsWith('|') && !l.includes('---'));
|
||
assert.ok(rows.length >= 5, `差异表应有表头 + 至少 4 行,实际 ${rows.length} 行`);
|
||
assert.ok(rows.every(r => r.split('|').filter(c => c.trim()).length >= 4), '差异表每行要说清"WebUI / 鸿蒙 / 为什么"');
|
||
});
|