ccpoad / web /assets /js /auto-refresh.test.js
anyalerob's picture
Upload folder using huggingface_hub
2986042 verified
Raw
History Blame Contribute Delete
7.92 kB
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const uiSource = fs.readFileSync(path.join(__dirname, 'ui.js'), 'utf8');
function extractCommonUiHelpers(source) {
const startMarker = '// 公共工具函数(DRY原则:消除重复代码)';
const endMarker = '// 通用可搜索下拉选择框组件 (SearchableCombobox)';
const start = source.indexOf(startMarker);
const end = source.indexOf(endMarker);
assert.notEqual(start, -1, '找不到 ui.js 公共工具函数区块起点');
assert.notEqual(end, -1, '找不到 ui.js 公共工具函数区块终点');
return source.slice(start, end);
}
function makeMemoryStorage() {
const map = new Map();
return {
getItem(k) { return map.has(k) ? map.get(k) : null; },
setItem(k, v) { map.set(k, String(v)); },
removeItem(k) { map.delete(k); },
clear() { map.clear(); }
};
}
function loadAutoRefresh(opts = {}) {
const settingsValue = opts.settingsValue ?? '0';
const settingsError = opts.settingsError === true;
const listeners = new Map();
const intervals = [];
let nextIntervalId = 1;
let nextTimeoutId = 1;
const state = {
hidden: Boolean(opts.initialHidden),
modal: null
};
const document = {
body: { dataset: {} },
readyState: 'complete',
addEventListener(type, handler) {
listeners.set(type, handler);
},
removeEventListener(type, handler) {
if (listeners.get(type) === handler) listeners.delete(type);
},
querySelector(selector) {
if (selector === '.modal.show') return state.modal;
return null;
}
};
Object.defineProperty(document, 'hidden', {
get: () => state.hidden,
configurable: true
});
const fetchCalls = [];
const fetchDataWithAuth = async (url) => {
fetchCalls.push(url);
if (settingsError) throw new Error('boom');
if (url === '/admin/settings') {
return [{ key: 'auto_refresh_interval_seconds', value: settingsValue }];
}
return null;
};
const sandbox = {
console,
document,
window: {
fetchDataWithAuth,
sessionStorage: opts.sessionStorage ?? makeMemoryStorage()
},
setInterval(fn, ms) {
const id = nextIntervalId++;
intervals.push({ id, fn, ms });
return id;
},
clearInterval(id) {
const idx = intervals.findIndex(i => i.id === id);
if (idx >= 0) intervals.splice(idx, 1);
},
setTimeout(fn) {
// 不真正调度,仅返回一个 id
return nextTimeoutId++;
},
clearTimeout() { /* noop */ },
Date,
Number,
JSON,
Math,
Promise,
Array,
Object,
String,
Error
};
vm.createContext(sandbox);
vm.runInContext(extractCommonUiHelpers(uiSource), sandbox);
return {
sandbox,
listeners,
intervals,
fetchCalls,
state,
createAutoRefresh: sandbox.window.createAutoRefresh
};
}
test('createAutoRefresh: 配置 = 0 时不启动 interval, 不注册 visibilitychange', async () => {
const env = loadAutoRefresh({ settingsValue: '0' });
let loadCalls = 0;
const ar = env.createAutoRefresh({ load: () => { loadCalls++; } });
await ar.init();
assert.equal(env.intervals.length, 0, '不应注册 setInterval');
assert.equal(env.listeners.has('visibilitychange'), false, '不应注册 visibilitychange');
assert.equal(loadCalls, 0);
});
test('createAutoRefresh: 配置 > 0 启动 interval, 触发时调用 load', async () => {
const env = loadAutoRefresh({ settingsValue: '30' });
let loadCalls = 0;
const ar = env.createAutoRefresh({ load: () => { loadCalls++; } });
await ar.init();
assert.equal(env.intervals.length, 1, '应注册 1 个 setInterval');
assert.equal(env.intervals[0].ms, 30000, '间隔应为 30000ms');
assert.equal(env.listeners.has('visibilitychange'), true);
// 触发 tick
env.intervals[0].fn();
assert.equal(loadCalls, 1);
env.intervals[0].fn();
assert.equal(loadCalls, 2);
});
test('createAutoRefresh: 配置 > 0 但 document.hidden=true 时跳过 load', async () => {
const env = loadAutoRefresh({ settingsValue: '60' });
let loadCalls = 0;
const ar = env.createAutoRefresh({ load: () => { loadCalls++; } });
await ar.init();
assert.equal(env.intervals.length, 1);
env.state.hidden = true;
env.intervals[0].fn();
assert.equal(loadCalls, 0, 'hidden 状态下不应调用 load');
env.state.hidden = false;
env.intervals[0].fn();
assert.equal(loadCalls, 1);
});
test('createAutoRefresh: .modal.show 存在时跳过 load (保护未保存内容)', async () => {
const env = loadAutoRefresh({ settingsValue: '30' });
let loadCalls = 0;
const ar = env.createAutoRefresh({ load: () => { loadCalls++; } });
await ar.init();
env.state.modal = { id: 'editModal' };
env.intervals[0].fn();
assert.equal(loadCalls, 0, '存在打开的对话框时应跳过');
env.state.modal = null;
env.intervals[0].fn();
assert.equal(loadCalls, 1);
});
test('createAutoRefresh: visibilitychange 隐藏时停止 interval, 恢复时立即 load 并重启', async () => {
const env = loadAutoRefresh({ settingsValue: '45' });
let loadCalls = 0;
const ar = env.createAutoRefresh({ load: () => { loadCalls++; } });
await ar.init();
assert.equal(env.intervals.length, 1);
const handler = env.listeners.get('visibilitychange');
assert.equal(typeof handler, 'function');
// 模拟切到后台
env.state.hidden = true;
handler();
assert.equal(env.intervals.length, 0, '隐藏时应清除 interval');
assert.equal(loadCalls, 0);
// 模拟切回前台
env.state.hidden = false;
handler();
assert.equal(loadCalls, 1, '恢复时应立即触发 load');
assert.equal(env.intervals.length, 1, '恢复时应重启 interval');
assert.equal(env.intervals[0].ms, 45000);
});
test('createAutoRefresh: 拉取设置失败时不启动', async () => {
const env = loadAutoRefresh({ settingsError: true });
let loadCalls = 0;
const ar = env.createAutoRefresh({ load: () => { loadCalls++; } });
await ar.init();
assert.equal(env.intervals.length, 0);
assert.equal(env.listeners.has('visibilitychange'), false);
assert.equal(loadCalls, 0);
});
test('createAutoRefresh: 缺少 load 回调时返回空 noop', async () => {
const env = loadAutoRefresh({ settingsValue: '30' });
const ar = env.createAutoRefresh({});
await ar.init();
assert.equal(env.intervals.length, 0);
assert.equal(env.fetchCalls.length, 0, '无 load 时不应调用接口');
});
test('createAutoRefresh: stop() 清理 interval 与 visibilitychange 监听', async () => {
const env = loadAutoRefresh({ settingsValue: '30' });
const ar = env.createAutoRefresh({ load: () => {} });
await ar.init();
assert.equal(env.intervals.length, 1);
assert.equal(env.listeners.has('visibilitychange'), true);
ar.stop();
assert.equal(env.intervals.length, 0);
assert.equal(env.listeners.has('visibilitychange'), false);
});
test('createAutoRefresh: sessionStorage 60s 缓存命中时不重复请求', async () => {
const sharedStorage = makeMemoryStorage();
// 第一次:拉取并写入缓存
{
const env = loadAutoRefresh({ settingsValue: '30', sessionStorage: sharedStorage });
const ar = env.createAutoRefresh({ load: () => {} });
await ar.init();
assert.equal(env.fetchCalls.length, 1);
}
// 第二次:相同进程的另一次 init,缓存仍在 60s 内,应跳过 /admin/settings
{
const env = loadAutoRefresh({ settingsValue: '999', sessionStorage: sharedStorage });
const ar = env.createAutoRefresh({ load: () => {} });
await ar.init();
assert.equal(env.fetchCalls.length, 0, '缓存应阻止再次请求');
assert.equal(env.intervals.length, 1);
assert.equal(env.intervals[0].ms, 30000, '应使用缓存的 30s 而非新值 999s');
}
});