direct-detail-navigation.test.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import { App as AntApp, ConfigProvider } from 'antd';
  2. import { act, render, screen, waitFor } from '@testing-library/react';
  3. import userEvent from '@testing-library/user-event';
  4. import { createMemoryRouter, RouterProvider } from 'react-router-dom';
  5. import { describe, expect, it, vi } from 'vitest';
  6. import { I18nProvider } from '../../src/frontend/i18n/react';
  7. import { api } from '../../src/frontend/services/api';
  8. import { mailhubTheme } from '../../src/frontend/theme';
  9. import type { SendEvent } from '../../src/frontend/types';
  10. import SendingLogs from '../../src/pages/SendingLogs';
  11. describe('Activity direct detail navigation', () => {
  12. it('restores direct-link tabs with history and closes past them to the filtered list', async () => {
  13. const user = userEvent.setup();
  14. mockActivityApis();
  15. const router = renderPage({
  16. initialEntries: ['/overview', '/activity/11?page=2&pageSize=25&status=failed&tab=raw'],
  17. initialIndex: 1
  18. });
  19. expect(await screen.findByRole('tab', { name: '原始日志', selected: true })).not.toBeNull();
  20. await user.click(screen.getByRole('tab', { name: 'Webhook' }));
  21. await waitFor(() => expect(currentTab(router)).toBe('webhooks'));
  22. await user.click(screen.getByRole('tab', { name: '互动跟踪' }));
  23. await waitFor(() => expect(currentTab(router)).toBe('tracking'));
  24. await act(async () => {
  25. await router.navigate(-1);
  26. });
  27. expect(currentTab(router)).toBe('webhooks');
  28. expect(screen.getByRole('tab', { name: 'Webhook', selected: true })).not.toBeNull();
  29. await act(async () => {
  30. await router.navigate(-1);
  31. });
  32. expect(currentTab(router)).toBe('raw');
  33. expect(screen.getByRole('tab', { name: '原始日志', selected: true })).not.toBeNull();
  34. await act(async () => {
  35. await router.navigate(1);
  36. });
  37. expect(currentTab(router)).toBe('webhooks');
  38. await act(async () => {
  39. await router.navigate(1);
  40. });
  41. expect(currentTab(router)).toBe('tracking');
  42. await user.click(screen.getByRole('button', { name: 'Close' }));
  43. await waitFor(() => expect(router.state.location.pathname).toBe('/activity'));
  44. const listParams = new URLSearchParams(router.state.location.search);
  45. expect(listParams.get('page')).toBe('2');
  46. expect(listParams.get('pageSize')).toBe('25');
  47. expect(listParams.get('status')).toBe('failed');
  48. expect(listParams.has('tab')).toBe(false);
  49. await act(async () => {
  50. await router.navigate(-1);
  51. });
  52. expect(router.state.location.pathname).toBe('/overview');
  53. });
  54. it('replaces an untouched direct detail with its list even without prior history', async () => {
  55. const user = userEvent.setup();
  56. mockActivityApis();
  57. const router = renderPage({
  58. initialEntries: ['/activity/11?page=1&pageSize=25'],
  59. initialIndex: 0
  60. });
  61. await screen.findByRole('dialog', { name: '发送详情' });
  62. await user.click(screen.getByRole('button', { name: 'Close' }));
  63. await waitFor(() => expect(router.state.location.pathname).toBe('/activity'));
  64. await act(async () => {
  65. await router.navigate(-1);
  66. });
  67. expect(router.state.location.pathname).toBe('/activity');
  68. });
  69. });
  70. function mockActivityApis() {
  71. vi.spyOn(api, 'events').mockResolvedValue({ events: [sendEvent], total: 1, page: 2, pageSize: 25 });
  72. vi.spyOn(api, 'domains').mockResolvedValue({ domains: [] });
  73. vi.spyOn(api, 'event').mockResolvedValue({ event: sendEvent });
  74. }
  75. function renderPage({ initialEntries, initialIndex }: { initialEntries: string[]; initialIndex: number }) {
  76. const router = createMemoryRouter([
  77. { path: '/activity', element: <SendingLogs /> },
  78. { path: '/activity/:eventId', element: <SendingLogs /> },
  79. { path: '*', element: <div>other</div> }
  80. ], { initialEntries, initialIndex });
  81. render(
  82. <ConfigProvider theme={{ ...mailhubTheme, token: { ...mailhubTheme.token, motion: false } }}>
  83. <AntApp>
  84. <I18nProvider>
  85. <RouterProvider router={router} />
  86. </I18nProvider>
  87. </AntApp>
  88. </ConfigProvider>
  89. );
  90. return router;
  91. }
  92. function currentTab(router: ReturnType<typeof createMemoryRouter>) {
  93. return new URLSearchParams(router.state.location.search).get('tab');
  94. }
  95. const sendEvent: SendEvent = {
  96. id: 11,
  97. userId: 1,
  98. domainId: null,
  99. smtpRelayId: null,
  100. domain: 'example.test',
  101. sender: 'sender@example.test',
  102. recipients: ['recipient@example.test'],
  103. subject: 'Delivery test',
  104. status: 'failed',
  105. detail: '550 mailbox unavailable',
  106. queueId: 'QUEUE-11',
  107. messageId: 'mh-11',
  108. createdAt: '2026-07-14T00:00:00.000Z'
  109. };