test(02-02): add AppLayout test scaffold for NAV-01 through NAV-04
- Mock ResizeObserver and matchMedia for SidebarProvider compatibility - NAV-01: verify sidebar data-sidebar element renders - NAV-02: verify data-testid=sidebar-wordmark gradient element - NAV-03: verify active nav item has data-active=true - NAV-04: verify SidebarTrigger Toggle Sidebar button renders
This commit is contained in:
95
frontend/src/components/AppLayout.test.tsx
Normal file
95
frontend/src/components/AppLayout.test.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { AppLayout } from './AppLayout'
|
||||
import type { AuthContext } from '@/hooks/useAuth'
|
||||
|
||||
// Mock ResizeObserver for sidebar tests
|
||||
globalThis.ResizeObserver = class {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
} as any
|
||||
|
||||
// Mock matchMedia for use-mobile hook used by SidebarProvider
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation((query: string) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
})
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: (key: string) => key }),
|
||||
}))
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
Link: ({ children, to, ...props }: any) => (
|
||||
<a href={to} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
useLocation: () => ({ pathname: '/' }),
|
||||
}))
|
||||
|
||||
const mockAuth: AuthContext = {
|
||||
user: { display_name: 'Test' } as any,
|
||||
loading: false,
|
||||
login: vi.fn(),
|
||||
register: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
token: 'test',
|
||||
refetch: vi.fn(),
|
||||
} as unknown as AuthContext
|
||||
|
||||
describe('AppLayout', () => {
|
||||
it('NAV-01: sidebar element renders with distinct background', () => {
|
||||
render(
|
||||
<AppLayout auth={mockAuth}>
|
||||
<div>content</div>
|
||||
</AppLayout>
|
||||
)
|
||||
// The sidebar renders with data-sidebar="sidebar" -- bg-sidebar token provides pastel background
|
||||
const sidebar = document.querySelector('[data-sidebar="sidebar"]')
|
||||
expect(sidebar).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('NAV-02: gradient wordmark renders in sidebar header', () => {
|
||||
render(
|
||||
<AppLayout auth={mockAuth}>
|
||||
<div>content</div>
|
||||
</AppLayout>
|
||||
)
|
||||
const wordmark = screen.getByTestId('sidebar-wordmark')
|
||||
expect(wordmark).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('NAV-03: active nav item (dashboard at /) has data-active="true"', () => {
|
||||
render(
|
||||
<AppLayout auth={mockAuth}>
|
||||
<div>content</div>
|
||||
</AppLayout>
|
||||
)
|
||||
const dashboardLink = screen.getByRole('link', { name: /nav\.dashboard/i })
|
||||
// SidebarMenuButton sets data-active on itself; it wraps the Link via asChild
|
||||
const menuButton = dashboardLink.closest('[data-active]')
|
||||
expect(menuButton).toHaveAttribute('data-active', 'true')
|
||||
})
|
||||
|
||||
it('NAV-04: SidebarTrigger collapse button is rendered', () => {
|
||||
render(
|
||||
<AppLayout auth={mockAuth}>
|
||||
<div>content</div>
|
||||
</AppLayout>
|
||||
)
|
||||
// SidebarTrigger renders with sr-only text "Toggle Sidebar"
|
||||
const trigger = screen.getByRole('button', { name: /toggle sidebar/i })
|
||||
expect(trigger).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user