import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { WorkSession } from '@solelog/shared'; import History from './History'; import { apiFetch } from '../lib/api'; import { downloadExport } from '../lib/export'; // Mock the network layer so no real requests are made. vi.mock('../lib/api', () => ({ apiFetch: vi.fn(), })); // Mock the authenticated CSV download helper. vi.mock('../lib/export', () => ({ downloadExport: vi.fn(), })); const mockedApiFetch = vi.mocked(apiFetch); const mockedDownloadExport = vi.mocked(downloadExport); function session(overrides: Partial = {}): WorkSession { return { id: 1, user_id: 'u1', activity_id: 1, activity_name: 'Frezen', insole_type: 'Kurk', pair_count: 2, start_time: '2026-01-02T08:30:00.000Z', end_time: '2026-01-02T09:31:01.000Z', duration_seconds: 3661, paused_seconds: 0, paused_at: null, status: 'completed', source: 'app', notes: null, created_at: '2026-01-02T08:30:00.000Z', ...overrides, }; } function renderHistory() { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return render( , ); } describe('History', () => { beforeEach(() => { mockedApiFetch.mockReset(); mockedDownloadExport.mockReset(); mockedApiFetch.mockResolvedValue([]); mockedDownloadExport.mockResolvedValue(undefined); }); afterEach(() => { vi.clearAllMocks(); }); it('renders the header and export button in Dutch', () => { renderHistory(); expect(screen.getByText('Geschiedenis')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Exporteer CSV' })).toBeInTheDocument(); }); it('shows the empty state when there are no sessions', async () => { mockedApiFetch.mockResolvedValue([]); renderHistory(); expect(await screen.findByText('Nog geen opgeslagen sessies.')).toBeInTheDocument(); }); it('renders a session card with activity name, type, count and duration', async () => { mockedApiFetch.mockResolvedValue([session({ duration_seconds: 3661 })]); renderHistory(); expect(await screen.findByText('Frezen')).toBeInTheDocument(); const card = screen.getByText('Frezen').closest('.session-card') as HTMLElement; expect(card).not.toBeNull(); expect(card.textContent).toContain('Kurk'); expect(card.textContent).toContain('2 inlegzolen'); // 3661s -> "1h 1m" (hours present). expect(card.textContent).toContain('1h 1m'); }); it('uses the singular noun for a count of 1', async () => { mockedApiFetch.mockResolvedValue([session({ pair_count: 1 })]); renderHistory(); const card = (await screen.findByText('Frezen')).closest('.session-card') as HTMLElement; expect(card.textContent).toContain('1 inlegzool'); expect(card.textContent).not.toContain('1 inlegzolen'); }); it('renders a Pauze line when paused_seconds > 0', async () => { mockedApiFetch.mockResolvedValue([session({ paused_seconds: 125 })]); renderHistory(); const card = (await screen.findByText('Frezen')).closest('.session-card') as HTMLElement; // 125s -> "2m 5s". expect(card.textContent).toContain('Pauze 2m 5s'); }); it('does not render a Pauze line when paused_seconds is 0', async () => { mockedApiFetch.mockResolvedValue([session({ paused_seconds: 0 })]); renderHistory(); const card = (await screen.findByText('Frezen')).closest('.session-card') as HTMLElement; expect(card.textContent).not.toContain('Pauze'); }); it('triggers the CSV download on Exporteer CSV', async () => { const user = userEvent.setup(); renderHistory(); await user.click(screen.getByRole('button', { name: 'Exporteer CSV' })); expect(mockedDownloadExport).toHaveBeenCalledTimes(1); }); });