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