mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
refactor: separate Cockpit controller and presentation components
This commit is contained in:
1 parent
43db5c19bb
commit
8075c70e6e
24 files changed
+2258
-1928
No files matched your search
@@ -0,0 +1,106 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { CockpitDisplayPortal } from './cockpitDisplayPortal.js';
|
||||
|
||||
function fixture() {
|
||||
const prior = Object.fromEntries(['document', 'window', 'requestAnimationFrame', 'cancelAnimationFrame'].map(key => [key, globalThis[key]]));
|
||||
const frames = new Map();
|
||||
let nextFrame = 0;
|
||||
let focusCalls = 0;
|
||||
let layouts = 0;
|
||||
class Node extends EventTarget {
|
||||
constructor(name) {
|
||||
super(); this.name = name; this.children = []; this.parentNode = null; this.scrollTop = 0;
|
||||
this.classList = { toggle() {} };
|
||||
}
|
||||
append(node) {
|
||||
node.remove(); this.children.push(node); node.parentNode = this;
|
||||
}
|
||||
remove() {
|
||||
if (this.parentNode) this.parentNode.children.splice(this.parentNode.children.indexOf(this), 1);
|
||||
this.parentNode = null;
|
||||
}
|
||||
before(node) {
|
||||
node.remove(); const parent = this.parentNode;
|
||||
parent.children.splice(parent.children.indexOf(this), 0, node); node.parentNode = parent;
|
||||
}
|
||||
after(node) {
|
||||
node.remove(); const parent = this.parentNode;
|
||||
parent.children.splice(parent.children.indexOf(this) + 1, 0, node); node.parentNode = parent;
|
||||
}
|
||||
contains(node) { return this === node || this.children.some(child => child.contains(node)); }
|
||||
focus() { focusCalls += 1; document.activeElement = this; }
|
||||
}
|
||||
const standard = new Node('standard'); standard.scrollTop = 71;
|
||||
const cockpit = new Node('cockpit'); cockpit.scrollTop = 19;
|
||||
const group = new Node('group'); const sibling = new Node('sibling');
|
||||
const slot = new Node('slot');
|
||||
standard.append(group); standard.append(sibling); cockpit.append(slot);
|
||||
cockpit.querySelector = () => slot;
|
||||
globalThis.document = { createComment: name => new Node(name), activeElement: group, body: { classList: { contains: () => false } } };
|
||||
globalThis.window = new EventTarget();
|
||||
globalThis.requestAnimationFrame = fn => { const id = ++nextFrame; frames.set(id, fn); return id; };
|
||||
globalThis.cancelAnimationFrame = id => frames.delete(id);
|
||||
const create = () => new CockpitDisplayPortal({ standardPanel: standard, cockpitPanel: cockpit, groups: [['hud', group]], layout: () => { layouts += 1; } });
|
||||
return { standard, cockpit, group, sibling, slot, frames, create,
|
||||
get focusCalls() { return focusCalls; }, get layouts() { return layouts; },
|
||||
flush() { while (frames.size) { const batch = [...frames.values()]; frames.clear(); batch.forEach(fn => fn()); } },
|
||||
restore() { Object.assign(globalThis, prior); },
|
||||
};
|
||||
}
|
||||
|
||||
test('Display groups return to their exact home with independent scroll positions', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
const owner = f.create();
|
||||
owner.setActive(true);
|
||||
assert.equal(f.group.parentNode, f.slot);
|
||||
f.cockpit.scrollTop = 0;
|
||||
f.flush();
|
||||
assert.equal(f.cockpit.scrollTop, 19);
|
||||
assert.equal(f.focusCalls, 1);
|
||||
owner.setActive(false);
|
||||
f.standard.scrollTop = 0;
|
||||
f.flush();
|
||||
assert.equal(f.standard.scrollTop, 71);
|
||||
owner.destroy();
|
||||
assert.deepEqual(f.standard.children, [f.group, f.sibling]);
|
||||
assert.equal(f.frames.size, 0);
|
||||
} finally { f.restore(); }
|
||||
});
|
||||
|
||||
test('superseded portal frames cannot restore obsolete focus or scroll', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
const owner = f.create();
|
||||
owner.setActive(true);
|
||||
const obsolete = [...f.frames.values()];
|
||||
owner.setActive(false);
|
||||
f.cockpit.scrollTop = 93;
|
||||
obsolete.forEach(callback => callback());
|
||||
assert.equal(f.focusCalls, 0);
|
||||
assert.equal(f.cockpit.scrollTop, 93);
|
||||
f.flush();
|
||||
assert.equal(f.focusCalls, 1);
|
||||
assert.equal(owner.restoreOwner, null);
|
||||
owner.destroy();
|
||||
} finally { f.restore(); }
|
||||
});
|
||||
|
||||
test('disposal restores groups and revokes mode listeners and queued focus work', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
const owner = f.create();
|
||||
window.dispatchEvent(new CustomEvent('gev:cockpit-mode-changed', { detail: { active: true } }));
|
||||
assert.equal(f.group.parentNode, f.slot);
|
||||
const obsolete = [...f.frames.values()];
|
||||
owner.destroy(); owner.destroy();
|
||||
obsolete.forEach(callback => callback());
|
||||
window.dispatchEvent(new CustomEvent('gev:cockpit-mode-changed', { detail: { active: true } }));
|
||||
owner.setActive(true);
|
||||
assert.deepEqual(f.standard.children, [f.group, f.sibling]);
|
||||
assert.equal(f.frames.size, 0);
|
||||
assert.equal(f.focusCalls, 0);
|
||||
assert.equal(f.layouts, 1);
|
||||
} finally { f.restore(); }
|
||||
});
|
||||
Reference in new issue
Block a user