142 lines
4.4 KiB
JavaScript
142 lines
4.4 KiB
JavaScript
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);
|
|
});
|