Files
MailUI4Agents/client/electron/test/nav-merge.test.mjs
JianFeeeee f6feb7c0df test(webui): 「滑动硬截断」诊断脚本(自带浏览器,不依赖共享实例)
用户:「webui 大片界面存在滑动硬截断」。

## 现状:这一轮我**没能复现**

量法是"内容溢出但没有可滚动祖先"⇒ 候选 0 个;换成用户视角四问
(有没有可滚动容器 / 能不能滚到底 / 末尾有没有被切 / 页面自己溢不溢出)后:

    390×700  通信  日历  联系人  我的 
    1400×700 通信  日历  联系人  我的 
    (造了 12 个会话、正文都很长;验完已归档)

即按这四种量法都测不到硬截断。**长正文详情页**那一块我的探针超时没跑完 ⇒
**未判定**,不能算"没问题"。

## 这次的交付物

`client/electron/test/manual/scroll-cut-verify.mjs`:把上面这套量法固化下来,
并且**自带浏览器**(不连共享 9222)—— 共享实例被历次被中断的运行留下僵尸上下文后,
`connectOverCDP` 会一直超时(我这个会话里已经遇到三次),诊断脚本必须在
"共享实例状态未知"时也能跑。

## 我需要的(否则只能继续猜)

请指一下**具体页面 + 具体手势/位置**:哪一屏、滚轮还是触摸拖动、硬截断出现在
列表末尾还是详情正文?也可以直接看截图。我上一轮"窄屏显示不全"也是同样情况 ——
我量不出来、但你说有,那多半是我量的维度不对。
2026-09-14 13:43:11 +08:00

128 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 导航重构的判据2026-09-14 用户三条要求)。
*
* 用户原话:
* ①「导航栏的内容有点多了,收件发件授权改为一个导航项,通过内部导航区分,
* 然后新建作为他们内部的一个悬浮的圆形加号,这样导航项就只剩通信,日历,联系人」
* ②「将管理和我的合并,管理员视角我的页面拉到最下面有一个管理」
* ③「复选框和地址猜测项的透明度问题还没改,他们才是真正需要拉低透明度的地方」
*
* 为什么写文件级判据:改完跑老套件 254 条**全绿**,因为它一条都没测导航结构 ——
* 我差点把"没红的测试"当成"改对了"。结构类改动必须自己带判据。
*/
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = dirname(fileURLToPath(import.meta.url));
const read = (...p) => readFileSync(join(HERE, '..', 'src', ...p), 'utf8');
const sidebar = read('components', 'Sidebar.tsx');
const narrow = read('components', 'NarrowNav.tsx');
const commTabs = read('components', 'CommTabs.tsx');
const app = read('App.tsx');
const account = read('components', 'AccountPage.tsx');
const uiStore = read('stores', 'uiStore.ts');
const css = read('index.css');
const mailView = read('components', 'MailView.tsx');
const addr = read('components', 'AddressInput.tsx');
// 「授权多选胶囊」现在住在共用组件里MailView 只是用它)
const chip = read('components', 'Composer.tsx');
test('① 导航只剩 通信/日历/联系人 三项(桌面与窄屏都要)', () => {
for (const [name, src] of [['Sidebar', sidebar], ['NarrowNav', narrow]]) {
// 三项都在
assert.match(src, /短:\s*'通信'|short:\s*'通信'/, `${name} 缺「通信」`);
assert.match(src, /'日历'/, `${name} 缺「日历」`);
assert.match(src, /'联系人'|'联系'/, `${name} 缺「联系人」`);
// 旧的独立项必须消失
for (const gone of ['收件', '发件', '授权请求', '用户管理']) {
assert.ok(!src.includes(`'${gone}'`), `${name} 里还留着旧的独立导航项「${gone}`);
}
}
});
test('① 「通信」的选中态覆盖三个子页签,点击回到上次那个', () => {
assert.match(sidebar, /modes:\s*\['inbox',\s*'sent',\s*'permissions'\]/);
assert.match(narrow, /modes:\s*\['inbox',\s*'sent',\s*'permissions'\]/);
/*
* 判据要的是**行为**:点「通信」(没有明确 target 时)回到上次那个子页签。
*
* 这里原先钉的是一条字面表达式 `setViewMode(target || commTab`,而代码后来写成了
* `setViewMode(target ?? (isComm ? commTab : modes[0]))` —— 行为一样(点通信回 commTab
* 点日历/联系人落到该项第一个模式),字面却不一样,于是判据红、代码没错。
* 钉字面表达式的判据会在每次等价改写时误报;钉"isComm 时落到 commTab"这个行为不会。
*/
assert.match(sidebar, /setViewMode\([^)]*isComm\s*\?\s*commTab/, '桌面点通信要回 commTab');
const desktopHandler = sidebar.match(/setViewMode\([^)]*isComm\s*\?\s*commTab[^)]*\)/)[0];
assert.match(desktopHandler, /target/, '桌面点非通信项时要有明确的 target否则日历/联系人点不动)');
assert.match(narrow, /setViewMode\(isComm \? commTab : mode\)/, '窄屏点通信要回 commTab');
});
test('① 内部导航CommTabs存在、挂在通信页、且带徽标', () => {
assert.match(commTabs, /data-testid="comm-tabs"/);
assert.match(commTabs, /'收件箱'[\s\S]*'发件箱'[\s\S]*'授权'/, '三个内部页签');
assert.match(commTabs, /pendingPerms/, '授权徽标不能因为合并而消失');
assert.match(app, /<CommTabs \/>/, 'App 里要渲染内部导航');
assert.match(app, /const isComm = viewMode === 'inbox'/, '只挂在通信三个页签下');
});
test('① 新建是悬浮圆形加号,且不再是导航项', () => {
assert.match(commTabs, /data-testid="compose-fab"/);
assert.match(commTabs, /rounded-full/, '必须是圆的(用户点名"圆形加号"');
assert.match(commTabs, /absolute bottom-4 right-4/, '悬浮在列表栏右下角');
assert.match(app, /<ComposeFab \/>/, 'App 里要渲染它');
assert.ok(!/新建<\/span>/.test(sidebar), '桌面侧栏不该再有为"新建"的导航文字');
assert.ok(!/新建<\/span>/.test(narrow), '窄屏导航不该再有"新建"');
});
test('② 管理与我的合并:入口在「我的」最下面,且仅管理员可见', () => {
assert.match(account, /user\?\.role === 'admin' &&/, '必须是条件渲染而不是禁用');
const logout = account.indexOf('退出登录');
const adminEntry = account.indexOf("setViewMode('admin')");
assert.ok(logout > 0 && adminEntry > logout, '管理入口要在「登录状态」之后(=页面最下面)');
assert.ok(!/mode: 'admin'/.test(sidebar), '侧栏不该再有独立的 admin 导航项');
assert.ok(!/mode: 'admin'/.test(narrow), '窄屏不该再有独立的 admin 导航项');
});
test('② uiStore 记住通信子页签,且 reset 能清掉', () => {
assert.match(uiStore, /commTab: CommTab/);
assert.match(uiStore, /setCommTab/);
assert.match(uiStore, /commTab: mode as CommTab/, 'setViewMode 要顺手记住');
assert.match(uiStore, /reset:[\s\S]{0,120}commTab: 'inbox'/, '登出后要复位');
});
test('③ 复选框与地址建议用"控件档"透度(比正文面更透)', () => {
/*
* 判据要的是**那颗胶囊的样式**,不是"MailView.tsx 这个文件里出现过 glass-control"。
*
* 授权多选的选项胶囊后来抽成了共用组件 `ComposerChip`MailView 与 Composer 共用),
* 于是"在 MailView.tsx 里 grep glass-control"必然红 —— 而实际样式是对的
* ComposerChip 的 neutral 未选中态就是 glass-control。文件是会搬的
* 组件不会:所以这里先钉"MailView 用的是 ComposerChip",再钉"ComposerChip 用控件档"。
*/
assert.match(mailView, /import \{[^}]*ComposerChip[^}]*\} from '\.\/Composer'/, 'MailView 要用共用的胶囊组件');
assert.match(mailView, /<ComposerChip/, '授权选项必须是那颗胶囊');
assert.match(chip, /glass-control/, '授权多选胶囊ComposerChip 未选中态)要用控件档');
assert.match(addr, /glass-control/, '地址建议菜单');
assert.ok(!/glass-control[^']*bg-white/.test(chip), '胶囊不能再同时写 bg-white');
assert.ok(!/glass-control[^"]*bg-white/.test(addr), '建议菜单不能再同时写 bg-white');
// 三档透度必须真的递减(正文 > 嵌套 > 控件)
const num = re => Number(css.match(re)[1]);
const big = num(/--bg-glass:\s*(0\.\d+)/);
const inner = num(/--bg-glass-inner:\s*(0\.\d+)/);
const ctrl = num(/--bg-glass-control:\s*(0\.\d+)/);
assert.ok(big > inner && inner > ctrl, `三档应递减:${big} > ${inner} > ${ctrl}`);
assert.ok(ctrl <= 0.6, `控件档要明显更透(当前 ${ctrl}`);
});
test('★ 判据自检:拿重构前的写法喂进来必须判红', () => {
const oldSidebar = `{ short: '收件', mode: 'inbox', Icon: InboxIcon },
{ short: '授权', title: '授权请求', mode: 'permissions', Icon: ShieldIcon }`;
assert.ok(oldSidebar.includes("'授权请求'"), '旧写法能被本判据的"必须消失"条款命中');
const oldChip = "'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'";
assert.ok(!/glass-control/.test(oldChip), '旧胶囊写法不含 glass-control ⇒ 会判红');
});