2026-06-22 12:26:52 +02:00
|
|
|
/**
|
2026-06-22 13:52:33 +02:00
|
|
|
* Tests for AppSidebar (M5-T01, updated M5-T06).
|
2026-06-22 12:26:52 +02:00
|
|
|
*
|
|
|
|
|
* Strategy: render AppSidebar inside a minimal AppShell (required by
|
|
|
|
|
* AppShell.Navbar) + MemoryRouter so useLocation() works.
|
|
|
|
|
*
|
|
|
|
|
* Coverage:
|
2026-06-22 13:52:33 +02:00
|
|
|
* 1. All four nav items render (Home, Records, Energy, Config).
|
2026-06-22 12:26:52 +02:00
|
|
|
* 2. Home nav item is active when pathname is '/'.
|
|
|
|
|
* 3. Records nav item is active when pathname is '/records'.
|
|
|
|
|
* 4. Config nav item is active when pathname is '/config'.
|
|
|
|
|
* 5. Home is NOT active when on '/records'.
|
2026-06-22 13:52:33 +02:00
|
|
|
* 6. Energy nav item is active when on '/energy'.
|
2026-06-22 12:26:52 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
|
|
|
import { screen } from '@testing-library/react'
|
|
|
|
|
import { render } from '@testing-library/react'
|
|
|
|
|
import { MantineProvider, AppShell } from '@mantine/core'
|
|
|
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
|
|
|
import { MemoryRouter } from 'react-router-dom'
|
|
|
|
|
import { AppSidebar } from './AppSidebar'
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Mock apiClient (LogoutButton calls POST /api/auth/logout)
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
vi.mock('../api/client', () => ({
|
|
|
|
|
default: {
|
|
|
|
|
POST: vi.fn(),
|
|
|
|
|
GET: vi.fn(),
|
|
|
|
|
},
|
|
|
|
|
ApiError: class ApiError extends Error {
|
|
|
|
|
status: number
|
|
|
|
|
body: unknown
|
|
|
|
|
constructor(status: number, body: unknown) {
|
|
|
|
|
super(`API error ${status}`)
|
|
|
|
|
this.name = 'ApiError'
|
|
|
|
|
this.status = status
|
|
|
|
|
this.body = body
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
registerLoginRedirect: vi.fn(),
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Helper: render AppSidebar inside the required AppShell + router providers
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
function renderSidebar(initialPath = '/') {
|
|
|
|
|
const queryClient = new QueryClient({
|
|
|
|
|
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return render(
|
|
|
|
|
<MantineProvider>
|
|
|
|
|
<QueryClientProvider client={queryClient}>
|
|
|
|
|
<MemoryRouter initialEntries={[initialPath]}>
|
|
|
|
|
<AppShell
|
|
|
|
|
navbar={{ width: 220, breakpoint: 'sm', collapsed: { mobile: false } }}
|
|
|
|
|
header={{ height: { base: 48, sm: 0 } }}
|
|
|
|
|
>
|
|
|
|
|
<AppSidebar />
|
|
|
|
|
</AppShell>
|
|
|
|
|
</MemoryRouter>
|
|
|
|
|
</QueryClientProvider>
|
|
|
|
|
</MantineProvider>,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Tests
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
describe('AppSidebar', () => {
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// 1. All nav items render
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
2026-06-22 13:52:33 +02:00
|
|
|
it('renders Home, Records, Energy, and Config nav items', () => {
|
2026-06-22 12:26:52 +02:00
|
|
|
renderSidebar('/')
|
|
|
|
|
expect(screen.getByTestId('nav-home')).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByTestId('nav-records')).toBeInTheDocument()
|
2026-06-22 13:52:33 +02:00
|
|
|
expect(screen.getByTestId('nav-energy')).toBeInTheDocument()
|
2026-06-22 12:26:52 +02:00
|
|
|
expect(screen.getByTestId('nav-config')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// 2. Home active on '/'
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
it('marks Home nav item as active when on "/"', () => {
|
|
|
|
|
renderSidebar('/')
|
|
|
|
|
// Mantine NavLink sets data-active="true" on the active item
|
|
|
|
|
const homeLink = screen.getByTestId('nav-home')
|
|
|
|
|
expect(homeLink).toHaveAttribute('data-active', 'true')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// 3. Records active on '/records'
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
it('marks Records nav item as active when on "/records"', () => {
|
|
|
|
|
renderSidebar('/records')
|
|
|
|
|
const recordsLink = screen.getByTestId('nav-records')
|
|
|
|
|
expect(recordsLink).toHaveAttribute('data-active', 'true')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// 4. Config active on '/config'
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
it('marks Config nav item as active when on "/config"', () => {
|
|
|
|
|
renderSidebar('/config')
|
|
|
|
|
const configLink = screen.getByTestId('nav-config')
|
|
|
|
|
expect(configLink).toHaveAttribute('data-active', 'true')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// 5. Home NOT active on '/records' (exact match guard)
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
it('does NOT mark Home as active when on "/records"', () => {
|
|
|
|
|
renderSidebar('/records')
|
|
|
|
|
const homeLink = screen.getByTestId('nav-home')
|
|
|
|
|
expect(homeLink).not.toHaveAttribute('data-active', 'true')
|
|
|
|
|
})
|
2026-06-22 13:52:33 +02:00
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// 6. Energy active on '/energy'
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
it('marks Energy nav item as active when on "/energy"', () => {
|
|
|
|
|
renderSidebar('/energy')
|
|
|
|
|
const energyLink = screen.getByTestId('nav-energy')
|
|
|
|
|
expect(energyLink).toHaveAttribute('data-active', 'true')
|
|
|
|
|
})
|
2026-06-22 12:26:52 +02:00
|
|
|
})
|