feat: pure session clock with pause/resume

This commit is contained in:
2026-07-20 13:43:02 +01:00
parent bf2dacefc1
commit 40ac770304
2 changed files with 58 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
export function initialClock() {
return { startedAt: null, pausedAt: null, pausedAccumMs: 0 };
}
export function start(state, now) {
if (state.startedAt != null) return state;
return { startedAt: now, pausedAt: null, pausedAccumMs: 0 };
}
export function pause(state, now) {
if (state.startedAt == null || state.pausedAt != null) return state;
return { ...state, pausedAt: now };
}
export function resume(state, now) {
if (state.pausedAt == null) return state;
return { ...state, pausedAt: null, pausedAccumMs: state.pausedAccumMs + (now - state.pausedAt) };
}
export function elapsedMs(state, now) {
if (state.startedAt == null) return 0;
const end = state.pausedAt != null ? state.pausedAt : now;
return Math.max(0, end - state.startedAt - state.pausedAccumMs);
}
export function isRunning(state) { return state.startedAt != null && state.pausedAt == null; }
export function isPaused(state) { return state.pausedAt != null; }
+30
View File
@@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest';
import { initialClock, start, pause, resume, elapsedMs, isRunning, isPaused } from '../../src/core/clock.js';
describe('session clock', () => {
it('reads zero before start', () => {
const c = initialClock();
expect(elapsedMs(c, 1000)).toBe(0);
expect(isRunning(c)).toBe(false);
});
it('measures elapsed from start', () => {
const c = start(initialClock(), 1000);
expect(isRunning(c)).toBe(true);
expect(elapsedMs(c, 61000)).toBe(60000); // 1 minute
});
it('freezes while paused and excludes paused time after resume', () => {
let c = start(initialClock(), 0);
c = pause(c, 60000); // paused at 1:00
expect(isPaused(c)).toBe(true);
expect(elapsedMs(c, 120000)).toBe(60000); // frozen at 1:00 while paused
c = resume(c, 180000); // resumed after 2 min paused
expect(elapsedMs(c, 240000)).toBe(120000); // 4 min wall − 2 min paused = 2 min
});
it('ignores double start', () => {
const c = start(start(initialClock(), 100), 5000);
expect(elapsedMs(c, 1100)).toBe(1000);
});
});