fix(worker): show paused time in history; reset to stopwatch on logout
This commit is contained in:
49
apps/worker/src/auth/AuthContext.test.tsx
Normal file
49
apps/worker/src/auth/AuthContext.test.tsx
Normal 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('/');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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);
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user