143 lines
4.9 KiB
JavaScript
143 lines
4.9 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createProgress } from '../lib/progress.mjs';
|
|
|
|
function fixture(t, intervalMs = 5000) {
|
|
t.mock.timers.enable({ apis: ['setInterval'] });
|
|
let time = 0;
|
|
const lines = [];
|
|
const progress = createProgress({ log: line => lines.push(line), intervalMs, now: () => time });
|
|
t.after(() => progress.close());
|
|
return {
|
|
progress, lines,
|
|
setTime(value) { time = value; },
|
|
tick(value) { time += value; t.mock.timers.tick(value); },
|
|
};
|
|
}
|
|
|
|
test('construction stays silent until a stage is available', t => {
|
|
const { progress, lines, tick } = fixture(t);
|
|
tick(15000);
|
|
assert.deepEqual(lines, []);
|
|
progress.stage('正在等待数据库连接');
|
|
assert.deepEqual(lines, ['正在等待数据库连接(已用时 0 秒)']);
|
|
});
|
|
|
|
test('the timer reports a waiting stage at the configured interval', t => {
|
|
const { progress, lines, tick } = fixture(t);
|
|
progress.stage('正在统计历史图片');
|
|
tick(5000);
|
|
tick(5000);
|
|
assert.deepEqual(lines, [
|
|
'正在统计历史图片(已用时 0 秒)',
|
|
'正在统计历史图片(已用时 5 秒)',
|
|
'正在统计历史图片(已用时 10 秒)',
|
|
]);
|
|
});
|
|
|
|
test('frequent updates preserve the newest message without printing every record', t => {
|
|
const { progress, lines, setTime } = fixture(t);
|
|
progress.stage('正在扫描引用');
|
|
for (let count = 1; count <= 1000; count++) {
|
|
setTime(count * 4);
|
|
progress.update('正在扫描引用,已检查 ' + count + ' 行');
|
|
}
|
|
assert.equal(lines.length, 1);
|
|
setTime(5000);
|
|
progress.update('正在扫描引用,已检查 1200 行');
|
|
assert.equal(lines.at(-1), '正在扫描引用,已检查 1200 行(已用时 5 秒)');
|
|
assert.equal(lines.length, 2);
|
|
});
|
|
|
|
test('timer output uses the last update message without resetting stage elapsed time', t => {
|
|
const { progress, lines, tick } = fixture(t);
|
|
progress.stage('正在扫描引用');
|
|
tick(2000);
|
|
progress.update('正在扫描引用,已检查 100 行');
|
|
tick(3000);
|
|
assert.deepEqual(lines, [
|
|
'正在扫描引用(已用时 0 秒)',
|
|
'正在扫描引用,已检查 100 行(已用时 5 秒)',
|
|
]);
|
|
});
|
|
|
|
test('synchronous updates emit progress even when interval callbacks cannot run', t => {
|
|
const { progress, lines, setTime } = fixture(t);
|
|
progress.stage('正在处理清单');
|
|
setTime(4999);
|
|
progress.update('正在处理清单 1');
|
|
assert.equal(lines.length, 1);
|
|
setTime(5000);
|
|
progress.update('正在处理清单 2');
|
|
setTime(10000);
|
|
progress.update('正在处理清单 3');
|
|
assert.deepEqual(lines, [
|
|
'正在处理清单(已用时 0 秒)',
|
|
'正在处理清单 2(已用时 5 秒)',
|
|
'正在处理清单 3(已用时 10 秒)',
|
|
]);
|
|
});
|
|
|
|
test('a timer callback immediately after an update does not duplicate output', t => {
|
|
const { progress, lines, setTime } = fixture(t);
|
|
progress.stage('正在上传');
|
|
setTime(5000);
|
|
progress.update('正在上传,已处理 50 张');
|
|
t.mock.timers.tick(5000);
|
|
assert.equal(lines.length, 2);
|
|
});
|
|
|
|
test('changing stage prints immediately and restarts its elapsed time', t => {
|
|
const { progress, lines, tick } = fixture(t);
|
|
progress.stage('正在统计');
|
|
tick(5000);
|
|
progress.stage('正在上传');
|
|
assert.equal(lines.at(-1), '正在上传(已用时 0 秒)');
|
|
tick(5000);
|
|
assert.equal(lines.at(-1), '正在上传(已用时 5 秒)');
|
|
});
|
|
|
|
test('close is idempotent and prevents timer, stage and update output', t => {
|
|
const { progress, lines, tick } = fixture(t);
|
|
progress.stage('正在扫描');
|
|
progress.close();
|
|
progress.close();
|
|
tick(15000);
|
|
progress.stage('已关闭');
|
|
progress.update('不应输出');
|
|
assert.deepEqual(lines, ['正在扫描(已用时 0 秒)']);
|
|
});
|
|
|
|
test('an update can initialize progress when no explicit stage was set', t => {
|
|
const { progress, lines, tick } = fixture(t);
|
|
progress.update('正在处理');
|
|
tick(5000);
|
|
assert.deepEqual(lines, ['正在处理(已用时 0 秒)', '正在处理(已用时 5 秒)']);
|
|
});
|
|
|
|
test('the interval is unrefed and its exact handle is cleared', t => {
|
|
let callback;
|
|
const handle = { unref: t.mock.fn() };
|
|
const interval = t.mock.method(globalThis, 'setInterval', (fn, ms) => {
|
|
callback = fn;
|
|
assert.equal(ms, 5000);
|
|
return handle;
|
|
});
|
|
const clear = t.mock.method(globalThis, 'clearInterval', () => {});
|
|
const log = t.mock.fn();
|
|
const progress = createProgress({ log, now: () => 0 });
|
|
assert.equal(interval.mock.callCount(), 1);
|
|
assert.equal(handle.unref.mock.callCount(), 1);
|
|
assert.equal(log.mock.callCount(), 0);
|
|
progress.close();
|
|
assert.deepEqual(clear.mock.calls[0].arguments, [handle]);
|
|
callback();
|
|
assert.equal(log.mock.callCount(), 0);
|
|
});
|
|
|
|
test('invalid intervals fail before starting a timer', () => {
|
|
for (const intervalMs of [0, -1, 1.5, NaN, Infinity]) {
|
|
assert.throws(() => createProgress({ intervalMs }), /正整数/);
|
|
}
|
|
});
|