Files
MOPC-Portal/tests/unit/live-timer.test.ts

92 lines
2.8 KiB
TypeScript
Raw Normal View History

/**
* Server-stamped phase timer math: every screen (admin, jury, big screen)
* derives the same countdown from cursor timestamps no client-local clocks.
*/
import { describe, it, expect } from 'vitest'
import { elapsedMs, remainingSeconds, formatClock, parseClock } from '@/lib/live-timer'
const t0 = new Date('2026-06-11T10:00:00Z')
const at = (s: number) => new Date(t0.getTime() + s * 1000)
describe('live-timer', () => {
it('elapsedMs counts from phaseStartedAt', () => {
expect(
elapsedMs(
{ phaseStartedAt: t0, phaseDurationSeconds: 300, phasePausedAt: null, phasePausedAccumMs: 0 },
at(90)
)
).toBe(90_000)
})
it('elapsedMs freezes while paused and subtracts accumulated pause', () => {
// paused at +60s, asked at +90s → frozen at 60s
expect(
elapsedMs(
{ phaseStartedAt: t0, phaseDurationSeconds: 300, phasePausedAt: at(60), phasePausedAccumMs: 0 },
at(90)
)
).toBe(60_000)
// resumed with 30s pause accumulated, asked at +120s → 90s elapsed
expect(
elapsedMs(
{ phaseStartedAt: t0, phaseDurationSeconds: 300, phasePausedAt: null, phasePausedAccumMs: 30_000 },
at(120)
)
).toBe(90_000)
})
it('elapsedMs accepts ISO strings (serialized cursor)', () => {
expect(
elapsedMs(
{
phaseStartedAt: t0.toISOString(),
phaseDurationSeconds: 300,
phasePausedAt: null,
phasePausedAccumMs: 0,
},
at(45)
)
).toBe(45_000)
})
it('remainingSeconds goes negative on overtime', () => {
expect(
remainingSeconds(
{ phaseStartedAt: t0, phaseDurationSeconds: 60, phasePausedAt: null, phasePausedAccumMs: 0 },
at(75)
)
).toBe(-15)
})
it('remainingSeconds is null without a running timer', () => {
expect(
remainingSeconds(
{ phaseStartedAt: null, phaseDurationSeconds: null, phasePausedAt: null, phasePausedAccumMs: 0 },
t0
)
).toBeNull()
})
it('formatClock renders m:ss and overtime as +m:ss', () => {
expect(formatClock(305)).toBe('5:05')
expect(formatClock(0)).toBe('0:00')
expect(formatClock(-83)).toBe('+1:23')
})
it('parseClock accepts m:ss, mm:ss and plain minutes', () => {
expect(parseClock('7:30')).toBe(450)
expect(parseClock('12:05')).toBe(725)
expect(parseClock('0:45')).toBe(45)
expect(parseClock('7')).toBe(420) // plain minutes
expect(parseClock(' 3:00 ')).toBe(180) // tolerant of whitespace
})
it('parseClock rejects garbage', () => {
expect(parseClock('')).toBeNull()
expect(parseClock('abc')).toBeNull()
expect(parseClock('5:75')).toBeNull() // seconds must be 0-59
expect(parseClock('-2:00')).toBeNull()
expect(parseClock('1:2:3')).toBeNull()
})
})