import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; const mockPush = jest.fn(); const mockReplace = jest.fn(); jest.mock('next/navigation', () => ({ usePathname: jest.fn(() => '/'), useSearchParams: jest.fn(() => new URLSearchParams()), useRouter: jest.fn(() => ({ push: mockPush, replace: mockReplace, })), })); jest.mock('next/link', () => { const MockLink = ({ children, href, onClick, ...props }: { children: React.ReactNode; href: string; onClick?: () => void; [key: string]: unknown }) => ( {children} ); MockLink.displayName = 'MockLink'; return MockLink; }); jest.mock('next/image', () => { const MockImage = ({ src, alt, width, height, className, ...props }: { src: string; alt: string; width: number; height: number; className?: string; [key: string]: unknown }) => ( {alt} ); MockImage.displayName = 'MockImage'; return MockImage; }); jest.mock('framer-motion', () => ({ motion: { div: ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => (
{children}
), header: ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => (
{children}
), a: ({ children, className, href, ...props }: { children: React.ReactNode; className?: string; href?: string; [key: string]: unknown }) => ( {children} ), nav: ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => ( ), }, AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}, })); jest.mock('lucide-react', () => ({ Menu: () => , X: () => , MessageCircle: () => , })); jest.mock('@/components/ui/button', () => { const MockButton = ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => ( ); MockButton.displayName = 'MockButton'; return { Button: MockButton }; }); jest.mock('@/lib/site-config', () => ({ useSiteConfig: () => ({ name: '四川睿新致远科技有限公司', shortName: '睿新致遠', displayName: '睿新致远', mainNav: [ { id: 'products', label: '产品', href: '/products', hasDropdown: true, dropdownKey: 'products' }, { id: 'solutions', label: '解决方案', href: '/solutions', hasDropdown: true, dropdownKey: 'solutions' }, { id: 'services', label: '服务', href: '/services' }, { id: 'about', label: '关于我们', href: '/about' }, { id: 'contact', label: '联系我们', href: '/contact' }, ], megaDropdown: { products: [ { id: 'erp', title: 'ERP 管理系统', description: '财务·采购·销售·库存·生产', href: '/products/erp' }, { id: 'crm', title: 'CRM 客户管理', description: '线索·商机·合同·服务', href: '/products/crm' }, ], solutions: [ { id: 'manufacturing', title: '制造业', description: '智能制造·MES·质量管控', href: '/solutions/manufacturing' }, ], }, }), SiteConfigProvider: ({ children }: { children: React.ReactNode }) => <>{children}, })); jest.mock('@/lib/constants', () => ({ COMPANY_INFO: { name: '四川睿新致远科技有限公司', shortName: '睿新致遠', displayName: '睿新致远', }, NAVIGATION_V2: [ { id: 'products', label: '产品', href: '/products', hasDropdown: true, dropdownKey: 'products' }, { id: 'solutions', label: '解决方案', href: '/solutions', hasDropdown: true, dropdownKey: 'solutions' }, { id: 'services', label: '服务', href: '/services' }, { id: 'about', label: '关于我们', href: '/about' }, { id: 'contact', label: '联系我们', href: '/contact' }, ], MEGA_DROPDOWN_DATA: { products: [ { id: 'erp', title: 'ERP 管理系统', description: '财务·采购·销售·库存·生产', href: '/products/erp' }, { id: 'crm', title: 'CRM 客户管理', description: '线索·商机·合同·服务', href: '/products/crm' }, ], solutions: [ { id: 'manufacturing', title: '制造业', description: '智能制造·MES·质量管控', href: '/solutions/manufacturing' }, ], }, })); jest.mock('@/hooks/use-focus-trap', () => ({ useFocusTrap: () => ({ current: null }), })); jest.mock('@/components/layout/mega-dropdown', () => ({ MegaDropdown: ({ label, groups }: { label: string; groups?: Array<{ id: string; title: string; description: string; href: string }> }) => (
{label} {(groups ?? []).map(item => ( {item.title} ))}
), })); import { Header } from './header'; describe('Header', () => { beforeEach(() => { jest.clearAllMocks(); }); afterEach(() => { jest.clearAllMocks(); }); describe('Rendering', () => { it('should render header component', () => { render(
); expect(screen.getByRole('banner')).toBeInTheDocument(); }); it('should render logo', () => { render(
); const logo = screen.getByAltText('四川睿新致远科技有限公司'); expect(logo).toBeInTheDocument(); }); it('should render desktop navigation', () => { render(
); expect(screen.getByTestId('desktop-navigation')).toBeInTheDocument(); }); it('should render navigation items', () => { render(
); expect(screen.getByText('产品')).toBeInTheDocument(); expect(screen.getByText('解决方案')).toBeInTheDocument(); expect(screen.getByText('服务')).toBeInTheDocument(); expect(screen.getByText('关于我们')).toBeInTheDocument(); expect(screen.getByText('联系我们')).toBeInTheDocument(); }); it('should render consult button', () => { render(
); expect(screen.getByTestId('consult-button')).toBeInTheDocument(); }); it('should render mobile menu button', () => { render(
); expect(screen.getByTestId('mobile-menu-button')).toBeInTheDocument(); }); }); describe('Mobile Menu', () => { it('should toggle mobile menu on button click', async () => { render(
); const menuButton = screen.getByTestId('mobile-menu-button'); expect(screen.queryByTestId('mobile-navigation')).not.toBeInTheDocument(); fireEvent.click(menuButton); await waitFor(() => { expect(screen.getByTestId('mobile-navigation')).toBeInTheDocument(); }); fireEvent.click(menuButton); await waitFor(() => { expect(screen.queryByTestId('mobile-navigation')).not.toBeInTheDocument(); }); }); it('should show X icon when menu is open', async () => { render(
); const menuButton = screen.getByTestId('mobile-menu-button'); fireEvent.click(menuButton); await waitFor(() => { expect(screen.getByTestId('x-icon')).toBeInTheDocument(); }); }); it('should show Menu icon when menu is closed', () => { render(
); expect(screen.getByTestId('menu-icon')).toBeInTheDocument(); }); }); describe('Accessibility', () => { it('should have proper ARIA attributes on menu button', () => { render(
); const menuButton = screen.getByTestId('mobile-menu-button'); expect(menuButton).toHaveAttribute('aria-expanded', 'false'); expect(menuButton).toHaveAttribute('aria-controls', 'mobile-menu'); expect(menuButton).toHaveAttribute('aria-label', '打开菜单'); }); it('should update aria-expanded when menu is toggled', async () => { render(
); const menuButton = screen.getByTestId('mobile-menu-button'); fireEvent.click(menuButton); await waitFor(() => { expect(menuButton).toHaveAttribute('aria-expanded', 'true'); expect(menuButton).toHaveAttribute('aria-label', '关闭菜单'); }); }); it('should not double-toggle menu on Enter or Space keydown', () => { render(
); const menuButton = screen.getByTestId('mobile-menu-button'); // Enter/Space 由原生 button 的 click 处理,keydown 不应再次 toggle fireEvent.keyDown(menuButton, { key: 'Enter' }); expect(screen.queryByTestId('mobile-navigation')).not.toBeInTheDocument(); fireEvent.keyDown(menuButton, { key: ' ' }); expect(screen.queryByTestId('mobile-navigation')).not.toBeInTheDocument(); }); it('should have proper navigation role', () => { render(
); const nav = screen.getByTestId('desktop-navigation'); expect(nav).toHaveAttribute('role', 'navigation'); }); }); describe('Navigation', () => { it('should have correct href for navigation items', () => { render(
); const contactLink = screen.getByText('联系我们').closest('a'); const serviceLink = screen.getByText('服务').closest('a'); expect(contactLink).toHaveAttribute('href', '/contact'); expect(serviceLink).toHaveAttribute('href', '/services'); }); }); });