/** * 授权页:**新到手的待决请求必须自己露出来**。 * * 这一条来自线上实测的报告:"我点到授权界面,才更新显示授权请求"。 * 复现出来的是:徽标数字实时变了(推送没问题),但新请求的正文看不见 —— * 因为 `expanded` 一旦被手动点过就冻结成快照,之后新出现的待决会话 * 一直处于折叠状态,直到离开再回来(组件重挂载、默认展开重算)。 * * 两条判据必须成对:新请求要自动展开,而用户在那之后的手动折叠**不许**被弹开。 */ import { describe, expect, it, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import PermissionList from '../../src/components/PermissionList'; import { useMailStore } from '../../src/stores/mailStore'; import { useSessionStore } from '../../src/stores/sessionStore'; import { useUIStore } from '../../src/stores/uiStore'; const perm = (sessionId: string, subject: string, id: string) => ({ mail_id: id, session_id: sessionId, from_name: 'dsh', to_name: 'gui-lab', subject, body: '工具:bash', mail_type: 'permission_request' as const, permission_options: ['同意', '拒绝'], permission_multi_select: false, permission_result: '', status: 'unread', created_at: '2026-09-13T01:00:00Z', session_alias: 'sess-' + sessionId, session_workspace: '/tmp/' + sessionId }); describe('授权页的自动展开', () => { beforeEach(() => { useMailStore.setState({ inbox: [], currentMail: null, accountErrors: [] } as never); useSessionStore.setState({ current: null } as never); useUIStore.setState({ viewMode: 'permissions' }); }); it('★ 新到手的待决请求要自动展开(不能等重进页面)', async () => { // 会话 A 已有待决 → 自动展开 useMailStore.setState({ inbox: [perm('sess-a', 'A 的旧请求', 'm-a1')] } as never); render(); expect(await screen.findByText('A 的旧请求')).toBeInTheDocument(); // 用户手动折叠一次 —— 这一步把 expanded 冻结成快照(线上就是这样) // 点会话分组头:按内容找按钮(别名与时间连在同一串文本里, // 用 getByText 会命中不到;/待决策/ 又会同时命中顶部计数) const headerA = screen.getAllByRole('button').find(b => (b.textContent || '').includes('sess-sess-a')); expect(headerA).toBeTruthy(); await userEvent.click(headerA!); await waitFor(() => expect(screen.queryByText('A 的旧请求')).toBeNull()); // 新请求到达(另一个会话,此前没有待决) useMailStore.setState({ inbox: [perm('sess-a', 'A 的旧请求', 'm-a1'), perm('sess-b', 'B 的新请求', 'm-b1')] } as never); // ★ 必须自己露出来;同时 A 保持用户折叠的状态(不能被弹开) expect(await screen.findByText('B 的新请求')).toBeInTheDocument(); expect(screen.queryByText('A 的旧请求')).toBeNull(); }); it('★ 同一个已折叠的会话里又来一条新待决 → 也要露出来(实测撞到的就是这种)', async () => { useMailStore.setState({ inbox: [perm('sess-a', 'A 的旧请求', 'm-a1')] } as never); render(); expect(await screen.findByText('A 的旧请求')).toBeInTheDocument(); const hdr = screen.getAllByRole('button').find(b => (b.textContent || '').includes('sess-sess-a')); await userEvent.click(hdr!); await waitFor(() => expect(screen.queryByText('A 的旧请求')).toBeNull()); // 同一会话又来一条(会话早就在折叠状态里)—— 必须自己打开 useMailStore.setState({ inbox: [perm('sess-a', 'A 的旧请求', 'm-a1'), perm('sess-a', 'A 的新一条', 'm-a2')] } as never); expect(await screen.findByText('A 的新一条')).toBeInTheDocument(); }); it('反向对照:没有新邮件时,手动折叠的分组保持折叠(尊重人的操作)', async () => { useMailStore.setState({ inbox: [perm('sess-a', 'A 的旧请求', 'm-a1')] } as never); render(); expect(await screen.findByText('A 的旧请求')).toBeInTheDocument(); const hdr = screen.getAllByRole('button').find(b => (b.textContent || '').includes('sess-sess-a')); await userEvent.click(hdr!); await waitFor(() => expect(screen.queryByText('A 的旧请求')).toBeNull()); // 只重渲染、没有新邮件 → 不该被弹开 useMailStore.setState({ inbox: [perm('sess-a', 'A 的旧请求', 'm-a1')] } as never); await new Promise(r => setTimeout(r, 80)); expect(screen.queryByText('A 的旧请求')).toBeNull(); }); });