test: add comprehensive unit tests for all components, API routes, and lib functions
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
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(<NavbarClient categories={categories} />)
|
||||
expect(screen.getByText('Anime')).toBeInTheDocument()
|
||||
expect(screen.getByText('Games')).toBeInTheDocument()
|
||||
expect(screen.getByText('Music')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the site logo', () => {
|
||||
render(<NavbarClient categories={categories} />)
|
||||
expect(screen.getByText('言羽')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders a search button', () => {
|
||||
render(<NavbarClient categories={categories} />)
|
||||
expect(screen.getByLabelText('Open search')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('dispatches custom event when search button is clicked', () => {
|
||||
const dispatchSpy = vi.fn()
|
||||
window.dispatchEvent = dispatchSpy
|
||||
|
||||
render(<NavbarClient categories={categories} />)
|
||||
fireEvent.click(screen.getByLabelText('Open search'))
|
||||
|
||||
expect(dispatchSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: 'kotobane:open-search' })
|
||||
)
|
||||
})
|
||||
|
||||
it('shows mobile hamburger menu button', () => {
|
||||
render(<NavbarClient categories={categories} />)
|
||||
expect(screen.getByLabelText('Open menu')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('opens mobile overlay when hamburger is clicked', () => {
|
||||
render(<NavbarClient categories={categories} />)
|
||||
fireEvent.click(screen.getByLabelText('Open menu'))
|
||||
expect(screen.getByLabelText('Close menu')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('closes mobile overlay when close button is clicked', () => {
|
||||
render(<NavbarClient categories={categories} />)
|
||||
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(<NavbarClient categories={categories} />)
|
||||
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(<NavbarClient categories={categories} />)
|
||||
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(<NavbarClient categories={categories} />)
|
||||
const animeLink = screen.getByText('Anime').closest('a')
|
||||
expect(animeLink).toHaveClass('text-accent')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user