用户两句:「侧边导航栏完全不可用,点击无法翻页,而且窄屏显示不全」、
「按顺序做吧」(= 做回信 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 条)。
63 lines
2.8 KiB
TypeScript
63 lines
2.8 KiB
TypeScript
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 />);
|
||
});
|
||
|
||
// 按**可见文字**找按钮(不用 getByTitle:title 属性在这个环境里查不到,
|
||
// 而按钮文字就是用户点的东西 —— 判据要贴住用户实际看到的那一层)
|
||
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');
|
||
});
|
||
});
|