import { fireEvent, render, screen, waitFor } from '@testing-library/react' import type { ReactNode } from 'react' import { SWRConfig } from 'swr' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { ItemsPage } from '../src/pages/ItemsPage' import { ApiError } from '../src/api/client' import * as itemsApi from '../src/api/items' import type { Item } from '../src/api/items' /* Page-level wiring through the same seam the hooks tests use: the api module is mocked, everything else (SWR, page states, dialog) is real. */ vi.mock('../src/api/items', () => ({ listItems: vi.fn(), getItem: vi.fn(), createItem: vi.fn(), updateItem: vi.fn(), deleteItem: vi.fn(), })) function item(overrides: Partial = {}): Item { return { id: 1, title: 'First', description: '', status: 'active', createdAt: '2026-09-16T10:00:00Z', updatedAt: '2026-09-16T10:00:00Z', ...overrides, } } function renderPage() { return render( new Map(), dedupingInterval: 0 }}> , ) } beforeEach(() => { vi.mocked(itemsApi.listItems).mockResolvedValue([]) }) afterEach(() => { vi.clearAllMocks() }) describe('ItemsPage states', () => { it('shows a sign-in card when the API answers 401 (OIDC enabled)', async () => { vi.mocked(itemsApi.listItems).mockRejectedValue( new ApiError(401, 'unauthorized', 'sign in via /api/auth/login'), ) renderPage() expect(await screen.findByText('Sign in required')).toBeInTheDocument() expect(screen.getByRole('button', { name: 'Sign in' })).toBeInTheDocument() expect(screen.queryByText('Cannot reach the Go API')).not.toBeInTheDocument() }) it('shows the backend-down card with a retry when the API is unreachable', async () => { vi.mocked(itemsApi.listItems).mockRejectedValue( new ApiError(0, 'network', 'Could not reach the API (fetch failed)'), ) renderPage() expect(await screen.findByText('Cannot reach the Go API')).toBeInTheDocument() expect(screen.getByRole('button', { name: /Retry/ })).toBeInTheDocument() }) it('renders the empty state and creates an item through the dialog', async () => { vi.mocked(itemsApi.createItem).mockResolvedValue(item({ title: 'From dialog' })) renderPage() // Empty state with its own CTA (the toolbar has a second "New item"). expect(await screen.findByText('No items yet')).toBeInTheDocument() const cta = screen.getAllByRole('button', { name: /New item/ }).at(-1)! fireEvent.click(cta) // Dialog: fill the form and submit. expect(await screen.findByRole('dialog')).toBeInTheDocument() fireEvent.change(screen.getByLabelText(/Title/), { target: { value: 'From dialog' } }) fireEvent.click(screen.getByRole('button', { name: /Create item/ })) await waitFor(() => expect(itemsApi.createItem).toHaveBeenCalledWith({ title: 'From dialog', description: undefined }), ) // Success closes the dialog and revalidates the list. await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()) expect(itemsApi.listItems).toHaveBeenCalledTimes(2) }) })