webtools/site/countdown/timer.test.mjs

143 lines
4.4 KiB
JavaScript
Raw Normal View History

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { Countdown, integer, splitSeconds } from './timer.mjs';
function fixture() {
let now = 1000;
return {
timer: new Countdown(() => now),
advance(ms) { now += ms; },
};
}
test('starts idle at zero and accepts nonnegative integers only', () => {
const { timer } = fixture();
assert.equal(timer.state, 'idle');
assert.deepEqual(timer.display(), { minutes: '0', seconds: '00' });
for (const invalid of ['-1', '1.5', '1e2', 'abc', '']) {
assert.equal(integer(invalid), 0n);
}
assert.equal(integer('00090'), 90n);
});
test('normalizes overflowing seconds and preserves arbitrarily large integers', () => {
const { timer } = fixture();
timer.edit('0', '90');
timer.toggle();
assert.deepEqual(timer.display(), { minutes: '1', seconds: '30' });
const minutes = '123456789012345678901234567890';
timer.reset();
timer.edit(minutes, '120');
assert.deepEqual(timer.display(), { minutes: String(BigInt(minutes) + 2n), seconds: '00' });
assert.deepEqual(splitSeconds(61n), { minutes: '1', seconds: '01' });
});
test('uses elapsed time, rounding up, even when refreshes are delayed', () => {
const { timer, advance } = fixture();
timer.edit('0', '90');
timer.toggle();
advance(999);
timer.update();
assert.deepEqual(timer.display(), { minutes: '1', seconds: '30' });
advance(1);
timer.update();
assert.deepEqual(timer.display(), { minutes: '1', seconds: '29' });
advance(75000);
timer.update();
assert.deepEqual(timer.display(), { minutes: '0', seconds: '14' });
});
test('pause/resume preserves fractional seconds and excludes paused time', () => {
const { timer, advance } = fixture();
timer.edit('0', '10');
timer.toggle();
advance(1250);
timer.toggle();
assert.equal(timer.state, 'paused');
assert.equal(timer.remaining, 8750n);
advance(100000);
timer.update();
assert.equal(timer.remaining, 8750n);
timer.toggle();
advance(749);
timer.update();
assert.equal(timer.display().seconds, '09');
advance(1);
timer.update();
assert.equal(timer.display().seconds, '08');
});
test('reset restores normalized last input while running or paused', () => {
const { timer, advance } = fixture();
timer.edit('2', '90');
for (const pause of [false, true]) {
timer.toggle();
advance(1500);
if (pause) timer.toggle();
timer.reset();
assert.equal(timer.state, 'idle');
assert.deepEqual(timer.display(), { minutes: '3', seconds: '30' });
assert.equal(timer.finished, false);
}
});
test('editing a paused timer establishes a new countdown and reset value', () => {
const { timer, advance } = fixture();
timer.edit('0', '10');
timer.toggle();
advance(2000);
timer.toggle();
timer.edit('0', '90');
assert.equal(timer.state, 'idle');
timer.toggle();
advance(3000);
timer.reset();
assert.deepEqual(timer.display(), { minutes: '1', seconds: '30' });
});
test('running countdown cannot be edited', () => {
const { timer } = fixture();
timer.edit('0', '10');
timer.toggle();
timer.edit('5', '0');
assert.equal(timer.duration, 10000n);
assert.equal(timer.state, 'running');
});
test('completion returns to idle and Start restarts the original duration', () => {
const { timer, advance } = fixture();
timer.edit('0', '2');
timer.toggle();
advance(5000);
timer.update();
assert.equal(timer.state, 'idle');
assert.equal(timer.finished, true);
assert.equal(timer.display().seconds, '00');
timer.toggle();
assert.equal(timer.finished, false);
assert.equal(timer.state, 'running');
assert.equal(timer.display().seconds, '02');
});
test('editing or resetting after completion clears the completion flag', () => {
const { timer } = fixture();
timer.toggle();
assert.equal(timer.finished, true);
timer.edit('0', '3');
assert.equal(timer.finished, false);
timer.toggle();
timer.reset();
assert.equal(timer.finished, false);
assert.equal(timer.display().seconds, '03');
});
test('stopping after the deadline completes instead of leaving a paused zero', () => {
const { timer, advance } = fixture();
timer.edit('0', '1');
timer.toggle();
advance(1000);
timer.toggle();
assert.equal(timer.state, 'idle');
assert.equal(timer.finished, true);
});