Files
gods-eye-view/src/panelEscape.test.mjs
T
Said Turksever 66e19f0c50 Drop the Radio icon from Context and make the panel easier to read
The Context header carried a Radio icon that opened a second, compact
Radio player on top of the panel, duplicating the Radio section that
already lives inside Context. Remove the icon, the compact player and
everything that kept the two in step. Radio stays in Context as its own
section with one Enable control; Cockpit's compact Radio is unchanged.

Context itself was hard to read: 7-8 px labels at low contrast, a dashed
placeholder box, and a glow halo. The two modes are now one segmented
control, labels and guidance are 10 px at higher contrast, the standby
hint is plain text, section titles use --text-secondary, and the panel
draws no glow.

Unused translations are pruned, tests and QA harnesses that pinned the
compact player are updated or retired, and CURRENT-STATE and the
changelog describe the new header.
2026-10-29 11:28:56 +00:00

202 lines
8.5 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');
import { bindPanelDisclosure, collapsePanelOnEscape } from './ui/panelDisclosure.js';
import assert from 'node:assert/strict';
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',
);
});
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();
});