feat(worker): poll + reconcile stopwatch to server (admin stop/cancel)

This commit is contained in:
Bas van Rossem
2026-06-17 22:50:31 +02:00
parent f1ec249ee7
commit 7d3daaa760
3 changed files with 95 additions and 5 deletions

View File

@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { Activity, WorkSession } from '@solelog/shared';
import Stopwatch from './Stopwatch';
import { ApiError } from '../lib/api';
import { useActivities } from '../api/activities';
import {
useActiveSessions,
@@ -247,4 +248,52 @@ describe('Stopwatch', () => {
expect(await screen.findByText('Gepauzeerd — tik om te hervatten')).toBeInTheDocument();
});
it('resets and shows a notice when the running session is stopped by the admin', async () => {
// Start with the session present so the screen adopts the running state.
mockedUseActiveSessions.mockReturnValue(
query<ReturnType<typeof useActiveSessions>>([activeSession()]),
);
const { rerender } = renderStopwatch();
// Confirm we are running (the Stop & Opslaan button is shown).
await screen.findByRole('button', { name: 'Stop & Opslaan' });
// The admin stops the session: the active list now no longer contains it.
mockedUseActiveSessions.mockReturnValue(query<ReturnType<typeof useActiveSessions>>([]));
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
rerender(
<QueryClientProvider client={queryClient}>
<Stopwatch />
</QueryClientProvider>,
);
// The stopwatch resets to idle and shows the admin-stopped notice.
expect(
await screen.findByText('Deze sessie is door de beheerder gestopt.'),
).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Start Stopwatch' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Stop & Opslaan' })).not.toBeInTheDocument();
});
it('resets locally when stop fails with a 409 (already closed)', async () => {
const user = userEvent.setup();
stopMutate.mockImplementation((_id: number, opts?: { onError?: (e: unknown) => void }) => {
opts?.onError?.(new ApiError(409, 'Conflict'));
});
mockedUseActiveSessions.mockReturnValue(
query<ReturnType<typeof useActiveSessions>>([activeSession()]),
);
renderStopwatch();
const stopBtn = await screen.findByRole('button', { name: 'Stop & Opslaan' });
await user.click(stopBtn);
expect(stopMutate).toHaveBeenCalledTimes(1);
// The timer resets to idle despite the 409 (no stuck running state).
await waitFor(() =>
expect(screen.getByRole('button', { name: 'Start Stopwatch' })).toBeInTheDocument(),
);
expect(screen.queryByRole('button', { name: 'Stop & Opslaan' })).not.toBeInTheDocument();
});
});