| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- import { App as AntApp, ConfigProvider } from 'antd';
- import { render, screen, waitFor } from '@testing-library/react';
- import userEvent from '@testing-library/user-event';
- import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom';
- import { describe, expect, it, vi } from 'vitest';
- import { AppContext } from '../../src/frontend/app-context';
- import { I18nProvider } from '../../src/frontend/i18n/react';
- import { mailhubTheme } from '../../src/frontend/theme';
- import { AdminLayout, navigationSelection, visibleNavigation } from '../../src/layouts/AdminLayout';
- import type { AppContextValue } from '../../src/frontend/app-context';
- describe('AdminLayout navigation', () => {
- it('hides all system management destinations from a normal user', () => {
- const paths = visibleNavigation(false).flatMap((group) => group.items.map((item) => item.path));
- expect(paths).not.toContain('/admin/users');
- expect(paths).not.toContain('/settings');
- });
- it('maps detail routes back to their owning navigation item', () => {
- expect(navigationSelection('/activity/42')).toBe('/activity');
- expect(navigationSelection('/domains/7/dns')).toBe('/domains');
- expect(navigationSelection('/inbox/messages/9')).toBe('/inbox');
- });
- it('navigates with semantic menu items and exposes a skip link', async () => {
- const user = userEvent.setup();
- renderShell('/overview', { role: 'admin' });
- expect(screen.getByRole('link', { name: '跳至主要内容' }).getAttribute('href')).toBe('#main-content');
- const activityItems = screen.getAllByText('发送活动');
- await user.click(activityItems[0]);
- expect(screen.getByTestId('location').textContent).toBe('/activity');
- await waitFor(() => expect(document.activeElement?.id).toBe('main-content'));
- });
- it('provides an explicit mobile navigation escape action', async () => {
- const user = userEvent.setup();
- renderShell('/overview', { role: 'user' });
- await user.click(screen.getByRole('button', { name: '打开主导航' }));
- const close = await screen.findByRole('button', { name: '关闭主导航' });
- expect(close.style.minHeight).toBe('44px');
- expect(close.style.minWidth).toBe('44px');
- await user.click(close);
- await waitFor(() => expect(screen.queryByRole('button', { name: '关闭主导航' })).toBeNull());
- });
- it('keeps mobile navigation available between Ant Design lg and the 1024px product breakpoint', async () => {
- vi.spyOn(window, 'matchMedia').mockImplementation((query) => mediaQueryList(query === '(min-width: 992px)', query));
- const user = userEvent.setup();
- renderShell('/overview', { role: 'user' });
- await user.click(screen.getByRole('button', { name: '打开主导航' }));
- expect(await screen.findByRole('button', { name: '关闭主导航' })).toBeTruthy();
- });
- });
- function mediaQueryList(matches: boolean, media: string): MediaQueryList {
- return {
- matches,
- media,
- onchange: null,
- addListener: () => undefined,
- removeListener: () => undefined,
- addEventListener: () => undefined,
- removeEventListener: () => undefined,
- dispatchEvent: () => false
- };
- }
- function renderShell(path: string, { role }: { role: 'admin' | 'user' }) {
- const context: AppContextValue = {
- user: {
- id: 1,
- username: 'operator',
- email: 'operator@example.test',
- role,
- status: 'active'
- },
- config: {
- 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
- },
- refreshBootstrap: vi.fn(async () => undefined),
- logout: vi.fn(async () => undefined)
- };
- return render(
- <ConfigProvider theme={mailhubTheme}>
- <AntApp>
- <I18nProvider>
- <AppContext.Provider value={context}>
- <MemoryRouter initialEntries={[path]}>
- <Routes>
- <Route element={<AdminLayout />}>
- <Route path="*" element={<LocationProbe />} />
- </Route>
- </Routes>
- </MemoryRouter>
- </AppContext.Provider>
- </I18nProvider>
- </AntApp>
- </ConfigProvider>
- );
- }
- function LocationProbe() {
- const location = useLocation();
- return <div data-testid="location">{location.pathname}</div>;
- }
|