跨端: 预设色板两套(pi:这不是"观感未验"而是机制上确定不同)+ 手写色清册跨文件 + TMPDIR 按会话分家 + 提交归属可判
pi 读完 `model/Wallpaper.ts` 后指出四处,全部处理。这一提交同时改了
`client/harmony/` 与 `client/electron/`(跨端改动),所以 subject 按新约定自报家门。
## 1 预设色板不随主题 —— **类别判错了:不是"未验",是机制上确定不同**
我上一版把"深色档预设"记成"观感未验"。pi 指出:WebUI 的 `.bg-preset-*` 写的是
`rgb(var(--c-blue-100))`,而 `--c-*` 在 `.dark` 里整体换了一套(blue-100 → `30 43 67`),
所以 **WebUI 的预设自动随主题变**;这边只有浅色那套 = 深色主题下"浅色渐变垫在深色系统表面之下",
正是这一整轮在治的病。**它不需要真机就能判**(机制写在代码里)—— 我把可判的东西
记成了"未验",这跟上一轮把"没做"写成"没验"是同一类错。
选 pi 倾向的那条(跟随主题,与 WebUI 一致):
- 色板两套:`LIGHT_*` 取 CSS `:root`、`DARK_*` 取 CSS `.dark`;`paletteFor(dark)` 选一套,
`layersFor(id, dark)` 按主题出层;
- `isDarkMode(theme, systemColorMode)` 放在纯逻辑里:选了 dark/light 就照办,
`system` 看系统当时的 `colorMode`(锚到 SDK:`COLOR_MODE_DARK = 0` / `COLOR_MODE_LIGHT = 1`;
读不到按浅色,与 WebUI 的 `:root` 默认一致);
- 系统深浅从 `resourceManager.getConfigurationSync().colorMode` 读
(`Context` 基类没有 `config`;`UIAbilityContext.config` 要转型;两个枚举取值一致,都核过 SDK);
- 判据:两套值与 `:root`/`.dark` **逐个相等**;每个预设的深浅两套**必须真的不同**
(否则"两套"是抄了两遍);网格线色也要换;`isDarkMode` 五种输入。
- **未做**:运行期间改系统深浅色不会自动重算(要重进页面)——系统侧正确做法是订阅
`applicationContext.on('environment', …)`,记在 §7.17b。
变异:`DARK_BLUE_100` 偏一位 → 红;`paletteFor` 永远返回浅色(= 我原来那个状态)→ 红;
`isDarkMode` 把系统深浅记反 → 红;页面把深浅写死成 false → 红。
## 2 手写色清册**跨文件按类扫**(原 A2 只保护 `Theme.ets`)
`Wallpaper.ts` 也有手写色。若对照是"按名字枚举"的,第 15 个色就会逃掉 ——
与 A2 要防的是同一件事,只是换了文件。现在一份清册按类扫:全 `ets/` 树里每个
`X: string = '#RRGGBB'` 都必须登记(Theme 的品牌/业务语义色,或预设色板 ——
后者常量名必须带 `LIGHT_`/`DARK_` 前缀,值由 CSS 两段比对负责)。反向也判清册过期。
变异:`Theme.ets` 加未登记色 → 红;`Wallpaper.ts` 加未登记色 → 红;
加一个"看着合规"的 `DARK_EXTRA` → 红。
## 3 `TMPDIR` 互踩(pi 提出)
这个 worktree 可能同时有多个 agent 跑构建,而 fpm 会把 291MB 的 `linux-unpacked`
**整份复制**进 `TMPDIR` —— 撞车就是随机的产物损坏。`whoami` 区分不开(大家都是 root),
所以按**会话**分家:`TMPDIR=$PWD/.tmp/${DSH_SESSION_ID:-$(id -un)-$$}`
(进了 `npm run build:linux` 与 BUILD.md 的手敲命令;普通终端退化成"用户+PID")。
## 4 提交归属变成**跑判据就看得出来**(pi 给的形状)
新的 `test/commit-hygiene.test.mjs`:扫最近 40 条提交,**同时改两侧目录**的提交
必须在 subject 里自报家门(`跨端:`)。两条防腐:基线 = 该判据文件自己的引入提交
(**历史不改**,规则管从今往后);分类逻辑拿合成输入自检
(未标注的混合提交必须判红、标注过的不许红)——否则"解析没跑起来"时它会全绿。
变异:`COMMIT_HYGIENE_BASELINE` 指到老提交 → 历史里那两个被卷进去的提交立刻判红。
## 验证
`hvigorw assembleHap` BUILD SUCCESSFUL;`npm test` 退出码 0
(12 个判据文件全绿 + vitest 258/258;`commit-hygiene` 在本提交落地后基线生效)。
This commit is contained in:
@ -12,12 +12,15 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } 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');
|
||||
/** SDK 位置(与 harmony-system-api 同一套默认值/环境变量) */
|
||||
const CLT = process.env.HARMONY_CLT || '/opt/huawei/command-line-tools';
|
||||
const MODULE_TS = join(HARMONY_ETS, 'model/Appearance.ts');
|
||||
|
||||
const A = await import(pathToFileURL(MODULE_TS).href);
|
||||
@ -268,7 +271,7 @@ test('★ 预设清单与 WebUI 一致(id + 顺序 + 中文标签)—— 少
|
||||
assert.deepEqual(W.PRESET_IDS.map(W.presetLabel), webLabels, '预设标签必须与 WebUI 一致(用户看到的就是这两个字)');
|
||||
// 每一个预设都要真能画出层来("有 id 但画不出东西"就是这个缺口的原始形态)
|
||||
for (const id of W.PRESET_IDS) {
|
||||
const layers = W.layersFor(id);
|
||||
const layers = W.layersFor(id, false);
|
||||
assert.ok(layers.length >= 1, `预设 ${id} 至少要有一层`);
|
||||
assert.ok(layers.every(l => l.colors.length > 0 || l.kind === 'grid'), `预设 ${id} 的层要有色标`);
|
||||
assert.ok(layers.some(l => l.kind === 'grid' || l.colors.length >= 2), `预设 ${id} 要能画出渐变`);
|
||||
@ -289,7 +292,7 @@ test('★ 预设色值与 WebUI 的调色板变量逐个对照(不是凭印象
|
||||
};
|
||||
const used = new Set();
|
||||
for (const id of W.PRESET_IDS) {
|
||||
for (const l of W.layersFor(id)) {
|
||||
for (const l of W.layersFor(id, false)) {
|
||||
for (const c of l.colors) if (c !== W.TRANSPARENT) used.add(c);
|
||||
if (l.lineColor) used.add(l.lineColor);
|
||||
}
|
||||
@ -313,7 +316,7 @@ test('★ 预设色值与 WebUI 的调色板变量逐个对照(不是凭印象
|
||||
}
|
||||
// 透明必须用关键字而不是 8 位色值(8 位色值在本仓是"手写玻璃"的证据,另有一条判据禁)
|
||||
for (const id of W.PRESET_IDS) {
|
||||
for (const l of W.layersFor(id)) {
|
||||
for (const l of W.layersFor(id, false)) {
|
||||
for (const c of l.colors) {
|
||||
assert.ok(!/^#[0-9A-Fa-f]{8}$/.test(c), `预设里的透明要用 Color.Transparent 语义(${c} 是 8 位色值)`);
|
||||
}
|
||||
@ -324,7 +327,7 @@ test('★ 预设色值与 WebUI 的调色板变量逐个对照(不是凭印象
|
||||
const cssBlock = webCss.slice(webCss.indexOf(`.bg-preset-${id}`));
|
||||
const cssBody = cssBlock.slice(0, cssBlock.indexOf('}'));
|
||||
const cssSegments = (cssBody.match(/(radial|linear)-gradient/g) || []).length;
|
||||
const mine = W.layersFor(id).filter(l => l.kind !== 'grid').length + (W.layersFor(id).some(l => l.kind === 'grid') ? 1 : 0);
|
||||
const mine = W.layersFor(id, false).filter(l => l.kind !== 'grid').length + (W.layersFor(id, false).some(l => l.kind === 'grid') ? 1 : 0);
|
||||
assert.ok(cssSegments >= 1, `CSS 里 ${id} 要有渐变段`);
|
||||
assert.equal(mine >= cssSegments, true, `${id}:CSS 有 ${cssSegments} 段,鸿蒙只画了 ${mine} 层`);
|
||||
}
|
||||
@ -334,22 +337,22 @@ test('画什么:none / preset / image 三档,图没取回来不许画空白'
|
||||
// 认不出的 preset → 默认档(与 WebUI 的 normalize 同一规则),不是"没有背景"
|
||||
assert.equal(W.normalizePreset('不认识'), 'aurora');
|
||||
assert.equal(W.normalizePreset(''), 'aurora');
|
||||
assert.equal(W.layersFor('不认识').length, W.layersFor('aurora').length, '认不出要走默认档的画法');
|
||||
assert.equal(W.layersFor('不认识', false).length, W.layersFor('aurora', false).length, '认不出要走默认档的画法');
|
||||
|
||||
const none = W.resolveBackground('none', 'aurora', 0.2, false);
|
||||
const none = W.resolveBackground('none', 'aurora', 0.2, false, false);
|
||||
assert.equal(none.kind, 'none');
|
||||
assert.deepEqual(none.layers, [], 'none 档不该画任何层');
|
||||
|
||||
const preset = W.resolveBackground('preset', 'mint', 0.2, false);
|
||||
const preset = W.resolveBackground('preset', 'mint', 0.2, false, false);
|
||||
assert.equal(preset.kind, 'preset');
|
||||
assert.ok(preset.layers.length >= 1, 'preset 档要真的画出层来(这就是那个缺口的判据)');
|
||||
assert.equal(preset.scrim, 0, 'preset 档不压暗(压暗是给图片用的)');
|
||||
|
||||
const img = W.resolveBackground('image', 'aurora', 0.24, true);
|
||||
const img = W.resolveBackground('image', 'aurora', 0.24, true, false);
|
||||
assert.equal(img.kind, 'image');
|
||||
assert.equal(img.scrim, 0.24, '压暗浓度要传给画的那一层');
|
||||
|
||||
const imgMissing = W.resolveBackground('image', 'aurora', 0.24, false);
|
||||
const imgMissing = W.resolveBackground('image', 'aurora', 0.24, false, false);
|
||||
assert.equal(imgMissing.kind, 'none', 'image 档但图没取回来 → 什么都不画(画一块空白会被当成"壁纸坏了")');
|
||||
});
|
||||
|
||||
@ -457,3 +460,98 @@ test('★ 背景画出来了还不够:每个页面要**让出**页面底,否
|
||||
assert.match(head, /@Prop bgActive: boolean = false;/, `${comp} 要声明 @Prop bgActive`);
|
||||
}
|
||||
});
|
||||
|
||||
// ───────────── 深色色板:pi 指出这是"机制上确定不同",不是"观感未验" ─────────────
|
||||
|
||||
/** 从 index.css 的某个段(:root 或 .dark)里读出调色板变量的 RGB */
|
||||
function cssPalette(selector) {
|
||||
const at = selector === ':root' ? webCss.indexOf(':root') : webCss.indexOf('.dark {');
|
||||
assert.ok(at >= 0, `CSS 里要有 ${selector} 段`);
|
||||
let depth = 0;
|
||||
let end = at;
|
||||
for (let i = webCss.indexOf('{', at); i < webCss.length; i++) {
|
||||
if (webCss[i] === '{') depth++;
|
||||
else if (webCss[i] === '}') {
|
||||
depth--;
|
||||
if (depth === 0) { end = i; break; }
|
||||
}
|
||||
}
|
||||
const body = webCss.slice(webCss.indexOf('{', at), end);
|
||||
const read = (name) => {
|
||||
const m = new RegExp(`${name}:\\s*(\\d+)\\s+(\\d+)\\s+(\\d+);`).exec(body);
|
||||
assert.ok(m, `${selector} 里要有 ${name}`);
|
||||
return '#' + [m[1], m[2], m[3]].map(n => Number(n).toString(16).padStart(2, '0').toUpperCase()).join('');
|
||||
};
|
||||
return {
|
||||
gray100: read('--c-gray-100'), gray200: read('--c-gray-200'),
|
||||
blue100: read('--c-blue-100'), blue200: read('--c-blue-200'),
|
||||
green100: read('--c-green-100'), amber100: read('--c-amber-100'), orange100: read('--c-orange-100')
|
||||
};
|
||||
}
|
||||
|
||||
test('★ 预设色板**两套**、且深色那套与 CSS 的 `.dark` 段逐个相等(不是"观感未验",是机制)', () => {
|
||||
/*
|
||||
* pi 的原话(2026-09-14):WebUI 的 `.bg-preset-*` 写的是 `rgb(var(--c-blue-100))`,
|
||||
* 而 `--c-*` 在 `.dark` 里整体换了一套(blue-100 → 30 43 67),
|
||||
* 所以 WebUI 的预设**自动随主题变**。这边若只有浅色那套,
|
||||
* 深色主题下就是"浅色渐变垫在深色系统表面之下" —— 同一个病。
|
||||
* **它不需要真机就能判**:机制写在代码里。所以从"未验"改成"判"。
|
||||
*/
|
||||
const lightCss = cssPalette(':root');
|
||||
const darkCss = cssPalette('.dark');
|
||||
// 两套必须真的不同(否则"两套"是假的:同一套抄了两遍)
|
||||
for (const k of Object.keys(lightCss)) {
|
||||
assert.notEqual(lightCss[k], darkCss[k], `CSS 里 ${k} 的深浅两套应当不同(判据前提)`);
|
||||
}
|
||||
const light = W.paletteFor(false);
|
||||
const dark = W.paletteFor(true);
|
||||
assert.equal(light.dark, false);
|
||||
assert.equal(dark.dark, true);
|
||||
for (const k of Object.keys(lightCss)) {
|
||||
assert.equal(light[k], lightCss[k], `浅色色板 ${k} 与 CSS :root 不一致`);
|
||||
assert.equal(dark[k], darkCss[k], `深色色板 ${k} 与 CSS .dark 不一致(深色档漏了一个色就是"半深不浅")`);
|
||||
}
|
||||
// 每个预设的层在两种主题下都要能画出来,且**颜色确实换了**
|
||||
for (const id of W.PRESET_IDS) {
|
||||
const l = W.layersFor(id, false);
|
||||
const d = W.layersFor(id, true);
|
||||
assert.equal(l.length, d.length, `${id} 深浅两套的层数要一致`);
|
||||
assert.notDeepEqual(
|
||||
l.map(x => x.colors.join(',') + '|' + x.lineColor),
|
||||
d.map(x => x.colors.join(',') + '|' + x.lineColor),
|
||||
`${id} 的深色档与浅色档颜色相同 —— 预设没有真的跟着主题走`
|
||||
);
|
||||
}
|
||||
// 网格档的线色也要换
|
||||
const meshL = W.layersFor('mesh', false).find(x => x.kind === 'grid');
|
||||
const meshD = W.layersFor('mesh', true).find(x => x.kind === 'grid');
|
||||
assert.equal(meshL.lineColor, lightCss.gray200);
|
||||
assert.equal(meshD.lineColor, darkCss.gray200, '网格线色也要跟着主题(否则深色下是一张白网格)');
|
||||
});
|
||||
|
||||
test('★ isDarkMode:system 要看系统当时的深浅,读不到时按浅色(与 WebUI 默认一致)', () => {
|
||||
assert.equal(A.isDarkMode('dark', 1), true, '用户选了深色就照办(即使系统是浅色)');
|
||||
assert.equal(A.isDarkMode('light', 0), false, '用户选了浅色就照办');
|
||||
assert.equal(A.isDarkMode('system', 0), true, 'system + 系统深色 → 深色色板');
|
||||
assert.equal(A.isDarkMode('system', 1), false, 'system + 系统浅色 → 浅色色板');
|
||||
assert.equal(A.isDarkMode('system', -1), false, '系统还没定(NOT_SET)→ 按浅色,与 WebUI 的 :root 默认一致');
|
||||
// 数值锚到 SDK:0=深、1=浅
|
||||
const sdkConst = readFileSync(join(CLT, 'sdk/default/openharmony/ets/api/@ohos.app.ability.ConfigurationConstant.d.ts'), 'utf8');
|
||||
assert.match(sdkConst, /COLOR_MODE_DARK = 0/, 'SDK 里深色是 0(别记反)');
|
||||
assert.match(sdkConst, /COLOR_MODE_LIGHT = 1/, 'SDK 里浅色是 1');
|
||||
// 页面真的按主题选色板(不是写死 false)
|
||||
const main = read('pages/MainPage.ets');
|
||||
assert.match(main, /isDarkMode\(snap\.theme, systemMode\)/, '主界面要按当前主题算深浅色');
|
||||
// 取调用点的正文要按**括号配对**,别用 `[^)]*` —— 里面还套着 `scrimOpacity(...)`,
|
||||
// 一遇到内层 `)` 就断(这正是"邻接/窗口不是结构"那条规则,我自己先守)
|
||||
const callAt = main.indexOf('resolveBackground(');
|
||||
assert.ok(callAt > 0, '要能找到 resolveBackground 调用');
|
||||
let depth = 0;
|
||||
let callEnd = callAt;
|
||||
for (let i = main.indexOf('(', callAt); i < main.length; i++) {
|
||||
if (main[i] === '(') depth++;
|
||||
else if (main[i] === ')') { depth--; if (depth === 0) { callEnd = i; break; } }
|
||||
}
|
||||
const callText = main.slice(callAt, callEnd + 1);
|
||||
assert.ok(/\bdark\b\s*\)/.test(callText), `深浅色要传给背景计划:${callText}`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user