fix(worker): show paused time in history; reset to stopwatch on logout

This commit is contained in:
Bas van Rossem
2026-06-17 21:10:01 +02:00
parent ce396ecf2d
commit 1765f4036c
4 changed files with 74 additions and 1 deletions

View File

@@ -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 (
<button type="button" onClick={signOut}>
Uitloggen
</button>
);
}
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(
<AuthProvider>
<SignOutButton />
</AuthProvider>
);
await user.click(screen.getByRole('button', { name: 'Uitloggen' }));
expect(window.location.pathname).toBe('/');
});
});

View File

@@ -19,6 +19,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, []); }, []);
const signOut = useCallback(() => { const signOut = useCallback(() => {
// Reset the router path so the next authed mount lands on Stopwatch, not Account.
window.history.replaceState(null, '', '/');
clearToken(); clearToken();
setIsAuthed(false); setIsAuthed(false);
}, []); }, []);

View File

@@ -31,6 +31,8 @@ function session(overrides: Partial<WorkSession> = {}): WorkSession {
start_time: '2026-01-02T08:30:00.000Z', start_time: '2026-01-02T08:30:00.000Z',
end_time: '2026-01-02T09:31:01.000Z', end_time: '2026-01-02T09:31:01.000Z',
duration_seconds: 3661, duration_seconds: 3661,
paused_seconds: 0,
paused_at: null,
status: 'completed', status: 'completed',
source: 'app', source: 'app',
notes: null, notes: null,
@@ -44,7 +46,7 @@ function renderHistory() {
return render( return render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<History /> <History />
</QueryClientProvider>, </QueryClientProvider>
); );
} }
@@ -94,6 +96,23 @@ describe('History', () => {
expect(card.textContent).not.toContain('1 inlegzolen'); 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 () => { it('triggers the CSV download on Exporteer CSV', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
renderHistory(); renderHistory();

View File

@@ -45,6 +45,9 @@ function SessionCard({ session }: { session: WorkSession }) {
{session.pair_count} {noun} {session.pair_count} {noun}
</span> </span>
<span className="pill pill-grey">{formatDuration(session.duration_seconds)}</span> <span className="pill pill-grey">{formatDuration(session.duration_seconds)}</span>
{session.paused_seconds > 0 && (
<span className="pill pill-grey">Pauze {formatDuration(session.paused_seconds)}</span>
)}
</div> </div>
</li> </li>
); );