import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import NavbarClient from '@/components/layout/NavbarClient' import type { Category } from '@/lib/types' const mockPush = vi.fn() vi.mock('next/navigation', () => ({ usePathname: vi.fn(() => '/'), useRouter: vi.fn(() => ({ push: mockPush })), })) const categories: Category[] = [ { id: '1', name: 'Anime', slug: 'anime', description: 'Anime news' }, { id: '2', name: 'Games', slug: 'games', description: null }, { id: '3', name: 'Music', slug: 'music', description: 'Music coverage' }, ] describe('NavbarClient', () => { beforeEach(() => { mockPush.mockReset() document.body.style.overflow = '' }) it('renders all category links', () => { render() expect(screen.getByText('Anime')).toBeInTheDocument() expect(screen.getByText('Games')).toBeInTheDocument() expect(screen.getByText('Music')).toBeInTheDocument() }) it('renders the site logo', () => { render() expect(screen.getByText('言羽')).toBeInTheDocument() }) it('renders a search button', () => { render() expect(screen.getByLabelText('Open search')).toBeInTheDocument() }) it('dispatches custom event when search button is clicked', () => { const dispatchSpy = vi.fn() window.dispatchEvent = dispatchSpy render() fireEvent.click(screen.getByLabelText('Open search')) expect(dispatchSpy).toHaveBeenCalledWith( expect.objectContaining({ type: 'kotobane:open-search' }) ) }) it('shows mobile hamburger menu button', () => { render() expect(screen.getByLabelText('Open menu')).toBeInTheDocument() }) it('opens mobile overlay when hamburger is clicked', () => { render() fireEvent.click(screen.getByLabelText('Open menu')) expect(screen.getByLabelText('Close menu')).toBeInTheDocument() }) it('closes mobile overlay when close button is clicked', () => { render() fireEvent.click(screen.getByLabelText('Open menu')) expect(screen.getByLabelText('Close menu')).toBeInTheDocument() fireEvent.click(screen.getByLabelText('Close menu')) expect(screen.queryByLabelText('Close menu')).not.toBeInTheDocument() }) it('closes mobile overlay when backdrop is clicked', () => { render() fireEvent.click(screen.getByLabelText('Open menu')) const backdrop = document.querySelector('.bg-black\\/60') expect(backdrop).toBeInTheDocument() if (backdrop) fireEvent.click(backdrop) expect(screen.queryByLabelText('Close menu')).not.toBeInTheDocument() }) it('locks body scroll when menu is open, unlocks when closed', () => { render() fireEvent.click(screen.getByLabelText('Open menu')) expect(document.body.style.overflow).toBe('hidden') fireEvent.click(screen.getByLabelText('Close menu')) expect(document.body.style.overflow).toBe('') }) it('marks the current path category as active', async () => { const { usePathname } = await import('next/navigation') vi.mocked(usePathname).mockReturnValue('/anime') render() const animeLink = screen.getByText('Anime').closest('a') expect(animeLink).toHaveClass('text-accent') }) })