跨端: 导航项徽标两侧补齐(我上次"撤回"错了 —— WebUI 是有的)
上一笔我凭"两侧导航都没有徽标"把刚写好的徽标**撤回**了。那是错的:
`client/electron/src/components/Sidebar.tsx:88-95,111-125` 明确有——
```
const badge = isComm ? unread + pendingPerms
: modes.includes('contacts') ? contacts.length : 0;
const badgeTone = isComm && pendingPerms > 0 ? 'perm' : isComm ? 'unread' : 'plain';
```
并且是 `absolute top-0.5 right-1` 压在导航项右上角,`>99` 显示 `99+`。
我当时只看了底栏 `NavItem`(那里确实没有),就把结论推到了"两侧都没有"。
## 补的东西
- 新增**纯逻辑** `model/NavItems.ts` 的 `navBadgeCount` / `navBadgeTone` / `navBadgeText`,
逐条对齐 WebUI 的口径:
· **通信** = 未读 + 待决策(两类"要动手"合起来);
· **联系人** = 联系人数;日历/「我的」= 0(没有徽标);
· 色调:待决策**橙**(有人卡在那儿等)优先于未读**红**(只是还没看);
· 负数当 0(计数来自网络,不假设它干净);`>99` → `99+`。
- **两侧**(底栏 `MainPage.NavItem` + 宽屏 `WideSidebar.NavItemBuilder`)都接上,
且读**同一组** AppStorage 键 —— 两处各算一套,数字迟早对不上,而用户同时看得到它们。
- 计数发布走 `AppStorage`(单向:窗格写、导航栏读),与 `KEY_WINDOW_INSETS` 同一套机制。
不把这两个数提到 `MainPage`:那样"从没进过通信页"也会去发请求。
- 徽标位置对齐 WebUI 的 `absolute top-0.5 right-1`(压在项的右上角)。
★ 第一版我排在文字**下面**,截图一眼可见那颗 3 掉到了「联系人」标签底下、
还把 48vp 的项撑高了 —— 方阵节奏乱掉。
## 判据(harmony-widescreen 6 → 7)
第 ⑦ 条**直接执行**鸿蒙侧的纯函数,且期望值在测试里**独立算一遍**
(不复用被测函数,否则是"用实现验实现")。
★ 接线部分我写错过一次,变异测试当场拆穿:第一版只判
`assert.match(src, /navBadgeCount\(/)` —— 把**渲染处**的调用换成 `0`
(徽标永远不显示),文件里仍留着一处调用,判据照样全绿。
⇒ 改成判**把值交给 Text 的那一行**,并且认出两侧写法不同(底栏走 helper
`Text(this.navBadgeOf(key))`,侧栏就地内联 `Text(navBadgeText(navBadgeCount(...)))`)。
**4 个变异方向全咬**:通信漏算待决策 ⇒ 红;色调优先级写反 ⇒ 红;
侧栏渲染处换空 ⇒ 红;底栏渲染处换空 ⇒ 红。
设备实测:侧栏「联系人」显示红 **3**(3 个联系人),位置在图标右上角。
This commit is contained in:
@ -202,4 +202,77 @@ test('⑤ 宽屏 app-shell 几何:padding/gap/radius 与 WebUI 同值(一比
|
||||
assert.match(code_, /clip\(this\.isWide\)/, '面板内容要被圆角裁剪(clip 宽屏才开)');
|
||||
// 容器 padding 宽屏 paneGap / 窄屏 0(壁纸从缝隙露出)
|
||||
assert.match(code_, /left: this\.isWide \? Theme\.paneGap : 0/, '宽屏容器左右 padding paneGap(窄屏 0 贴合全屏)');
|
||||
});
|
||||
});
|
||||
test('⑦ 导航项徽标:取值/色调与 WebUI Sidebar 同口径(纯逻辑,不需要设备)', async () => {
|
||||
/*
|
||||
* WebUI `Sidebar.tsx:88-95` 的 `badge` / `badgeTone`:
|
||||
* `isComm ? unread + pendingPerms : (contacts ? contacts.length : 0)`
|
||||
* `isComm && pendingPerms > 0 ? 'perm' : isComm ? 'unread' : 'plain'`
|
||||
*
|
||||
* ★ 这条判据**直接执行**鸿蒙侧的纯函数(`model/NavItems.ts`,无 SDK 依赖),
|
||||
* 并把期望值**在测试里独立算一遍**(不复用被测函数)—— 否则就是"用实现验实现"。
|
||||
*
|
||||
* 为什么值得单列一条:底栏与侧栏都要显示徽标,而两处若各写一套判断,
|
||||
* 同一时刻一个显示"3 / 未读"、另一个显示"3 / 待批",用户会以为哪里出错了。
|
||||
* 所以取值与色调都必须来自**同一组纯函数**。
|
||||
*/
|
||||
const mod = await import(join(HARMONY_ETS, 'model', 'NavItems.ts'));
|
||||
const { navBadgeCount, navBadgeTone, navBadgeText } = mod;
|
||||
|
||||
// ① 通信 = 未读 + 待决策(两类"要动手"合起来,与 WebUI 同)
|
||||
assert.equal(navBadgeCount('comm', 5, 2, 0), 7, '通信徽标 = 未读 5 + 待决策 2');
|
||||
// ② 联系人 = 联系人数
|
||||
assert.equal(navBadgeCount('contacts', 0, 0, 3), 3, '联系人徽标 = 联系人数');
|
||||
// ③ 日历 / 我的 没有徽标(日历的"有事"是格子上的点,不是导航栏该喊的)
|
||||
assert.equal(navBadgeCount('calendar', 5, 2, 3), 0, '日历不该有徽标');
|
||||
assert.equal(navBadgeCount('me', 5, 2, 3), 0, '「我的」不该有徽标');
|
||||
// ④ 负数当 0(计数来自网络,不假设干净)
|
||||
assert.equal(navBadgeCount('comm', -1, -1, -1), 0, '负数一律当 0');
|
||||
assert.equal(navBadgeCount('contacts', 0, 0, -5), 0, '联系人数为负也当 0');
|
||||
|
||||
// ⑤ 色调:待决策橙(更急)优先于未读红;非通信项是中性
|
||||
assert.equal(navBadgeTone('comm', 2), 'perm', '有人的授权在等我 ⇒ 橙(比"还没看"更急)');
|
||||
assert.equal(navBadgeTone('comm', 0), 'unread', '只有未读 ⇒ 红');
|
||||
assert.equal(navBadgeTone('contacts', 0), 'plain', '联系人数是中性色');
|
||||
|
||||
// ⑥ 文字:0 → 空串(页面据此不渲染)、>99 → 99+(与 WebUI 同一写法)
|
||||
assert.equal(navBadgeText(0), '', '0 要变成空串(否则会渲染一个"0"徽标)');
|
||||
assert.equal(navBadgeText(99), '99');
|
||||
assert.equal(navBadgeText(100), '99+');
|
||||
|
||||
/*
|
||||
* ⑦ 接线:**两侧**(底栏 NavItem + 宽屏侧栏)都要用这同一组函数。
|
||||
* 只在一处接的话,另一个视图里徽标就消失了 —— 而用户两个视图都会用到。
|
||||
*/
|
||||
const main = stripComments(read('pages/MainPage.ets'));
|
||||
const sidebar = stripComments(read('pages/WideSidebar.ets'));
|
||||
/*
|
||||
* ★ 判**徽标真的被渲染出来**,不是"文件里出现过 navBadgeCount"。
|
||||
*
|
||||
* 第一版只 `assert.match(src, /navBadgeCount\(/)` —— 变异测试当场证明它不咬:
|
||||
* 把渲染处的 `navBadgeCount(...)` 换成 `0`(徽标永远不显示),
|
||||
* 文件里仍留着别处的调用,判据照样全绿。
|
||||
*
|
||||
* 两侧的**写法不同**(都合法),所以按各自形状判:
|
||||
* · 底栏 MainPage:走一个 helper —— `Text(this.navBadgeOf(key))`,
|
||||
* helper 体内 `navBadgeText(navBadgeCount(...))`;
|
||||
* · 宽屏 WideSidebar:就地内联 —— `Text(navBadgeText(navBadgeCount(...)))`。
|
||||
* 两边都要判到,且都要判底色来自 `navBadgeTone`。
|
||||
*/
|
||||
assert.match(main, /Text\(this\.navBadgeOf\(/,
|
||||
'底栏要把徽标交给 Text 渲染(光"文件里出现过 navBadgeCount"不算接线)');
|
||||
assert.match(main, /navBadgeText\(navBadgeCount\(/,
|
||||
'底栏的 helper 要真的用 navBadgeText(navBadgeCount(...)) 算值');
|
||||
assert.match(main, /backgroundColor\(navBadgeTone\(/,
|
||||
'底栏的徽标底色要由 navBadgeTone 决定');
|
||||
|
||||
assert.match(sidebar, /Text\(navBadgeText\(navBadgeCount\(/,
|
||||
'侧栏要把徽标交给 Text 渲染(光"文件里出现过 navBadgeCount"不算接线)');
|
||||
assert.match(sidebar, /backgroundColor\(navBadgeTone\(/,
|
||||
'侧栏的徽标底色要由 navBadgeTone 决定');
|
||||
// 两侧读**同一组** AppStorage 键(各算一套的话数字会不一致)
|
||||
for (const key of ['agentmail.nav.unread', 'agentmail.nav.pending', 'agentmail.nav.contacts']) {
|
||||
assert.ok(main.includes(key), `MainPage 要读 ${key}`);
|
||||
assert.ok(sidebar.includes(key), `WideSidebar 要读 ${key}(与底栏同一组键)`);
|
||||
}
|
||||
});
|
||||
|
||||
@ -137,7 +137,7 @@ const SUITE = [
|
||||
// 三维地址拼装:直接执行 `model/ReplyTarget.ts` 真逻辑(行为判据)
|
||||
['test/harmony-reply-target.test.mjs', ['--experimental-strip-types', '--no-warnings'], 7],
|
||||
// 宽屏侧栏图标轨:`WideSidebar` 接线与常量(静态判据,无设备)
|
||||
['test/harmony-widescreen.test.mjs', [], 6]
|
||||
['test/harmony-widescreen.test.mjs', [], 7]
|
||||
];
|
||||
|
||||
// 自检 1:清单里的文件必须真的存在(写错名字 = 那条判据永远不跑)
|
||||
|
||||
Reference in New Issue
Block a user