diff --git a/apps/worker/src/auth/AuthContext.test.tsx b/apps/worker/src/auth/AuthContext.test.tsx new file mode 100644 index 0000000..42eb116 --- /dev/null +++ b/apps/worker/src/auth/AuthContext.test.tsx @@ -0,0 +1,49 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { AuthProvider, useAuth } from './AuthContext'; +import { clearToken, setToken } from '../lib/auth-storage'; + +// The sign-in network call is irrelevant here; stub it so nothing hits the network. +vi.mock('../lib/api', () => ({ + signIn: vi.fn(), +})); + +function SignOutButton() { + const { signOut } = useAuth(); + return ( + + ); +} + +describe('AuthContext signOut', () => { + beforeEach(() => { + clearToken(); + window.history.replaceState(null, '', '/'); + }); + + afterEach(() => { + vi.clearAllMocks(); + window.history.replaceState(null, '', '/'); + }); + + it('resets the path to / so re-login lands on the Stopwatch', async () => { + const user = userEvent.setup(); + setToken('tok'); + // Simulate logging out while sitting on the Account tab. + window.history.replaceState(null, '', '/account'); + expect(window.location.pathname).toBe('/account'); + + render( + + + + ); + + await user.click(screen.getByRole('button', { name: 'Uitloggen' })); + + expect(window.location.pathname).toBe('/'); + }); +}); diff --git a/apps/worker/src/auth/AuthContext.tsx b/apps/worker/src/auth/AuthContext.tsx index e9ae72d..e2dda0f 100644 --- a/apps/worker/src/auth/AuthContext.tsx +++ b/apps/worker/src/auth/AuthContext.tsx @@ -19,6 +19,8 @@ export function AuthProvider({ children }: { children: ReactNode }) { }, []); const signOut = useCallback(() => { + // Reset the router path so the next authed mount lands on Stopwatch, not Account. + window.history.replaceState(null, '', '/'); clearToken(); setIsAuthed(false); }, []); diff --git a/apps/worker/src/screens/History.test.tsx b/apps/worker/src/screens/History.test.tsx index fae0806..a3c370c 100644 --- a/apps/worker/src/screens/History.test.tsx +++ b/apps/worker/src/screens/History.test.tsx @@ -31,6 +31,8 @@ function session(overrides: Partial = {}): WorkSession { 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, @@ -44,7 +46,7 @@ function renderHistory() { return render( - , + ); } @@ -94,6 +96,23 @@ describe('History', () => { 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(); diff --git a/apps/worker/src/screens/History.tsx b/apps/worker/src/screens/History.tsx index 6aa0d61..b905ba3 100644 --- a/apps/worker/src/screens/History.tsx +++ b/apps/worker/src/screens/History.tsx @@ -45,6 +45,9 @@ function SessionCard({ session }: { session: WorkSession }) { {session.pair_count} {noun} {formatDuration(session.duration_seconds)} + {session.paused_seconds > 0 && ( + Pauze {formatDuration(session.paused_seconds)} + )} );