mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
210 lines
9.0 KiB
JavaScript
210 lines
9.0 KiB
JavaScript
import { readShellSource } from './testSupport/readShellSource.mjs';
|
|
import { onKeyDown as cockpitKeyDown } from './ui/cockpitInput.js';
|
|
import { readFileSync as readRadioSource } from 'node:fs';
|
|
const radioBindings = readRadioSource(new URL('./ui/radioBindings.js', import.meta.url), 'utf8');
|
|
const radioPresentation = readRadioSource(new URL('./ui/radioPresentation.js', import.meta.url), 'utf8');
|
|
const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', import.meta.url), 'utf8');
|
|
import { bindPanelDisclosure, collapsePanelOnEscape } from './ui/panelDisclosure.js';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import test from 'node:test';
|
|
|
|
const source = readShellSource();
|
|
|
|
function method(name, nextName) {
|
|
const start = source.indexOf(` ${name}(`);
|
|
const end = source.indexOf(`\n ${nextName}(`, start);
|
|
assert.ok(start >= 0 && end > start, `${name} source is available`);
|
|
return source.slice(start, end);
|
|
}
|
|
|
|
function panelFixture({ id = 'data-panel', nested = false, nestedCollapsed = false, onDisclosure = false } = {}) {
|
|
let collapsed = false;
|
|
let focused = false;
|
|
let blurred = false;
|
|
const disclosure = {
|
|
focus: () => { focused = true; },
|
|
blur: () => { blurred = true; focused = false; },
|
|
contains: (candidate) => candidate === disclosure,
|
|
};
|
|
const panel = {
|
|
id,
|
|
classList: { contains: (name) => name === 'collapsed' && collapsed },
|
|
contains: (target) => target?.panel === panel,
|
|
querySelector: () => disclosure,
|
|
};
|
|
const inner = nested ? { id: 'param-slider-panel' } : panel;
|
|
const target = onDisclosure ? disclosure : {
|
|
panel,
|
|
closest: (selector) => (
|
|
nested && nestedCollapsed && selector.includes(':not(.collapsed)') ? panel : inner
|
|
),
|
|
};
|
|
if (onDisclosure) {
|
|
disclosure.panel = panel;
|
|
disclosure.closest = () => panel;
|
|
}
|
|
const document = { getElementById: (candidate) => candidate === id ? panel : null };
|
|
const manager = new Function('document', 'collapsePanelOnEscape', `return {${method('_collapsePanelOnEscape', '_initCommandDockPins')}};`)(document, collapsePanelOnEscape);
|
|
manager.setPanelCollapsed = (candidate, value, options) => {
|
|
assert.equal(candidate, id);
|
|
assert.equal(value, true);
|
|
assert.deepEqual(options, { explicit: true });
|
|
collapsed = true;
|
|
};
|
|
const event = {
|
|
key: 'Escape', target, defaultPrevented: false, propagationStopped: false,
|
|
preventDefault() { this.defaultPrevented = true; },
|
|
stopPropagation() { this.propagationStopped = true; },
|
|
};
|
|
return {
|
|
manager, panel, inner, target, event,
|
|
collapsed: () => collapsed,
|
|
focused: () => focused,
|
|
blurred: () => blurred,
|
|
};
|
|
}
|
|
|
|
test('Escape collapses an expanded panel and returns focus to its disclosure', () => {
|
|
const fixture = panelFixture();
|
|
assert.equal(fixture.manager._collapsePanelOnEscape(fixture.event, 'data-panel'), true);
|
|
assert.equal(fixture.collapsed(), true);
|
|
assert.equal(fixture.focused(), true);
|
|
assert.equal(fixture.event.defaultPrevented, true);
|
|
assert.equal(fixture.event.propagationStopped, true);
|
|
});
|
|
|
|
test('Escape on an expanded panel disclosure closes it without retaining focus', () => {
|
|
const fixture = panelFixture({ onDisclosure: true });
|
|
assert.equal(fixture.manager._collapsePanelOnEscape(fixture.event, 'data-panel'), true);
|
|
assert.equal(fixture.collapsed(), true);
|
|
assert.equal(fixture.focused(), false);
|
|
assert.equal(fixture.blurred(), true);
|
|
assert.equal(fixture.event.defaultPrevented, true);
|
|
assert.equal(fixture.event.propagationStopped, true);
|
|
});
|
|
|
|
test('non-Escape, handled, collapsed, and outside events do not collapse a panel', () => {
|
|
for (const mutate of [
|
|
(fixture) => { fixture.event.key = 'Enter'; },
|
|
(fixture) => { fixture.event.defaultPrevented = true; },
|
|
(fixture) => { fixture.manager.setPanelCollapsed('data-panel', true, { explicit: true }); },
|
|
(fixture) => { fixture.event.target = { panel: null, closest: () => null }; },
|
|
]) {
|
|
const fixture = panelFixture();
|
|
mutate(fixture);
|
|
const wasCollapsed = fixture.collapsed();
|
|
assert.equal(fixture.manager._collapsePanelOnEscape(fixture.event, 'data-panel'), false);
|
|
assert.equal(fixture.collapsed(), wasCollapsed);
|
|
assert.equal(fixture.focused(), false);
|
|
}
|
|
});
|
|
|
|
test('an expanded nested panel owns Escape before its expanded parent', () => {
|
|
const inner = panelFixture({ id: 'param-slider-panel' });
|
|
assert.equal(inner.manager._collapsePanelOnEscape(inner.event, 'param-slider-panel'), true);
|
|
|
|
const outer = panelFixture({ id: 'pp-toggles', nested: true });
|
|
assert.equal(outer.manager._collapsePanelOnEscape(outer.event, 'pp-toggles'), false);
|
|
assert.equal(outer.collapsed(), false);
|
|
assert.equal(outer.focused(), false);
|
|
});
|
|
|
|
test('a collapsed nested panel does not block the next Escape from closing its parent', () => {
|
|
const outer = panelFixture({ id: 'pp-toggles', nested: true, nestedCollapsed: true });
|
|
assert.equal(outer.manager._collapsePanelOnEscape(outer.event, 'pp-toggles'), true);
|
|
assert.equal(outer.collapsed(), true);
|
|
assert.equal(outer.focused(), true);
|
|
});
|
|
|
|
test('Location Escape clears a hidden draft search before restoring disclosure focus', () => {
|
|
const fixture = panelFixture({ id: 'location-bar' });
|
|
const removed = [];
|
|
let blurred = false;
|
|
fixture.manager._locationSearch = {
|
|
classList: { remove: (...names) => removed.push(...names) },
|
|
value: 'focus cleanup',
|
|
blur: () => { blurred = true; },
|
|
};
|
|
assert.equal(fixture.manager._collapsePanelOnEscape(fixture.event, 'location-bar'), true);
|
|
assert.deepEqual(removed, ['expanded']);
|
|
assert.equal(fixture.manager._locationSearch.value, '');
|
|
assert.equal(blurred, true);
|
|
assert.equal(fixture.focused(), true);
|
|
});
|
|
|
|
test('panel chrome wires Escape for every declared collapse target', () => {
|
|
const init = method('_initPanelChrome', '_collapsePanelOnEscape');
|
|
assert.match(init, /for \(const \[targetId, buttons\] of targets\)/);
|
|
assert.match(init, /bindPanelDisclosure\(\{[\s\S]*?onEscape: \(event\) => this\._collapsePanelOnEscape\(event, targetId\)/);
|
|
assert.match(source, /createHoverDisclosure\(\{[\s\S]*?onEscape: \(event\) => this\._collapsePanelOnEscape\(event, panelId\)/);
|
|
});
|
|
|
|
test('Cockpit Escape collapses Contact or Live Signals before exiting Cockpit', () => {
|
|
const onKeyDown = cockpitKeyDown.toString();
|
|
assert.match(
|
|
onKeyDown,
|
|
/event\.target\?\.closest\?\.\('\.cesium-credit-lightbox'\)[\s\S]*?return;/,
|
|
'the focused attribution lightbox keeps ownership of Escape before Cockpit',
|
|
);
|
|
assert.match(
|
|
onKeyDown,
|
|
/this\.context\?\.contains\(event\.target\)[\s\S]*?setContextCollapsed\(true\)[\s\S]*?event\.target === this\.contextToggle[\s\S]*?contextToggle\?\.blur[\s\S]*?contextToggle\?\.focus/,
|
|
);
|
|
assert.match(
|
|
onKeyDown,
|
|
/this\.signalStream\?\.contains\(event\.target\)[\s\S]*?setSignalCollapsed\(true, \{ user: true \}\)[\s\S]*?event\.target === this\.signalToggle[\s\S]*?signalToggle\?\.blur[\s\S]*?signalToggle\?\.focus/,
|
|
);
|
|
assert.ok(onKeyDown.indexOf('setContextCollapsed(true)') < onKeyDown.indexOf('this.exit()'));
|
|
assert.ok(onKeyDown.indexOf('setSignalCollapsed(true') < onKeyDown.indexOf('this.exit()'));
|
|
});
|
|
|
|
test('Cockpit utility Escape leaves an expanded nested Parameters panel to the shared handler', () => {
|
|
assert.match(
|
|
radioBindings,
|
|
/const nestedPanel = event\.target\?\.closest\?\.\(\s*'\.panel-collapsible:not\(\.collapsed\), #param-slider-panel:not\(\.collapsed\)',?\s*\);[\s\S]*?if \(nestedPanel\) return;[\s\S]*?setCockpitDisclosure/,
|
|
);
|
|
assert.match(
|
|
radioBindings,
|
|
/const kind = displayOpen \? 'display' : 'radio';[\s\S]*?escapedFromDisclosure[\s\S]*?returnFocus: !escapedFromDisclosure[\s\S]*?disclosure\?\.blur/,
|
|
'Cockpit utility disclosures clear their own focus when Escape closes them',
|
|
);
|
|
assert.match(
|
|
radioBindings,
|
|
/_contextRadioDock\?\.classList\.contains\('disclosure-open'\)[\s\S]*?escapedFromDisclosure[\s\S]*?setRadioDisclosure\(false, \{ returnFocus: !escapedFromDisclosure \}\)[\s\S]*?_contextRadioToggleBtn\?\.blur/,
|
|
'compact Radio disclosure clears its own focus when Escape closes it',
|
|
);
|
|
});
|
|
|
|
|
|
test('panel bindings have a single owner and are inert after destruction', () => {
|
|
const panel = new EventTarget();
|
|
const button = new EventTarget();
|
|
let collapsed = true;
|
|
let changes = 0;
|
|
let keys = 0;
|
|
panel.classList = { contains: () => collapsed };
|
|
const bind = () => bindPanelDisclosure({
|
|
panel, buttons: [button, button],
|
|
onChange(value, options) { collapsed = value; changes += 1; assert.equal(options.explicit, true); },
|
|
onEscape() { keys += 1; },
|
|
});
|
|
const first = bind();
|
|
button.dispatchEvent(new Event('click'));
|
|
panel.dispatchEvent(new Event('keydown'));
|
|
assert.equal(collapsed, false);
|
|
assert.equal(changes, 1);
|
|
assert.equal(keys, 1);
|
|
first.destroy();
|
|
first.destroy();
|
|
button.dispatchEvent(new Event('click'));
|
|
panel.dispatchEvent(new Event('keydown'));
|
|
assert.equal(changes, 1);
|
|
assert.equal(keys, 1);
|
|
const second = bind();
|
|
button.dispatchEvent(new Event('click'));
|
|
assert.equal(changes, 2);
|
|
assert.equal(collapsed, true);
|
|
second.destroy();
|
|
});
|