Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/lib/free-tools/round-timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,14 @@ export function settlePrep(state: RoundState, now: number): RoundState {
return a === state.prep.a && b === state.prep.b ? state : { ...state, prep: { a, b } };
}

/** Moves to another segment with a fresh, paused speech clock. Prep banks carry over. */
/**
* Moves to another segment with a fresh, paused speech clock. Prep banks carry
* over. Asking for the segment already shown, including past either end of the
* round, changes nothing, so a stray arrow key never wipes a running speech.
*/
export function goToSegment(state: RoundState, format: RoundFormat, index: number, now: number): RoundState {
const segmentIndex = Math.min(Math.max(0, index), format.segments.length - 1);
if (segmentIndex === state.segmentIndex) return state;
return { segmentIndex, speech: PAUSED, prep: pausePrep(state.prep, now) };
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/tools/ToolsHubPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function ToolsHubPage() {
<ToolPageLayout
route="/tools"
title="Free debate tools"
intro={<p>Tools for debate practice that work in any browser and need no account. Your rounds and drafts are saved in your browser, not on a server. Use them in a club session, on a projector in class, or embedded on your own site.</p>}
intro={<p>Tools for debate practice that work in any browser and need no account. The timer saves your round in this browser, not on a server. Use them in a club session, on a projector in class, or embedded on your own site.</p>}
shortAnswer={{ question: t('shortAnswer.hub.question'), answer: t('shortAnswer.hub.answer', { total: PRACTICE_MOTIONS.length }) }}
structuredData={hubStructuredData(FREE_TOOLS.map(({ route, name }) => ({ route, name })))}
tool={(
Expand Down
10 changes: 10 additions & 0 deletions src/test/free-tools/round-timer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ describe('round timer', () => {
expect(prepRemainingMs(state, 'a', 10 * MIN)).toBe(210 * S);
});

it('keeps the running speech when navigation is already at the first or last speech', () => {
// The arrow keys can ask for a speech past either end; that must not wipe the clock.
const running = toggleSpeech(initialRoundState(FORMATS.pf), 0);
expect(goToSegment(running, FORMATS.pf, -1, 30 * S)).toBe(running);
const last = toggleSpeech(goToSegment(running, FORMATS.pf, 10, 0), 0);
expect(last.segmentIndex).toBe(10);
expect(goToSegment(last, FORMATS.pf, 11, 30 * S)).toBe(last);
expect(elapsedMs(goToSegment(last, FORMATS.pf, 11, 30 * S).speech, 30 * S)).toBe(30 * S);
});

it('restores a stored round only when it fits the format', () => {
const running = toggleSpeech(goToSegment(initialRoundState(FORMATS.pf), FORMATS.pf, 4, 0), 1234);
const stored = JSON.parse(JSON.stringify(running));
Expand Down
Loading