// 宽屏侧栏(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 是 `