Files
kidgo/tools/editor-pick-test.mjs
T

137 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 编辑器交互回归:调色板无「查看属性」工具;无论手上拿什么方块(橡皮除外),
// 点击画布上任意方块都选中该格并在属性面板显示其属性(不覆盖已有方块)。
const noop = () => { };
let fail = 0;
const ok = (c, m) => { console.log((c ? ' ✔ ' : ' ✗ ') + m); if (!c) fail++; };
// ---------- 最小假 DOM(addEventListener 收句柄,children 真实入列)----------
const els = new Map();
function mkEl(id = '') {
const e = {
id, tagName: 'DIV', value: '', _html: '', textContent: '',
style: {}, dataset: {}, children: [],
width: 800, height: 608,
classList: {
_s: new Set(),
add(...c) { c.forEach(x => this._s.add(x)); },
remove(...c) { c.forEach(x => this._s.delete(x)); },
toggle(c, on) { on ? this._s.add(c) : this._s.delete(c); },
contains(c) { return this._s.has(c); },
},
_h: {},
addEventListener(t, fn) { (this._h[t] = this._h[t] || []).push(fn); },
removeEventListener: noop,
appendChild(c) { this.children.push(c); return c; },
querySelector: () => mkEl(), querySelectorAll: () => [],
getBoundingClientRect: () => ({ left: 0, top: 0, width: 800, height: 608 }),
getContext: () => ctx2d, focus: noop, click: noop,
preventDefault: noop,
};
Object.defineProperty(e, 'innerHTML', {
get() { return this._html; },
set(v) { this._html = String(v); this.children = []; },
});
return e;
}
const ctx2d = new Proxy({
canvas: { width: 800, height: 608 },
measureText: () => ({ width: 12 }),
createLinearGradient: () => ({ addColorStop: noop }),
createRadialGradient: () => ({ addColorStop: noop }),
getImageData: () => ({ data: [] }),
}, {
get(t, k) { return k in t ? t[k] : noop; },
set(t, k, v) { t[k] = v; return true; },
});
const getEl = s => { if (!els.has(s)) els.set(s, mkEl(s)); return els.get(s); };
globalThis.location = { search: '', href: 'http://localhost/', pathname: '/', reload: noop };
Object.defineProperty(globalThis, 'navigator', { value: { maxTouchPoints: 0, userAgent: 'node' }, configurable: true });
globalThis.matchMedia = q => ({ matches: false, media: q, addEventListener: noop, removeEventListener: noop });
globalThis.window = {
addEventListener: noop, removeEventListener: noop, AudioContext: null, webkitAudioContext: null,
innerWidth: 1280, innerHeight: 800, location: globalThis.location, navigator: globalThis.navigator, matchMedia: globalThis.matchMedia,
};
globalThis.addEventListener = noop;
globalThis.removeEventListener = noop;
globalThis.confirm = () => true;
globalThis.alert = noop;
globalThis.localStorage = { _d: {}, getItem(k) { return this._d[k] ?? null; }, setItem(k, v) { this._d[k] = String(v); }, removeItem(k) { delete this._d[k]; } };
globalThis.document = {
getElementById: getEl, querySelector: getEl, querySelectorAll: () => [],
createElement(t) { const e = mkEl(); e.tagName = t.toUpperCase(); return e; },
addEventListener: noop, body: mkEl('body'),
};
const { Editor, PALETTE } = await import('../js/editor.js');
// 画布坐标 → 格子:pos() 用 clientX/Y(scroll=0,缩放 1:1),格心 (tx*32+16, ty*32+16)
const ev = (tx, ty, button = 0) => ({ clientX: tx * 32 + 16, clientY: ty * 32 + 16, button, preventDefault: noop });
const clickTile = (tx, ty, button = 0) => {
for (const fn of getEl('#ed-canvas')._h.mousedown) fn(ev(tx, ty, button));
};
// renderProps 先写面板 innerHTML(标题),再把每个属性字段 appendChild 成子行
const propsHtml = () => {
const box = getEl('#ed-props');
return box.innerHTML + box.children.map(c => c.innerHTML || '').join('');
};
console.log('=== 调色板不再有「查看属性」工具 ===');
ok(!PALETTE.some(p => p[0] === '?'), "PALETTE 中已无 '?'(查看属性)");
ok(!PALETTE.some(p => p[1] === '查看属性'), 'PALETTE 中已无「查看属性」条目');
Editor.init({ startLevel: noop });
const palLabels = getEl('#palette').children.map(c => c.textContent || '');
ok(palLabels.length === PALETTE.length, `调色板渲染 ${PALETTE.length} 项(无工具项),实际 ${palLabels.length}`);
ok(!palLabels.some(t => t.includes('查看属性')), '调色板 UI 无「查看属性」按钮');
console.log('=== 打开地图,构造一块带属性的摄像头 C ===');
Editor.open();
const f = Editor.curFace();
f.grid[5][7] = 'C'; // 摄像头:SCHEMA 里有 4 个可编辑属性
f.props[5][7] = { w: 4, h: 4, mode: 'zoom', zoom: 1.5 };
console.log('=== 手上拿刺(^),点击已有摄像头 → 只看属性,不覆盖 ===');
Editor.sel = '^';
clickTile(7, 5);
ok(f.grid[5][7] === 'C', '格子内容未被覆盖(仍是 C)');
ok(Editor.selTile && Editor.selTile.tx === 7 && Editor.selTile.ty === 5, 'selTile 指向被点方块 (7,5)');
const html1 = propsHtml();
ok(html1.includes('区域宽') && html1.includes('镜头模式'), '属性面板显示该方块的属性(区域宽/镜头模式)');
console.log('=== 手上拿方块(#),点击同一格 → 同样只查看,不覆盖 ===');
Editor.sel = '#';
clickTile(7, 5);
ok(f.grid[5][7] === 'C', '拿 # 点击也不覆盖已有方块');
ok(propsHtml().includes('区域宽'), '属性面板仍显示 C 的属性');
console.log('=== 手上拿炮台(O),点击空格子 → 放置并显示新方块属性 ===');
Editor.sel = 'O';
clickTile(3, 9);
ok(f.grid[9][3] === 'O', '空格子正常放置 O');
ok(propsHtml().includes('发射间隔'), '放置后属性面板显示 O 的属性');
console.log('=== 提示牌 T 的文本属性可随点击查看 ===');
f.grid[9][4] = 'T'; f.props[9][4] = { msg: '小心刺' };
Editor.sel = '%'; // 拿着冰面点提示牌
clickTile(4, 9);
ok(f.grid[9][4] === 'T' && propsHtml().includes('提示文字'), '非相关工具点击 T 也能查看文字属性');
console.log('=== 橡皮擦例外:选中橡皮点击 → 擦除 ===');
Editor.sel = '.';
clickTile(4, 9);
ok(f.grid[9][4] === '.', '橡皮擦点击后 T 被擦除');
Editor.sel = '^'; // 恢复非橡皮工具
clickTile(7, 5); // 顺手确认 C 还在
console.log('=== 右键擦除与工具无关 ===');
Editor.sel = '#';
clickTile(3, 9, 2); // button=2 右键
ok(f.grid[9][3] === '.', '右键擦除不受手上工具影响');
console.log('=== 属性面板空态提示仍存在 ===');
Editor.show(); // 重置 selTile
ok(propsHtml().includes('点击画布上的方块'), '未选中时显示引导文案');
console.log(fail === 0 ? '\n✅ 编辑器点击查看属性 全部断言通过' : `\n❌ 失败 ${fail} 项`);
process.exit(fail === 0 ? 0 : 1);