Files
MailUI4Agents/client/electron/test/components/Sidebar.test.tsx
JianFeeeee 5ce25f6fdb fix(webui): 修侧栏点击无法翻页 + 回信 UI 统一成一个组件
用户两句:「侧边导航栏完全不可用,点击无法翻页,而且窄屏显示不全」、
「按顺序做吧」(= 做回信 UI 统一)。

## ① 侧栏点击无法翻页(严重,我的错)

导航合并时我把点击目标写成:

    onClick={() => setViewMode(target || commTab || modes[0])}

只有「通信」有意回上次的子页签,而**日历/联系人没有 target** ⇒ 点它们会跳到
`commTab`(收件箱)⇒ 表现为"点了没反应/不翻页"。

修成 `setViewMode(target ?? (isComm ? commTab : modes[0]))`。

**为什么没被拦住**:我的验证全在看**结构与样式**(导航项数、徽标、圆角、玻璃、
对比度),**一次都没点过**。所以补了 `test/components/Sidebar.test.tsx`:点每一项,
断言落到它自己那一项,并带一条反向对照。真浏览器点击也复验:日历→日历页、
联系→联系人、通信→回通信页。

## ② 回信 UI 统一(用户点名的欠账)

新增 `src/components/Composer.tsx`:**形状**(输入区 / 动作行 / 提示)只有一处定义,
**差异**用 `header`(选项胶囊)、`footerExtra`、`submit.tone`、`density`
(compact=批注、roomy=回信正文)条件渲染 —— 差异是数据,不是又一套 UI。

三处各写一套的地方现在都走它:提问型授权表单、审批型(同意/拒绝)、ReplyBar 回信。
输入框的边框/圆角/聚焦环/禁用态从"抄了三遍"变成一处。

**重构时我引入过一个危险的错**:给审批型加了个"提交备注"按钮,兜底用 `options[0]`
(= 同意)⇒ 点一下就**默认批准**。被既有判据当场抓住(`PermissionPanel` 两条红),
已改成"点选项即提交"(`submit` 现在是可选的),并用 `variant="action"`
把同意/拒绝的颜色语义恢复成原来的实心绿 / 浅红。

## ③ 我自己的流程问题(写下来)

- 结构类改动必须配一条"**点它**"的判据 —— 这次就是缺了它。
- 单测里改 store 后必须 `rerender` 再点:否则闭包里是旧值(我第一版因此误判代码有问题)。
- 窄屏"显示不全"**没能复现**:390×844 与 320×568 都量了 —— 无横向溢出、
  内容面板完整落在悬浮导航之上、最后一行完整可见。需要用户指出具体页面。

判据:vitest **15 文件 / 258 用例全绿**(含新增 Sidebar 点击 4 条)。
2026-09-14 12:26:06 +08:00

63 lines
2.8 KiB
TypeScript
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.

import { render, screen, fireEvent } from '@testing-library/react';
import { beforeEach, describe, expect, it } from 'vitest';
import Sidebar from '../../src/components/Sidebar';
import { useUIStore } from '../../src/stores/uiStore';
import { useAuthStore } from '../../src/stores/authStore';
import { useMailStore } from '../../src/stores/mailStore';
import { useContactStore } from '../../src/stores/contactStore';
/*
* 侧栏导航**点了要去对地方**。
*
* 这个判据是补写的:用户报「侧边导航栏完全不可用,点击无法翻页」——
* 根因是「通信」合并成一项后我写成 `setViewMode(target || commTab || modes[0])`
* 而日历/联系人没有 `target` ⇒ 点它们会跳到 `commTab`(收件箱)。
*
* 我当时的验证只看了 DOM 结构与样式(导航项数、徽标、圆角、玻璃),
* **一次都没点过**,所以这个 bug 一路到了线上。结构类判据必须配一条"点它"的判据。
*/
describe('侧栏导航点击目标', () => {
let view: ReturnType<typeof render>;
beforeEach(() => {
useUIStore.getState().reset();
useAuthStore.setState({ user: { username: 'gui-lab', role: 'admin' } as never });
useMailStore.setState({ inbox: [] });
useContactStore.setState({ contacts: [] });
view = render(<Sidebar />);
});
// 按**可见文字**找按钮(不用 getByTitletitle 属性在这个环境里查不到,
// 而按钮文字就是用户点的东西 —— 判据要贴住用户实际看到的那一层)
const click = (label: string) => {
const btn = [...document.querySelectorAll('button')].find(b =>
(b.textContent || '').trim() === label
);
if (!btn) throw new Error(`找不到导航项:${label}(现有:${[...document.querySelectorAll('button')].map(b => b.textContent?.trim()).join('/')}`);
fireEvent.click(btn);
return useUIStore.getState().viewMode;
};
it('点「日历」去日历(不是回通信)', () => {
expect(click('日历')).toBe('calendar');
});
it('点「联系」去联系人', () => {
expect(click('联系')).toBe('contacts');
});
it('点「通信」去通信上次用的子页签', () => {
// ★ 改 store 之后必须**重新渲染**再点:否则组件的点击闭包里还是旧的 commTab
// (我第一版没 rerender拿到 'inbox',差点误判成代码有问题)。
// 这里也说明了另一件事:内部页签渲染在 App 里而不是 Sidebar 里,
// 所以 Sidebar 的单测无法通过"点页签"来设置它。
useUIStore.getState().setViewMode('sent');
view.rerender(<Sidebar />);
expect(click('通信')).toBe('sent');
});
it('★ 反向对照:从日历点「通信」不会停在日历', () => {
useUIStore.getState().setViewMode('calendar');
expect(click('通信')).toBe('inbox');
});
});