refactor: separate Cockpit controller and presentation components

This commit is contained in:
Sameh Khamis committed 2026-09-13 02:33:38 -07:00
1 parent 43db5c19bb
commit 8075c70e6e
24 files changed
+2258 -1928

No files matched your search

+106
View File
@@ -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(); }
});