All checks were successful
Build and Push Docker Image / build (push) Successful in 28s
The repo was authored prettier-style (trailing-comma 'all') but .oxfmtrc.json was set to 'es5', so every formatted file diverged. Switch the config to 'all' to match the existing code, ignore docs/** and **/drizzle/** (prose + generated snapshots the formatter should not own), and reformat the source tree once for consistency. No behavioural change; all suites green (api 60, worker 28, admin 21).
124 lines
4.0 KiB
TypeScript
124 lines
4.0 KiB
TypeScript
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> = {}): 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(
|
|
<QueryClientProvider client={queryClient}>
|
|
<History />
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
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);
|
|
});
|
|
});
|