'use strict'; // 纯逻辑自测(不需要 Electron、不需要显示器): // node test/clock-model.test.js // 走时模型是所有功能的地基,这里把它单独钉死,GUI 的问题就不会和算法问题混在一起。 const clockModel = require('../src/clock-model'); let passed = 0; const failures = []; function check(name, ok, detail) { if (ok) { passed += 1; console.log(`PASS ${name}${detail ? ` [${detail}]` : ''}`); } else { failures.push(name); console.log(`FAIL ${name}${detail ? ` [${detail}]` : ''}`); } } const T0 = 1_700_000_000_000; // 固定基准时刻,测试不依赖真实时钟 const SECOND = 1000; console.log('--- 正常走时 ---'); { const c = clockModel.create(T0); check('初始倍率为 1', c.rate === 1); check('走 1 秒真实时间 = 1 秒显示时间', clockModel.displayMs(c, T0 + SECOND) === T0 + SECOND); check('走 1 小时真实时间 = 1 小时显示时间', clockModel.displayMs(c, T0 + 3600 * SECOND) === T0 + 3600 * SECOND); } { // 分 3600 次推进,验证「基准 + 经过时间 × 倍率」不会像逐帧累加那样漂移 const c = clockModel.create(T0); let now = T0; for (let i = 0; i < 3600; i += 1) { now += SECOND; clockModel.displayMs(c, now); } check('一小时逐秒推进后零漂移', clockModel.displayMs(c, now) === T0 + 3600 * SECOND); } console.log('--- 倍率 ---'); { const c = clockModel.create(T0); clockModel.setRate(c, T0, 2); check('倍率 2:1 秒真实时间走 2 秒', clockModel.displayMs(c, T0 + SECOND) === T0 + 2 * SECOND); } { const c = clockModel.create(T0); clockModel.setRate(c, T0, 0.5); check('倍率 0.5:半速', clockModel.displayMs(c, T0 + SECOND) === T0 + 500); } { const c = clockModel.create(T0); clockModel.setRate(c, T0, -1); check('倍率 -1:走 1 秒真实时间倒退 1 秒', clockModel.displayMs(c, T0 + SECOND) === T0 - SECOND); check('倍率 -1:再走 5 秒仍在继续倒退(不是卡住)', clockModel.displayMs(c, T0 + 6 * SECOND) === T0 - 6 * SECOND); } { const c = clockModel.create(T0); clockModel.setRate(c, T0, -2.5); check('倍率 -2.5:小数 + 负数同时成立', clockModel.displayMs(c, T0 + 4 * SECOND) === T0 - 10 * SECOND); } console.log('--- 切换倍率不跳变 ---'); { const c = clockModel.create(T0); const before = clockModel.displayMs(c, T0 + 3000); clockModel.setRate(c, T0 + 3000, 7); const after = clockModel.displayMs(c, T0 + 3000); check('切倍率的瞬间显示时间连续(同一时刻前后相等)', before === after, `${before} → ${after}`); check('切换后按新倍率走', clockModel.displayMs(c, T0 + 4000) === before + 7 * SECOND); } console.log('--- 暂停 / 继续 ---'); { const c = clockModel.create(T0); clockModel.setRate(c, T0, 4); clockModel.setRate(c, T0 + SECOND, 0); const frozenAt = clockModel.displayMs(c, T0 + SECOND); check('倍率 0:显示时间冻结', clockModel.displayMs(c, T0 + 60 * SECOND) === frozenAt); check('暂停时记住了暂停前的倍率 4', c.lastRate === 4); clockModel.setRate(c, T0 + 60 * SECOND, c.lastRate); check('继续:从暂停处按原倍率接着走', clockModel.displayMs(c, T0 + 61 * SECOND) === frozenAt + 4 * SECOND); } { const c = clockModel.create(T0); clockModel.setRate(c, T0, 0); check('倍率一直是 0 时 lastRate 保持默认 1', c.lastRate === 1); } console.log('--- 快进 / 快退 ---'); { const c = clockModel.create(T0); const before = clockModel.displayMs(c, T0 + 2000); clockModel.shift(c, T0 + 2000, 5 * SECOND); check('+5 秒:显示时间立刻加 5 秒', clockModel.displayMs(c, T0 + 2000) === before + 5 * SECOND); check('快进后基准确实变了', c.baseMs === before + 5 * SECOND && c.baseRealMs === T0 + 2000); check('快进后继续按倍率走', clockModel.displayMs(c, T0 + 3000) === before + 6 * SECOND); } { const c = clockModel.create(T0); clockModel.setRate(c, T0, -1); const before = clockModel.displayMs(c, T0 + 10 * SECOND); clockModel.shift(c, T0 + 10 * SECOND, 30 * 60 * SECOND); check('负倍率下 +30 分钟:先固化显示时间再平移(不与倒转打架)', clockModel.displayMs(c, T0 + 10 * SECOND) === before + 30 * 60 * SECOND); check('平移后负倍率继续生效', clockModel.displayMs(c, T0 + 11 * SECOND) === before + 30 * 60 * SECOND - SECOND); } { const c = clockModel.create(T0); clockModel.setRate(c, T0, 0); const before = clockModel.displayMs(c, T0 + 1000); clockModel.shift(c, T0 + 1000, 10 * 60 * SECOND); check('暂停状态下快进仍然生效,且之后继续冻结', clockModel.displayMs(c, T0 + 60 * SECOND) === before + 10 * 60 * SECOND); } { // 五个快进按钮的档位 const deltas = [5 * SECOND, 60 * SECOND, 5 * 60 * SECOND, 10 * 60 * SECOND, 30 * 60 * SECOND]; const c = clockModel.create(T0); const before = clockModel.displayMs(c, T0); for (const d of deltas) clockModel.shift(c, T0, d); const total = deltas.reduce((a, b) => a + b, 0); check('五档快进累加正确(+5s/+1m/+5m/+10m/+30m)', clockModel.displayMs(c, T0) === before + total, `总计 ${total / 1000} 秒`); } console.log('--- 设定时间 ---'); { const c = clockModel.create(T0); clockModel.setRate(c, T0, 60); clockModel.setTimeOfDay(c, T0 + 5000, 8, 30, 15); const d = new Date(clockModel.displayMs(c, T0 + 5000)); check('设定为 08:30:15 且毫秒归零', d.getHours() === 8 && d.getMinutes() === 30 && d.getSeconds() === 15 && d.getMilliseconds() === 0, `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`); check('设定后高倍率继续生效', clockModel.displayMs(c, T0 + 6000) === d.getTime() + 60 * SECOND); } { const c = clockModel.create(T0); clockModel.setTimeOfDay(c, T0, 0, 0, 0); const d = new Date(clockModel.displayMs(c, T0)); check('设定为 00:00:00(边界)', d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0); } { const c = clockModel.create(T0); clockModel.setTimeOfDay(c, T0, 23, 59, 59); const d = new Date(clockModel.displayMs(c, T0 + 2000)); check('23:59:59 后跨零点进位', d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 1, `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`); } console.log(''); if (failures.length === 0) { console.log(`全部通过:${passed}/${passed}`); process.exit(0); } else { console.log(`通过 ${passed},失败 ${failures.length}:${failures.join(';')}`); process.exit(1); }