135 lines
6.6 KiB
JavaScript
135 lines
6.6 KiB
JavaScript
// 设置模块单元测试:默认布局 / 应用定位 / 进入 / 确定保存 / 恢复默认
|
||
const noop = () => { };
|
||
|
||
class El {
|
||
constructor(tag = 'DIV') {
|
||
this.tagName = tag.toUpperCase();
|
||
this.value = '';
|
||
this.textContent = '';
|
||
this._html = '';
|
||
this._classes = new Set();
|
||
this._style = {};
|
||
this.dataset = {};
|
||
this.children = [];
|
||
this._ev = {};
|
||
this.parent = null;
|
||
const self = this;
|
||
this.classList = {
|
||
add: c => self._classes.add(c),
|
||
remove: c => self._classes.delete(c),
|
||
contains: c => self._classes.has(c),
|
||
toggle: (c, f) => { const on = f === undefined ? !self._classes.has(c) : f; on ? self._classes.add(c) : self._classes.delete(c); return on; },
|
||
};
|
||
}
|
||
get style() { return this._style; }
|
||
set className(v) { this._classes = new Set(String(v).split(/\s+/).filter(Boolean)); }
|
||
get className() { return [...this._classes].join(' '); }
|
||
set innerHTML(v) { this._html = v; if (v === '') this.children = []; }
|
||
get innerHTML() { return this._html; }
|
||
appendChild(c) { c.parent = this; this.children.push(c); return c; }
|
||
remove() { if (this.parent) { const i = this.parent.children.indexOf(this); if (i >= 0) this.parent.children.splice(i, 1); } }
|
||
querySelector() { return new El(); }
|
||
querySelectorAll(sel) { return sel === '.tc-btn' ? this.children.filter(c => c._classes.has('tc-btn')) : []; }
|
||
addEventListener(type, fn) { (this._ev[type] ||= []).push(fn); }
|
||
removeEventListener() { }
|
||
dispatch(type, ev) { (this._ev[type] || []).forEach(fn => fn(ev)); }
|
||
setPointerCapture() { }
|
||
releasePointerCapture() { }
|
||
getBoundingClientRect() { return { left: 0, top: 0, width: 800, height: 608 }; }
|
||
}
|
||
|
||
const byId = new Map();
|
||
const touchMap = new Map();
|
||
globalThis.localStorage = {
|
||
_d: {},
|
||
getItem(k) { return k in this._d ? this._d[k] : null; },
|
||
setItem(k, v) { this._d[k] = String(v); },
|
||
removeItem(k) { delete this._d[k]; },
|
||
};
|
||
globalThis.document = {
|
||
getElementById(id) { if (!byId.has(id)) byId.set(id, new El()); return byId.get(id); },
|
||
createElement(t) { return new El(t); },
|
||
querySelector(sel) {
|
||
const m = sel.match(/data-touch="([^"]+)"/);
|
||
if (m) { const k = m[1]; if (!touchMap.has(k)) touchMap.set(k, new El()); return touchMap.get(k); }
|
||
return new El();
|
||
},
|
||
querySelectorAll() { return []; },
|
||
};
|
||
globalThis.window = { addEventListener: noop, removeEventListener: noop };
|
||
globalThis.confirm = () => true;
|
||
|
||
let fail = 0;
|
||
const ok = (c, m) => { console.log((c ? ' ✔ ' : ' ✗ ') + m); if (!c) fail++; };
|
||
|
||
const { Save } = await import('../js/save.js');
|
||
const { Settings } = await import('../js/settings.js');
|
||
|
||
console.log('=== 初始化 / 默认布局 ===');
|
||
Settings.init();
|
||
const s0 = Save.data.settings;
|
||
ok(!!s0 && !!s0.touch, '存档含 settings.touch');
|
||
const keys = ['left', 'right', 'jump', 'shoot', 'down', 'pause', 'suicide'];
|
||
ok(keys.every(k => s0.touch[k]), '7 个手柄键都有默认布局');
|
||
ok(s0.touch.left.cx === 12 && s0.touch.jump.size === 94, '默认坐标/尺寸正确');
|
||
ok(byId.get('title-user').textContent === '玩家:无名氏', '标题默认显示「无名氏」');
|
||
|
||
console.log('=== apply 把布局写到真实手柄 ===');
|
||
const L = touchMap.get('left'), J = touchMap.get('jump');
|
||
ok(L._style.left === '12%' && L._style.top === '80%' && L._style.width === '62px', 'left 按钮定位/尺寸已应用');
|
||
ok(J._style.width === '94px' && J._style.left === '88%', 'jump 按钮定位/尺寸已应用');
|
||
|
||
console.log('=== 进入 / 确定 保存用户名与布局 ===');
|
||
Settings.enter();
|
||
byId.get('set-username').value = '阿测玩家';
|
||
Settings.confirm();
|
||
ok(Save.data.settings.username === '阿测玩家', '用户名已保存到存档');
|
||
ok(byId.get('title-user').textContent === '玩家:阿测玩家', '标题用户名已更新');
|
||
ok(localStorage.getItem('iwbtc_fan_v1').includes('阿测玩家'), '已写入 localStorage');
|
||
|
||
console.log('=== 恢复默认 ===');
|
||
byId.get('set-username').value = '随便';
|
||
Settings.reset();
|
||
ok(Settings._state().username === '', '恢复默认后用户名为空');
|
||
ok(Settings._state().touch.jump.size === 94 && Settings._state().touch.left.cx === 12, '恢复默认后布局回到出厂值');
|
||
// 恢复默认后必须再点确定才落盘
|
||
ok(Save.data.settings.username === '阿测玩家', '恢复默认未自动落盘(需点确定)');
|
||
Settings.confirm();
|
||
ok(Save.data.settings.username === '', '点确定后空用户名落盘');
|
||
|
||
console.log('=== 布局越长越界会被限制(place 约束在交互层,这里校验默认在合法区间)===');
|
||
ok(keys.every(k => { const p = Save.data.settings.touch[k]; return p.cx >= 4 && p.cx <= 96 && p.size >= 24 && p.size <= 160; }), '默认布局均在合法范围');
|
||
|
||
console.log('=== UI SCREENS 注册(防止设置屏永远不显示)===');
|
||
const uiSrc = (await import('node:fs')).readFileSync(new URL('../js/ui.js', import.meta.url), 'utf8');
|
||
ok(/SCREENS\s*=\s*\[[^\]]*'scr-settings'/.test(uiSrc), 'scr-settings 已加入 SCREENS 列表');
|
||
ok(/data-act="settings"/.test((await import('node:fs')).readFileSync(new URL('../index.html', import.meta.url), 'utf8')), '标题菜单有「设置」入口');
|
||
|
||
console.log('=== 模拟拖动 / 缩放(防「按下即跳角」)===');
|
||
Settings.enter(); // 重新同步工作副本
|
||
const ev = (t, x, y) => ({ target: t, clientX: x, clientY: y, pointerId: 1, preventDefault: noop, stopPropagation: noop });
|
||
const left = Settings._els().left;
|
||
const handle = left.children.find(c => c._classes.has('tc-rsz')) || left.children[0];
|
||
const wt = Settings._state().touch;
|
||
const cx0 = wt.left.cx, cy0 = wt.left.cy;
|
||
|
||
// 在按钮中心 (96,486) 按下,向右拖 40px(预览宽 800 → +5%)
|
||
left.dispatch('pointerdown', ev(left, 96, 486));
|
||
left.dispatch('pointermove', ev(left, 136, 486));
|
||
ok(Math.abs(wt.left.cx - (cx0 + 5)) < 0.01 && wt.left.cy === cy0, '拖动:cx 按位移增量变化,无跳角');
|
||
// 再拖回原处
|
||
left.dispatch('pointermove', ev(left, 96, 486));
|
||
ok(Math.abs(wt.left.cx - cx0) < 0.01, '拖动:逆向位移可精确归位');
|
||
left.dispatch('pointerup', ev(left, 96, 486));
|
||
|
||
// 缩放手柄:中心 (96,486),起点距中心 ~56,拉远到 ~113 → 尺寸翻倍
|
||
const size0 = wt.left.size;
|
||
handle.dispatch('pointerdown', ev(handle, 136, 526));
|
||
handle.dispatch('pointermove', ev(handle, 176, 566));
|
||
ok(wt.left.size > size0 && wt.left.size <= 160, `缩放:尺寸随距离放大(${size0}→${wt.left.size.toFixed(0)})`);
|
||
handle.dispatch('pointerup', ev(handle, 176, 566));
|
||
ok(wt.left.cx === cx0 && wt.left.cy === cy0, '缩放:不改变按钮位置');
|
||
|
||
console.log(fail === 0 ? '\n✅ settings 全部断言通过' : `\n❌ 失败 ${fail} 项`);
|
||
process.exit(fail === 0 ? 0 : 1);
|