/** * 账号选择器的行为测试。 * * 判据集中在三件会**静默出错**的事上: * 1. 只有一个账号时不该出现「全部邮箱」(它是噪声,会让人以为漏了谁) * 2. 从"全部邮箱"切到某个账号后**必须重取收件箱** —— 不重取就会继续显示 * 聚合结果,看起来像切换没生效 * 3. 没有账号时不该渲染这个控件(那时界面该由登录页负责) */ import { describe, expect, it, vi, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import AccountSwitcher from '../../src/components/AccountSwitcher'; import { useAccountStore } from '../../src/stores/accountStore'; import { useMailStore } from '../../src/stores/mailStore'; import { useUIStore } from '../../src/stores/uiStore'; import * as api from '../../src/api/client'; const acct = (id: string, name: string) => ({ id, displayName: name, gateway: 'http://192.168.2.60:8180', token: `tok-${id}`, username: name }); function seed(accounts: ReturnType[], activeId: string) { useAccountStore.setState({ accounts, activeId, loaded: true } as never); } describe('AccountSwitcher', () => { beforeEach(() => { vi.restoreAllMocks(); vi.spyOn(api, 'getInbox').mockResolvedValue({ mails: [], total: 0 } as never); useAccountStore.setState({ accounts: [], activeId: 'all', loaded: true } as never); useMailStore.setState({ inbox: [], accountErrors: [] }); useUIStore.setState({ viewMode: 'inbox' }); }); it('没有账号时不渲染(那时该由登录页负责)', () => { const { container } = render(); expect(container.firstChild).toBeNull(); }); it('★ 只有一个账号时退化成静态标题:不出现「全部邮箱」', async () => { seed([acct('a1', '工作邮箱')], 'a1'); render(); const btn = screen.getByTestId('account-switcher'); expect(btn).toHaveTextContent('工作邮箱'); await userEvent.click(btn); // 点不开:聚合没有意义(一个账号的"全部"就是它自己) expect(screen.queryByTestId('account-menu')).toBeNull(); expect(screen.queryByTestId('account-option-all')).toBeNull(); }); it('两个账号时可以展开,菜单里有「全部邮箱」与各账号', async () => { seed([acct('a1', '工作邮箱'), acct('a2', '私人邮箱')], 'a1'); render(); await userEvent.click(screen.getByTestId('account-switcher')); expect(screen.getByTestId('account-menu')).toBeInTheDocument(); expect(screen.getByTestId('account-option-all')).toHaveTextContent('全部邮箱'); expect(screen.getByTestId('account-option-a1')).toHaveTextContent('工作邮箱'); expect(screen.getByTestId('account-option-a2')).toHaveTextContent('私人邮箱'); }); it('★ 切到某个账号后必须重取收件箱(否则会继续显示聚合结果)', async () => { seed([acct('a1', '工作邮箱'), acct('a2', '私人邮箱')], 'all'); render(); await userEvent.click(screen.getByTestId('account-switcher')); await userEvent.click(screen.getByTestId('account-option-a2')); await waitFor(() => expect(useAccountStore.getState().activeId).toBe('a2')); expect(api.getInbox).toHaveBeenCalled(); }); it('★ 切到「全部邮箱」也要重取(聚合要并发问多个账号)', async () => { seed([acct('a1', '工作邮箱'), acct('a2', '私人邮箱')], 'a1'); render(); await userEvent.click(screen.getByTestId('account-switcher')); await userEvent.click(screen.getByTestId('account-option-all')); await waitFor(() => expect(useAccountStore.getState().activeId).toBe('all')); // 两个可用账号 → 走聚合路径(getInboxWithAuth 每个账号一次) const withAuth = vi.spyOn(api, 'getInboxWithAuth').mockResolvedValue({ mails: [], total: 0 } as never); await useMailStore.getState().fetchInbox('all'); expect(withAuth).toHaveBeenCalledTimes(2); }); it('聚合模式下**只有一个可用账号**时不并发两个请求(第二条是多余失败面)', async () => { useAccountStore.setState({ accounts: [{ ...acct('a1', '工作邮箱') }, { ...acct('a2', '坏账号'), token: '' }], activeId: 'all', loaded: true } as never); const withAuth = vi.spyOn(api, 'getInboxWithAuth').mockResolvedValue({ mails: [], total: 0 } as never); await useMailStore.getState().fetchInbox('all'); expect(withAuth).not.toHaveBeenCalled(); expect(api.getInbox).toHaveBeenCalled(); }); it('★ 某个账号取失败必须在 store 里说出来,而不是静默少一份', async () => { useAccountStore.setState({ accounts: [acct('a1', '工作邮箱'), acct('a2', '私人邮箱')], activeId: 'all', loaded: true } as never); vi.spyOn(api, 'getInboxWithAuth') .mockResolvedValueOnce({ mails: [{ mail_id: 'm1' }], total: 1 } as never) .mockRejectedValueOnce(new Error('HTTP 401:密钥无效')); await useMailStore.getState().fetchInbox('all'); expect(useMailStore.getState().inbox.map((m: any) => m.mail_id)).toEqual(['m1']); expect(useMailStore.getState().accountErrors).toEqual([ { account: '私人邮箱', error: 'HTTP 401:密钥无效' } ]); }); it('「管理账号…」跳到账号页', async () => { seed([acct('a1', '工作邮箱'), acct('a2', '私人邮箱')], 'a1'); render(); await userEvent.click(screen.getByTestId('account-switcher')); await userEvent.click(screen.getByTestId('account-manage')); expect(useUIStore.getState().viewMode).toBe('account'); }); });