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 }), /正整数/); } });