// 宽屏侧栏(WideSidebar)判据 —— 逐项复刻 WebUI 的 `Sidebar`。
//
// ★★ 2026-09-19 重写本文件的头部说明(上面那一版是错的,与它判的东西矛盾):
// 旧文写的是「60px 图标轨 / 通信·日历·联系人(中)/ 设置(底)/
// 选中态只换颜色 + 品牌浅底,**不加背景块**」—— 那个描述是**编的**:
// · `Sidebar.tsx` 第 26-44 行是**三项**(通信/日历/「**联系**」)——「我的」不在导航轨里,
// 而是 **底部头像按钮**(`Sidebar.tsx:186`);
// · `Sidebar.tsx:110` 有 `{short}`(有文字);
// · `index.css:1590` 的 `.nav-item[data-active='true']` **有底色块**,
// 而且 CSS 注释专门说明侧栏**必须有**(“图标底下那一块底色是它唯一的选中线索”)。
// 更糟的是:判据当时**引用了这段自编的描述当依据**,于是把错误锁死 —— 全绿。
// 现在的口径:每一项都回读 WebUI 源码取值,不再引“我上次写的那句话”。
//
// 鸿蒙侧原来**没有**宽屏布局(只有底部导航条),WideSidebar 补的就是"宽屏模式"这半:
// ≥768vp 时 MainPage 挂侧栏、藏底部条(见 MainPage.build 的 onAreaChange)。
import { code, prose, stripComments } from './lib/read.mjs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { test } from 'node:test';
import assert from 'node:assert/strict';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..', '..');
const HARMONY_ETS = join(ROOT, 'client/harmony/entry/src/main/ets');
/** WebUI 源码根:判据要**回读**它取值,而不是引注释里的话 */
const WEBUI_SRC = join(ROOT, 'client/electron/src');
const read = p => prose(join(HARMONY_ETS, p));
/*
* 读 WebUI 的源文件(**不去注释**:注释里就有真实理由)。
* ★ 用共享入口 `prose` 而不是裸 `readFileSync`:判据目录有一条 hygiene 判据
* 专门拦裸用(理由是那些用法会把"读哪个文件、剥不剥注释"变成每个文件各说各话)。
*/
const readWeb = p => prose(join(WEBUI_SRC, p));
/** 取某个成员/方法正文(按行切到下一个成员声明) */
function memberBody(src, signature) {
const lines = src.split('\n');
const start = lines.findIndex(l => l.includes(signature));
assert.ok(start > 0, `要能找到 ${signature}`);
let stop = lines.length;
for (let i = start + 1; i < lines.length; i++) {
if (/^ (@Builder|build\(|NavItemBuilder\()/.test(lines[i])) { stop = i; break; }
}
return lines.slice(start, stop).join('\n');
}
test('① 宽度:SIDEBAR_WIDTH = 60vp(WebUI 的 `w-[60px]` 是同一数字)', () => {
const sidebar = read('pages/WideSidebar.ets');
const code_ = stripComments(sidebar);
assert.match(code_, /export const SIDEBAR_WIDTH: number = 60\s*;/, '要导出 60 这个常量');
const main = read('pages/MainPage.ets');
assert.match(main, /WideSidebar\(/, 'MainPage 要真正挂 WideSidebar');
});
test('② 侧栏**三项**(通信/日历/联系)+ 底部一簇 —— 与底栏四项不是同一份清单', () => {
/*
* ★★ 2026-09-19 重写(用户:「你写的app和webui大面积不符,问题特别大」)。
*
* 上一版这条判的是「遍历 `NAV_CONTENT_ITEMS`」—— 而 `NAV_CONTENT_ITEMS` 是
* **四项**(含「我的」)。那正是**错的形状**:四项只对**底栏**。
*
* WebUI 两套导航**本来就不同**(同一份源码里两个文件):
* · `NarrowNav.tsx:37-40` items = 通信/日历/**联系人**,
* 再加第 4 个「我的」按钮(`NarrowNav.tsx:143`)⇒ 底栏 **4 项**;
* · `Sidebar.tsx:26-44` navItems = 通信/日历/**联系** ⇒ 侧栏 **3 项**,
* 「我的」是底部**头像按钮**(`Sidebar.tsx:186`,旁边的注释就说它是
* `title=...点击管理账号`)。
*
* 这条件判据的写法变了:不再只查鸿蒙源码的字符串,而是
* **回读 `Sidebar.tsx` 数出 `short:` 的个数**,要求鸿蒙那一份也是同一数字 ——
* 否则下次 WebUI 改了项数,这里只会继续绿。
*/
const sidebarSrc = readWeb('components/Sidebar.tsx');
const narrowSrc = readWeb('components/NarrowNav.tsx');
// 从 WebUI 源码里数导航项:`short: 'xx'` 的行(两处各数一遍)
const shortOf = (src) => [...src.matchAll(/short:\s*'([^']+)'/g)].map(m => m[1]);
const webSidebarShort = shortOf(sidebarSrc);
const webNarrowShort = shortOf(narrowSrc);
assert.deepEqual(webSidebarShort, ['通信', '日历', '联系'],
`WebUI 侧栏应为 3 项(读到的是 ${JSON.stringify(webSidebarShort)})——若这里变了,下面的期望值要跟着改`);
assert.deepEqual(webNarrowShort, ['通信', '日历', '联系人'],
`WebUI 底栏内容项应为 3 项(读到的是 ${JSON.stringify(webNarrowShort)})——与侧栏的第三项措辞**故意不同**`);
// 鸿蒙侧:侧栏清单必须与 WebUI 侧栏逐字一致(项数与文案)
const navItems = read('model/NavItems.ts');
const sidebarList = navItems.match(/export const NAV_SIDEBAR_ITEMS[\s\S]*?\];/);
assert.ok(sidebarList, 'model/NavItems.ts 要有 NAV_SIDEBAR_ITEMS(侧栏专用的三项清单)');
const harmonySidebarShort = [...sidebarList[0].matchAll(/label:\s*'([^']+)'/g)].map(m => m[1]);
assert.deepEqual(harmonySidebarShort, webSidebarShort,
`NAV_SIDEBAR_ITEMS 的 label 要与 WebUI 侧栏逐字一致(现在读到 ${JSON.stringify(harmonySidebarShort)})`);
// 侧栏**不许**用四项那份清单(那正是这次报的“多出一个我的”)
const sidebar = stripComments(read('pages/WideSidebar.ets'));
assert.match(sidebar, /ForEach\(NAV_SIDEBAR_ITEMS,/,
'侧栏要遍历 NAV_SIDEBAR_ITEMS(三项)');
assert.ok(!/ForEach\(NAV_CONTENT_ITEMS,/.test(sidebar),
'侧栏不许再遍历 NAV_CONTENT_ITEMS(那是**底栏**的四项清单,含「我的」)');
// 品牌标:WebUI 是 `