import { App as AntApp, ConfigProvider } from 'antd';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { AppContext, type AppContextValue } from '../../src/frontend/app-context';
import { I18nProvider } from '../../src/frontend/i18n/react';
import { detailHistoryState } from '../../src/frontend/navigation-state';
import { api } from '../../src/frontend/services/api';
import { mailhubTheme } from '../../src/frontend/theme';
import type { InboundMailbox, InboundMessage, RuntimeConfig } from '../../src/frontend/types';
import Inbox from '../../src/pages/Inbox';
describe('Inbox detail return path', () => {
afterEach(() => vi.restoreAllMocks());
it('keeps the original list history when another message is selected from an open detail', async () => {
const user = userEvent.setup();
const first = messageFixture(9, 1, 'Sent', 'First message');
const second = messageFixture(10, 1, 'Sent', 'Second message');
mockInboxApis([mailboxFixture(1)], [first, second]);
vi.spyOn(api, 'inboundMessage').mockImplementation(async (id) => ({ message: id === second.id ? second : first }));
const listPath = '/inbox?mailboxId=1&folder=Sent&page=2';
const router = createInboxRouter(['/overview', listPath], 1);
renderRouter(router);
await user.click(await screen.findByRole('button', { name: 'First message · sender@example.test' }));
await waitFor(() => expect(router.state.location.pathname).toBe('/inbox/messages/9'));
expect(detailHistoryState(router.state.location.state)).toEqual({ listPath, depth: 1, origin: 'list' });
fireEvent.click(screen.getByRole('button', { name: 'Second message · sender@example.test' }));
await waitFor(() => expect(router.state.location.pathname).toBe('/inbox/messages/10'));
expect(detailHistoryState(router.state.location.state)).toEqual({ listPath, depth: 2, origin: 'list' });
fireEvent.click(screen.getByRole('button', { name: 'Close' }));
await waitFor(() => expect(`${router.state.location.pathname}${router.state.location.search}`).toBe(listPath));
await act(async () => {
await router.navigate(-1);
});
expect(router.state.location.pathname).toBe('/overview');
});
it('keeps direct detail tabs navigable and closes them without reopening on Back', async () => {
const user = userEvent.setup();
const deepLinked = messageFixture(20, 2, 'Archive', 'Archived message');
const messages = vi.fn(async () => ({ messages: [deepLinked], total: 1, page: 1, pageSize: 25 }));
mockInboxApis([mailboxFixture(1), mailboxFixture(2)], [deepLinked]);
vi.spyOn(api, 'inboundMessages').mockImplementation(messages);
vi.spyOn(api, 'inboundMessage').mockResolvedValue({ message: deepLinked });
const router = createInboxRouter(['/overview', '/inbox/messages/20'], 1);
renderRouter(router);
expect(await screen.findByText('Archived message')).toBeTruthy();
await waitFor(() => {
const params = new URLSearchParams(router.state.location.search);
expect(params.get('mailboxId')).toBe('2');
expect(params.get('folder')).toBe('Archive');
});
await waitFor(() => expect(messages).toHaveBeenCalledWith(expect.objectContaining({ mailboxId: 2, folder: 'Archive' })));
await user.click(screen.getByRole('tab', { name: 'HTML 源码' }));
await waitFor(() => expect(new URLSearchParams(router.state.location.search).get('tab')).toBe('html'));
expect(detailHistoryState(router.state.location.state)).toEqual({
listPath: '/inbox?mailboxId=2&folder=Archive',
depth: 1,
origin: 'direct'
});
await act(async () => {
await router.navigate(-1);
});
expect(router.state.location.pathname).toBe('/inbox/messages/20');
expect(new URLSearchParams(router.state.location.search).has('tab')).toBe(false);
expect(detailHistoryState(router.state.location.state)).toBeNull();
await act(async () => {
await router.navigate(1);
});
expect(new URLSearchParams(router.state.location.search).get('tab')).toBe('html');
expect(detailHistoryState(router.state.location.state)?.origin).toBe('direct');
fireEvent.click(screen.getByRole('button', { name: 'Close' }));
await waitFor(() => expect(router.state.location.pathname).toBe('/inbox'));
const params = new URLSearchParams(router.state.location.search);
expect(params.get('mailboxId')).toBe('2');
expect(params.get('folder')).toBe('Archive');
await act(async () => {
await router.navigate(-1);
});
expect(router.state.location.pathname).toBe('/overview');
expect(router.state.location.pathname).not.toContain('/messages/');
});
});
function createInboxRouter(initialEntries: string[], initialIndex: number) {
return createMemoryRouter([
{ path: '/inbox', element:
${subject}
`, rawMessage: `Subject: ${subject}` }; } const runtimeConfig: RuntimeConfig = { appBaseUrl: 'https://mail.example.test', mailHostname: 'mail.example.test', sendingIp: '192.0.2.10', defaultSpfMechanisms: '', dmarcPolicy: 'none', dmarcRua: '', sendRequiresVerified: true, engagementTrackingEnabled: true, listUnsubscribeMailto: '', listUnsubscribeUrl: '', listUnsubscribePostEnabled: false, feedbackIdEnabled: false, reportAbuseTo: '', csaComplaintsTo: '', bounceAddress: '', bounceEnvelopeEnabled: false }; const appContext: AppContextValue = { user: { id: 1, username: 'admin', email: 'admin@example.test', role: 'admin', status: 'active' }, config: runtimeConfig, refreshBootstrap: vi.fn(async () => undefined), logout: vi.fn(async () => undefined) };