test: follow formatted shell owners and make playback keyboard focus explicit

This commit is contained in:
Sameh Khamis committed 2026-09-13 04:49:02 -07:00
1 parent 945a809994
commit 841da87ca4
13 files changed
+90 -83

No files matched your search

+7
View File
@@ -192,6 +192,13 @@ try {
window.__godsEyeView.styleManager.getControlState().recording, window.__godsEyeView.styleManager.getControlState().recording,
), ),
); );
// A focused panel owns Escape to collapse one level. Release panel focus
// before testing the global playback shortcut; the canvas is not tabbable.
await page.evaluate(() => document.activeElement?.blur());
check(
'global playback shortcut has document focus',
await page.evaluate(() => document.activeElement === document.body),
);
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
await page.waitForFunction( await page.waitForFunction(
() => () =>
+17 -11
View File
@@ -1,3 +1,4 @@
import { StyleManager } from './ui/applicationShell.js';
import { enter as cockpitEnter, navigateContext } from './ui/cockpitTrackingController.js'; import { enter as cockpitEnter, navigateContext } from './ui/cockpitTrackingController.js';
import { CockpitViewController } from './ui/cockpitController.js'; import { CockpitViewController } from './ui/cockpitController.js';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
@@ -15,6 +16,11 @@ const cameraVerbs = fs.readFileSync(path.join(ROOT, 'src', 'cameraVerbs.js'), 'u
const cockpitTracking = fs.readFileSync(path.join(ROOT, 'src', 'cockpitTracking.js'), 'utf8'); const cockpitTracking = fs.readFileSync(path.join(ROOT, 'src', 'cockpitTracking.js'), 'utf8');
function body(source, pattern, label) { function body(source, pattern, label) {
const name = pattern.source.match(/^(\w+)/)?.[1];
if (source === ui && typeof StyleManager.prototype[name] === 'function') {
const method = StyleManager.prototype[name].toString();
return method.slice(method.indexOf(') {') + 3, -1);
}
const match = source.match(pattern); const match = source.match(pattern);
assert.ok(match, `${label} is missing`); assert.ok(match, `${label} is missing`);
return match[1]; return match[1];
@@ -40,7 +46,7 @@ test('Cockpit takeover invalidates deferred work before camera cancellation', ()
], 'Cockpit takeover'); ], 'Cockpit takeover');
assert.match( assert.match(
ui, ui,
/onCameraTakeover: \(\) => this\._stampNavigation\(\{ cancelPendingSelection: false \}\),/, /onCameraTakeover: \(\) =>\s*this\._stampNavigation\(\{\s*cancelPendingSelection: false,?\s*\}\),/,
'Cockpit retires stale camera work without clearing the aircraft selection it adopts', 'Cockpit retires stale camera work without clearing the aircraft selection it adopts',
); );
}); });
@@ -51,14 +57,14 @@ test('one explicit tracking selection clears sibling IDs before publishing its d
/_persistAwarenessSelection\(event, cleared = false\) \{([\s\S]*?)\n \}/, /_persistAwarenessSelection\(event, cleared = false\) \{([\s\S]*?)\n \}/,
'tracking persistence', 'tracking persistence',
); );
assert.match(persist, /adoptLayerParams\?\.\(layerId,/); assert.match(persist, /adoptLayerParams\?\.\(\s*layerId,/);
assert.match(persist, /\['flights', 'selectedFlightsTrackingId'\]/); assert.match(persist, /\['flights', 'selectedFlightsTrackingId'\]/);
assert.match(persist, /\['military', 'selectedMilitaryTrackingId'\]/); assert.match(persist, /\['military', 'selectedMilitaryTrackingId'\]/);
assert.match(persist, /\['satellites', 'selectedSatTrackingId'\]/); assert.match(persist, /\['satellites', 'selectedSatTrackingId'\]/);
assert.match(persist, /if \(otherLayerId === layerId\) continue;/); assert.match(persist, /if \(otherLayerId === layerId\) continue;/);
assert.match( assert.match(
persist, persist,
/for \(const \[otherLayerId, otherKey\][\s\S]*?setLayerParams\(otherLayerId,[\s\S]*?adoptLayerParams\?\.\(layerId,/, /for \(const \[otherLayerId, otherKey\][\s\S]*?setLayerParams\(\s*otherLayerId,[\s\S]*?adoptLayerParams\?\.\(\s*layerId,/,
'the previous family clears before Flight/Military/Satellite publishes the new durable ID', 'the previous family clears before Flight/Military/Satellite publishes the new durable ID',
); );
}); });
@@ -73,9 +79,9 @@ test('navigation clears dormant tracker IDs without aborting unrelated layer res
assert.match(stamp, /flightsLayer\.cancelPendingTrackingRestore\?\.\(\)/); assert.match(stamp, /flightsLayer\.cancelPendingTrackingRestore\?\.\(\)/);
assert.match(stamp, /militaryFlightsLayer\.cancelPendingTrackingRestore\?\.\(\)/); assert.match(stamp, /militaryFlightsLayer\.cancelPendingTrackingRestore\?\.\(\)/);
assert.match(stamp, /satellitesLayer\.cancelPendingTrackingRestore\?\.\(\)/); assert.match(stamp, /satellitesLayer\.cancelPendingTrackingRestore\?\.\(\)/);
assert.match(stamp, /if \(!passivelyClearedShareSelection && !flightsLayer\.getTrackedInfo\?\.\(\)\)[\s\S]*?selectedFlightsTrackingId: null/); assert.match(stamp, /if \(\s*!passivelyClearedShareSelection\s*&&\s*!flightsLayer\.getTrackedInfo\?\.\(\)\s*\)[\s\S]*?selectedFlightsTrackingId: null/);
assert.match(stamp, /if \(!passivelyClearedShareSelection && !militaryFlightsLayer\.getTrackedInfo\?\.\(\)\)[\s\S]*?selectedMilitaryTrackingId: null/); assert.match(stamp, /if \(\s*!passivelyClearedShareSelection\s*&&\s*!militaryFlightsLayer\.getTrackedInfo\?\.\(\)\s*\)[\s\S]*?selectedMilitaryTrackingId: null/);
assert.match(stamp, /if \(!passivelyClearedShareSelection && !satellitesLayer\.getTrackedInfo\?\.\(\)\)[\s\S]*?selectedSatTrackingId: null/); assert.match(stamp, /if \(\s*!passivelyClearedShareSelection\s*&&\s*!satellitesLayer\.getTrackedInfo\?\.\(\)\s*\)[\s\S]*?selectedSatTrackingId: null/);
}); });
test('voice Cockpit entry reaches the camera only through stamping seams', () => { test('voice Cockpit entry reaches the camera only through stamping seams', () => {
@@ -108,7 +114,7 @@ test('voice Cockpit entry reaches the camera only through stamping seams', () =>
// Seam 1: any tracker handoff stamps, so the adoption step is covered. // Seam 1: any tracker handoff stamps, so the adoption step is covered.
assert.match( assert.match(
ui, ui,
/viewer\.trackedEntityChanged\.addEventListener\(\(entity\) => \{\s*if \(entity && !this\._disposed\) this\._stampNavigation\(\{ cancelPendingSelection: false \}\);/, /viewer\.trackedEntityChanged\.addEventListener\(\s*\(entity\) => \{\s*if \(entity && !this\._disposed\)\s*this\._stampNavigation\(\{\s*cancelPendingSelection: false,?\s*\}\);/,
'tracker handoff must stamp', 'tracker handoff must stamp',
); );
// Seam 2 is pinned by "Cockpit takeover invalidates deferred work" above. // Seam 2 is pinned by "Cockpit takeover invalidates deferred work" above.
@@ -251,7 +257,7 @@ test('deferred search releases only after its final authority check', () => {
], 'deferred search component'); ], 'deferred search component');
assert.match(ui, /begin: \(\) => this\._beginDeferredNavigation\('location'\)/); assert.match(ui, /begin: \(\) => this\._beginDeferredNavigation\('location'\)/);
assert.match(ui, /beforeFly: \(generation\) => this\._reassertNavigationHandoff\(generation\)/); assert.match(ui, /beforeFly: \(generation\) => this\._reassertNavigationHandoff\(generation\)/);
assert.match(ui, /isCurrent: \(generation\) => !this\._disposed && generation === this\._navigationGeneration/); assert.match(ui, /isCurrent: \(generation\) =>\s*!this\._disposed && generation === this\._navigationGeneration/);
assert.match(ui, /onSettled: \(generation\) => this\._settleLocationSearchUi\(generation\)/); assert.match(ui, /onSettled: \(generation\) => this\._settleLocationSearchUi\(generation\)/);
assert.doesNotMatch(search.slice(0, search.indexOf('await this.search')), /_releaseFollowCamera/); assert.doesNotMatch(search.slice(0, search.indexOf('await this.search')), /_releaseFollowCamera/);
}); });
@@ -263,15 +269,15 @@ test('a direct globe gesture retires delayed camera and selection restore only',
); );
assert.match( assert.match(
ui, ui,
/viewer\?\.canvas\?\.addEventListener\('pointerdown', this\._initialShareGestureHandler/, /viewer\?\.canvas\?\.addEventListener\(\s*'pointerdown',\s*this\._initialShareGestureHandler/,
); );
assert.match( assert.match(
ui, ui,
/viewer\?\.canvas\?\.addEventListener\('wheel', this\._initialShareGestureHandler/, /viewer\?\.canvas\?\.addEventListener\(\s*'wheel',\s*this\._initialShareGestureHandler/,
); );
assert.match( assert.match(
ui, ui,
/removeEventListener\('pointerdown', this\._initialShareGestureHandler\)[\s\S]*?removeEventListener\('wheel', this\._initialShareGestureHandler\)/, /removeEventListener\(\s*'pointerdown',\s*this\._initialShareGestureHandler,?\s*\)[\s\S]*?removeEventListener\(\s*'wheel',\s*this\._initialShareGestureHandler,?\s*\)/,
); );
const stamp = body( const stamp = body(
ui, ui,
+1 -1
View File
@@ -115,5 +115,5 @@ test('a disposed UI cannot activate a camera when enabling finishes late', async
await pending; await pending;
assert.equal(activations, 0); assert.equal(activations, 0);
const uiSource = readFileSync(new URL('./ui/applicationShell.js', import.meta.url), 'utf8'); const uiSource = readFileSync(new URL('./ui/applicationShell.js', import.meta.url), 'utf8');
assert.match(uiSource, /shouldFocus: \(\) => !this\._disposed && this\._dataManager\.isEnabled\('cctv'\)/); assert.match(uiSource, /shouldFocus: \(\) =>\s*!this\._disposed\s*&&\s*this\._dataManager\.isEnabled\('cctv'\)/);
}); });
+21 -21
View File
@@ -87,13 +87,13 @@ test('Cockpit vision cycle exposes exactly five real visual styles without NONE'
assert.match(cycleVisionMode.toString(), /const modes = COCKPIT_VISION_MODES;/); assert.match(cycleVisionMode.toString(), /const modes = COCKPIT_VISION_MODES;/);
assert.match(setVisionMode.toString(), /const labels = \{\s*optical: inherited,\s*crt: 'CRT',\s*nvg: 'NVG',\s*thermal: 'FLIR',\s*noir: 'NOIR',?\s*\};/); assert.match(setVisionMode.toString(), /const labels = \{\s*optical: inherited,\s*crt: 'CRT',\s*nvg: 'NVG',\s*thermal: 'FLIR',\s*noir: 'NOIR',?\s*\};/);
assert.doesNotMatch(setVisionMode.toString(), /none: 'NONE'/); assert.doesNotMatch(setVisionMode.toString(), /none: 'NONE'/);
assert.match(ui, /getInheritedVisionLabel: \(\) => \([\s\S]*?STYLE_STATUS_LABELS\[this\.activeStyle\]/); assert.match(ui, /getInheritedVisionLabel: \(\) =>\s*\(?[\s\S]*?STYLE_STATUS_LABELS\[this\.activeStyle\]/);
assert.match(html, /id="cockpit-vision-current-label"[^>]*>NORMAL<\/strong>/); assert.match(html, /id="cockpit-vision-current-label"[^>]*>NORMAL<\/strong>/);
assert.match(ui, /const target = applyCockpitVisionStageIntensities\(this\.stages, next, this\._cockpitVisionRestore\);/); assert.match(ui, /const target = applyCockpitVisionStageIntensities\(\s*this\.stages,\s*next,\s*this\._cockpitVisionRestore,?\s*\);/);
assert.match(ui, /this\._cockpitVisionRestore = captureCockpitVisionBaseline\(this\.stages, this\.transitions\);/); assert.match(ui, /this\._cockpitVisionRestore = captureCockpitVisionBaseline\(\s*this\.stages,\s*this\.transitions,?\s*\);/);
assert.match( assert.match(
ui, ui,
/if \(next === 'optical'\) \{[\s\S]*?applyCockpitVisionStageIntensities\(this\.stages, next, this\._cockpitVisionRestore\);[\s\S]*?return;[\s\S]*?const target = applyCockpitVisionStageIntensities/, /if \(next === 'optical'\) \{[\s\S]*?applyCockpitVisionStageIntensities\(\s*this\.stages,\s*next,\s*this\._cockpitVisionRestore,?\s*\);[\s\S]*?return;[\s\S]*?const target = applyCockpitVisionStageIntensities/,
'the inherited entry must restore the map shader while CRT, NVG, FLIR, and NOIR remain temporary Cockpit overrides', 'the inherited entry must restore the map shader while CRT, NVG, FLIR, and NOIR remain temporary Cockpit overrides',
); );
assert.match( assert.match(
@@ -181,7 +181,7 @@ test('the Contact panel never hides itself out from under its own NEXT button',
); );
assert.match( assert.match(
css, css,
/\.cockpit-context-window\[data-state="lost"\]/, /\.cockpit-context-window\[data-state='lost'\]/,
'CONTACT LOST reuses the panel-level data-state cue that "uncertain" already uses', 'CONTACT LOST reuses the panel-level data-state cue that "uncertain" already uses',
); );
}); });
@@ -255,15 +255,15 @@ test('Cockpit owns a focused shared Display portal and compact Radio controls',
const cockpitRadio = html.match(/id="cockpit-radio-panel"[\s\S]*?<\/div>\s*<\/div>\s*<\/aside>/); const cockpitRadio = html.match(/id="cockpit-radio-panel"[\s\S]*?<\/div>\s*<\/div>\s*<\/aside>/);
assert.ok(cockpitRadio, 'Cockpit compact Radio controls are missing'); assert.ok(cockpitRadio, 'Cockpit compact Radio controls are missing');
assert.doesNotMatch(cockpitRadio[0], /context-radio-details-btn|radio-filter|radio-tuner/); assert.doesNotMatch(cockpitRadio[0], /context-radio-details-btn|radio-filter|radio-tuner/);
assert.match(css, /\.cockpit-utility-controls[\s\S]*?top:\s*var\(--cockpit-utility-top/); assert.match(css, /\.cockpit-utility-controls[\s\S]*?top:\s*var\(\s*--cockpit-utility-top/);
assert.match(css, /\[data-cockpit-launcher="display"\]\s*\{\s*width:\s*var\(--left-collapsed-width, 176px\)/); assert.match(css, /\[data-cockpit-launcher='display'\]\s*\{\s*width:\s*var\(\s*--left-collapsed-width, 176px\)/);
assert.match(css, /--display-panel-expanded-width:\s*272px/); assert.match(css, /--display-panel-expanded-width:\s*272px/);
assert.match(css, /#pp-toggles\s*\{[\s\S]*?--pp-expanded-width:\s*var\(--display-panel-expanded-width\)/); assert.match(css, /#pp-toggles\s*\{[\s\S]*?--pp-expanded-width:\s*var\(\s*--display-panel-expanded-width\)/);
assert.match(css, /is-expanded:has\(\[data-cockpit-launcher="display"\]\)\s*\{[\s\S]*?width:\s*var\(--display-panel-expanded-width\)/); assert.match(css, /is-expanded:has\(\[data-cockpit-launcher='display'\]\)\s*\{[\s\S]*?width:\s*var\(\s*--display-panel-expanded-width\)/);
assert.doesNotMatch(ui, /--cockpit-display-expanded-width|dataPanelWidth/); assert.doesNotMatch(ui, /--cockpit-display-expanded-width|dataPanelWidth/);
assert.match(css, /is-expanded:has\(\[data-cockpit-launcher="display"\]\)[\s\S]*?box-shadow:\s*0 8px 32px rgba\(0, 0, 0, \.42\)/); assert.match(css, /is-expanded:has\(\[data-cockpit-launcher='display'\]\)[\s\S]*?box-shadow:\s*0 8px 32px rgba\(0, 0, 0, 0\.42\)/);
assert.match(css, /\.cockpit-utility-control\.is-expanded \.cockpit-utility-launcher\s*\{[\s\S]*?border:\s*0;[\s\S]*?background:\s*transparent;[\s\S]*?box-shadow:\s*none;/); assert.match(css, /\.cockpit-utility-control\.is-expanded \.cockpit-utility-launcher\s*\{[\s\S]*?border:\s*0;[\s\S]*?background:\s*transparent;[\s\S]*?box-shadow:\s*none;/);
assert.match(css, /\.cockpit-utility-control\.is-expanded \.cockpit-utility-divider\s*\{[\s\S]*?linear-gradient\(90deg, rgb\(0 212 255 \/ 28%\), rgba\(0, 212, 255, 0\.18\) 58%, transparent\)[\s\S]*?box-shadow:\s*0 0 7px rgba\(0, 212, 255, \.22\);/); assert.match(css, /\.cockpit-utility-control\.is-expanded \.cockpit-utility-divider\s*\{[\s\S]*?linear-gradient\(\s*90deg,\s*rgb\(0 212 255 \/ 28%\),\s*rgba\(0, 212, 255, 0\.18\) 58%,\s*transparent\s*\)[\s\S]*?box-shadow:\s*0 0 7px rgba\(0, 212, 255, 0\.22\);/);
assert.match(radioBindings, /this\._cockpitDisplayToggleBtn\.textContent = displayOpen \? '▶' : '◀';/); assert.match(radioBindings, /this\._cockpitDisplayToggleBtn\.textContent = displayOpen \? '▶' : '◀';/);
assert.match(radioBindings, /this\._cockpitRadioToggleBtn\.textContent = radioOpen \? '▶' : '◀';/); assert.match(radioBindings, /this\._cockpitRadioToggleBtn\.textContent = radioOpen \? '▶' : '◀';/);
assert.match(css, /#cockpit-display-panel\s*\{[\s\S]*?display:\s*flex;[\s\S]*?gap:\s*7px;[\s\S]*?padding:\s*0;[\s\S]*?border-top:\s*0;/); assert.match(css, /#cockpit-display-panel\s*\{[\s\S]*?display:\s*flex;[\s\S]*?gap:\s*7px;[\s\S]*?padding:\s*0;[\s\S]*?border-top:\s*0;/);
@@ -275,8 +275,8 @@ test('Cockpit owns a focused shared Display portal and compact Radio controls',
assert.match(css, /--cockpit-radio-expanded-width:\s*232px/); assert.match(css, /--cockpit-radio-expanded-width:\s*232px/);
assert.doesNotMatch(css, /--map-stack-(?:collapsed|expanded)-width/); assert.doesNotMatch(css, /--map-stack-(?:collapsed|expanded)-width/);
assert.doesNotMatch(html, /id="stack-panel"/, 'the retired Map Stack panel must not remain in Cockpit layout'); assert.doesNotMatch(html, /id="stack-panel"/, 'the retired Map Stack panel must not remain in Cockpit layout');
assert.match(css, /\[data-cockpit-launcher="radio"\]\s*\{\s*width:\s*var\(--cockpit-radio-collapsed-width, 148px\)/); assert.match(css, /\[data-cockpit-launcher='radio'\]\s*\{\s*width:\s*var\(\s*--cockpit-radio-collapsed-width, 148px\)/);
assert.match(css, /is-expanded:has\(\[data-cockpit-launcher="radio"\]\)[\s\S]*?--cockpit-radio-expanded-width/); assert.match(css, /is-expanded:has\(\[data-cockpit-launcher='radio'\]\)[\s\S]*?--cockpit-radio-expanded-width/);
assert.doesNotMatch(ui, /--cockpit-radio-tab-width/); assert.doesNotMatch(ui, /--cockpit-radio-tab-width/);
assert.doesNotMatch(css, /\.cockpit-utility-launcher:hover/); assert.doesNotMatch(css, /\.cockpit-utility-launcher:hover/);
const desktopUtilityCss = css.slice( const desktopUtilityCss = css.slice(
@@ -348,11 +348,11 @@ test('Cockpit side surfaces behave as two single-expanded accordions', () => {
); );
assert.match( assert.match(
ui, ui,
/!nextCollapsed && this\.cockpitView\?\.active && panelId === 'data-panel'[\s\S]*?_cockpitContextCollapsedForDataPanel = !this\.cockpitView\.contextCollapsed[\s\S]*?this\.cockpitView\.setContextCollapsed\(true\);/, /!nextCollapsed\s*&&\s*this\.cockpitView\?\.active\s*&&\s*panelId === 'data-panel'[\s\S]*?_cockpitContextCollapsedForDataPanel =\s*!this\.cockpitView\.contextCollapsed[\s\S]*?this\.cockpitView\.setContextCollapsed\(true\);/,
); );
assert.match( assert.match(
ui, ui,
/nextCollapsed && this\.cockpitView\?\.active && panelId === 'data-panel'[\s\S]*?_cockpitContextCollapsedForDataPanel[\s\S]*?this\.cockpitView\.setContextCollapsed\(false\);/, /nextCollapsed\s*&&\s*this\.cockpitView\?\.active\s*&&\s*panelId === 'data-panel'[\s\S]*?_cockpitContextCollapsedForDataPanel[\s\S]*?this\.cockpitView\.setContextCollapsed\(false\);/,
'closing Data Layers must restore Contact only after an automatic collapse', 'closing Data Layers must restore Contact only after an automatic collapse',
); );
assert.match(setContextCollapsed.toString(), assert.match(setContextCollapsed.toString(),
@@ -404,7 +404,7 @@ test('fresh Cockpit entry temporarily collapses map panels and exit restores the
); );
assert.match( assert.match(
callback[1], callback[1],
/_cockpitPanelRestore\.set\(panelId, panel\.classList\.contains\('collapsed'\)\)/, /_cockpitPanelRestore\.set\(\s*panelId,\s*panel\.classList\.contains\('collapsed'\),?\s*\)/,
'entry must remember each panel\'s exact collapsed state before hiding it', 'entry must remember each panel\'s exact collapsed state before hiding it',
); );
assert.match( assert.match(
@@ -449,12 +449,12 @@ test('fresh Cockpit entry temporarily collapses map panels and exit restores the
}); });
test('real disclosure changes reconsider only their own temporary panel lane', () => { test('real disclosure changes reconsider only their own temporary panel lane', () => {
const collapseStart = ui.indexOf('setPanelCollapsed(panelId, collapsed, {'); const collapseStart = ui.indexOf(' setPanelCollapsed(');
const collapse = ui.slice( const collapse = ui.slice(
collapseStart, collapseStart,
ui.indexOf('toggleCleanView(forceEnabled)', collapseStart), ui.indexOf('toggleCleanView(forceEnabled)', collapseStart),
); );
assert.match(collapse, /classList\.contains\('collapsed'\) === nextCollapsed && !wasAutoCollapsed[\s\S]*?return;/); assert.match(collapse, /classList\.contains\('collapsed'\) === nextCollapsed\s*&&\s*!wasAutoCollapsed[\s\S]*?return;/);
assert.match(collapse, /_rightPanelStack\?\.contains\(panelEl\)[\s\S]*?_scheduleRightPanelLayout\(\{ reconsiderAutoCollapse: true \}\)/); assert.match(collapse, /_rightPanelStack\?\.contains\(panelEl\)[\s\S]*?_scheduleRightPanelLayout\(\{ reconsiderAutoCollapse: true \}\)/);
assert.match(collapse, /_scheduleLeftPanelLayout\(\{[\s\S]*?reconsiderAutoCollapse: this\._leftPanelStack\?\.contains\(panelEl\) === true/); assert.match(collapse, /_scheduleLeftPanelLayout\(\{[\s\S]*?reconsiderAutoCollapse: this\._leftPanelStack\?\.contains\(panelEl\) === true/);
}); });
@@ -462,7 +462,7 @@ test('real disclosure changes reconsider only their own temporary panel lane', (
test('Cockpit hides the complete top-center globe action group', () => { test('Cockpit hides the complete top-center globe action group', () => {
assert.match( assert.match(
css, css,
/body\.cockpit-mode :is\([\s\S]*?#top-center-actions[\s\S]*?\) \{ display: none !important; \}/, /body\.cockpit-mode :is\([\s\S]*?#top-center-actions[\s\S]*?\)\s*\{\s*display: none !important;\s*\}/,
); );
assert.match( assert.match(
css, css,
@@ -587,7 +587,7 @@ test('Cockpit panel corridors reserve the owned topline readouts', () => {
assert.match(signalLayout, /cockpit-utility-controls[\s\S]*?--cockpit-utility-max-height/); assert.match(signalLayout, /cockpit-utility-controls[\s\S]*?--cockpit-utility-max-height/);
assert.match( assert.match(
css, css,
/\.cockpit-utility-controls\s*\{[\s\S]*?top:\s*var\(--cockpit-utility-top, var\(--left-stack-safe-top, var\(--left-stack-top\)\)\)/, /\.cockpit-utility-controls\s*\{[\s\S]*?top:\s*var\(\s*--cockpit-utility-top,\s*var\(\s*--left-stack-safe-top,\s*var\(\s*--left-stack-top\)\s*\)\s*\)/,
'the first frame before Cockpit publishes an anchor still needs the fallback chain', 'the first frame before Cockpit publishes an anchor still needs the fallback chain',
); );
assert.match(css, /body\.cockpit-mode #left-panel-stack\s*\{[\s\S]*?transition:\s*none;/); assert.match(css, /body\.cockpit-mode #left-panel-stack\s*\{[\s\S]*?transition:\s*none;/);
@@ -859,7 +859,7 @@ test('cockpit state cannot report entryAllowed while already active', () => {
); );
assert.match( assert.match(
state, state,
/const entryAllowed = !active && Boolean\(/, /const entryAllowed =\s*!active &&\s*Boolean\(/,
'entry is impossible while already inside, unconditionally', 'entry is impossible while already inside, unconditionally',
); );
assert.match(state, /entryBlockedReason:/, 'and a refusal can be explained'); assert.match(state, /entryBlockedReason:/, 'and a refusal can be explained');
+9 -9
View File
@@ -166,7 +166,7 @@ function flattenRules(source) {
rules.push({ rules.push({
order: rules.length, order: rules.length,
media: [...mediaStack], media: [...mediaStack],
parts: splitTopLevel(head, ',').map((part) => part.trim()).filter(Boolean), parts: splitTopLevel(head, ',').map((part) => part.trim().replace(/\(\s+/g, '(').replace(/\s+\)/g, ')')).filter(Boolean),
decls, decls,
}); });
index = close + 1; index = close + 1;
@@ -440,18 +440,18 @@ test('custom properties inside modelled offsets are provably non-negative', () =
const layout = fs.readFileSync(new URL('./ui/panelLayoutController.js', import.meta.url), 'utf8'); const layout = fs.readFileSync(new URL('./ui/panelLayoutController.js', import.meta.url), 'utf8');
const start = layout.indexOf('_updateCommandDockTrayStack() {'); const start = layout.indexOf('_updateCommandDockTrayStack() {');
assert.ok(start > 0, '_updateCommandDockTrayStack is missing'); assert.ok(start > 0, '_updateCommandDockTrayStack is missing');
const writer = layout.slice(start, start + 1800); const writer = layout.slice(start, layout.indexOf(' _scheduleAdaptivePanelLayout(', start));
// Every value traces back to a rect height, floored at 0 and rounded up. // Every value traces back to a rect height, floored at 0 and rounded up.
assert.match(writer, /const locationHeight = [\s\S]{0,120}?getBoundingClientRect\(\)\.height \|\| 0;/); assert.match(writer, /const locationHeight =\s*[\s\S]{0,180}?getBoundingClientRect\(\)\.height\s*\|\| 0;/);
assert.match(writer, /const presetsHeight = [\s\S]{0,120}?getBoundingClientRect\(\)\.height \|\| 0;/); assert.match(writer, /const presetsHeight =\s*[\s\S]{0,180}?getBoundingClientRect\(\)\.height\s*\|\| 0;/);
assert.match(writer, /const lowerPinnedHeight = [\s\S]{0,160}?getBoundingClientRect\(\)\.height \|\| 0;/); assert.match(writer, /const lowerPinnedHeight =\s*[\s\S]{0,220}?getBoundingClientRect\(\)\.height\s*\|\| 0;/);
assert.match(writer, /const locationHeightPx = Math\.ceil\(locationHeight\);/); assert.match(writer, /const locationHeightPx = Math\.ceil\(locationHeight\);/);
assert.match(writer, /const presetsHeightPx = Math\.ceil\(presetsHeight\);/); assert.match(writer, /const presetsHeightPx = Math\.ceil\(presetsHeight\);/);
assert.match(writer, /'--dock-location-pinned-height', `\$\{locationHeightPx\}px`/); assert.match(writer, /'--dock-location-pinned-height',\s*`\$\{locationHeightPx\}px`/);
assert.match(writer, /'--dock-presets-pinned-height', `\$\{presetsHeightPx\}px`/); assert.match(writer, /'--dock-presets-pinned-height',\s*`\$\{presetsHeightPx\}px`/);
assert.match(writer, /'--dock-lower-pinned-height', `\$\{Math\.ceil\(lowerPinnedHeight\)\}px`/); assert.match(writer, /'--dock-lower-pinned-height',\s*`\$\{Math\.ceil\(lowerPinnedHeight\)\}px`/);
assert.match(writer, /'--dock-pinned-stack-height', stackHeight/); assert.match(writer, /'--dock-pinned-stack-height', stackHeight/);
assert.match(writer, /const stackHeight = pinnedCount > 1[\s\S]{0,160}?`calc\(\$\{locationHeightPx\}px \+ \$\{presetsHeightPx\}px \+ 1\.2rem\)`/); assert.match(writer, /const stackHeight =\s*pinnedCount > 1[\s\S]{0,160}?`calc\(\$\{locationHeightPx\}px \+ \$\{presetsHeightPx\}px \+ 1\.2rem\)`/);
}); });
test('the inputs behind the measured constants are unchanged', () => { test('the inputs behind the measured constants are unchanged', () => {
+1 -1
View File
@@ -379,7 +379,7 @@ test('a fresh boot starts 3D aircraft ON in proximity — codec, both layers, an
const html = await readFile(new URL('../../index.html', import.meta.url), 'utf8'); const html = await readFile(new URL('../../index.html', import.meta.url), 'utf8');
assert.match(html, /class="pp-toggle-btn active" id="models3d-toggle" aria-pressed="true"/, assert.match(html, /class="pp-toggle-btn active" id="models3d-toggle" aria-pressed="true"/,
'index.html: the 3D button paints lit on first paint, before ui.js runs — and says so'); 'index.html: the 3D button paints lit on first paint, before ui.js runs — and says so');
assert.match(ui, /this\._models3dBtn\?\.setAttribute\('aria-pressed', String\(this\._models3dEnabled\)\)/, assert.match(ui, /this\._models3dBtn\?\.setAttribute\(\s*'aria-pressed',\s*String\(this\._models3dEnabled\),?\s*\)/,
'ui.js: and keeps aria-pressed synchronized, so the lit state is not colour-only'); 'ui.js: and keeps aria-pressed synchronized, so the lit state is not colour-only');
assert.match(html, /class="pp-slider-row visible" id="models3d-mode-row"/, assert.match(html, /class="pp-slider-row visible" id="models3d-mode-row"/,
'index.html: and the Proximity/All row paints open with it'); 'index.html: and the Proximity/All row paints open with it');
+7 -7
View File
@@ -97,7 +97,7 @@ test('share-follow failures use the universal top-center status instead of the b
assert.match(handler, /this\._showGlobalStatusNotice\(message\)/); assert.match(handler, /this\._showGlobalStatusNotice\(message\)/);
assert.match(handler, /this\.initialRestorePromise\.then\(showAfterStartupCover\)/); assert.match(handler, /this\.initialRestorePromise\.then\(showAfterStartupCover\)/);
assert.match(handler, /this\._lifetime\.frame\(\(\) => \{/); assert.match(handler, /this\._lifetime\.frame\(\(\) => \{/);
assert.match(handler, /this\._lifetime\.listen\(startupCover, 'transitionend', showOnce, \{ once: true \}\)/); assert.match(handler, /this\._lifetime\.listen\(\s*startupCover,\s*'transitionend',\s*showOnce,\s*\{ once: true \},?\s*\)/);
assert.match(handler, /fallbackTimer = this\._lifetime\.timeout\(showOnce, 1000\)/); assert.match(handler, /fallbackTimer = this\._lifetime\.timeout\(showOnce, 1000\)/);
assert.doesNotMatch(handler, /this\._showToast\(message\)/); assert.doesNotMatch(handler, /this\._showToast\(message\)/);
assert.doesNotMatch(handler, /pushCockpitSignal/); assert.doesNotMatch(handler, /pushCockpitSignal/);
@@ -107,7 +107,7 @@ test('share-follow failures use the universal top-center status instead of the b
assert.match(handler, /this\._shareTrackingNoticeGeneration \+= 1/); assert.match(handler, /this\._shareTrackingNoticeGeneration \+= 1/);
assert.match(handler, /canPresentDeferredStatusNotice\(/); assert.match(handler, /canPresentDeferredStatusNotice\(/);
assert.match(handler, /if \(this\._shareTrackingAcquiringKey\) return/); assert.match(handler, /if \(this\._shareTrackingAcquiringKey\) return/);
assert.match(handler, /result\.classification === 'followed' \|\| result\.classification === 'cancelled'/); assert.match(handler, /result\.classification === 'followed'\s*\|\|\s*result\.classification === 'cancelled'/);
}); });
test('universal notice masks active loading only for its own fixed dwell', () => { test('universal notice masks active loading only for its own fixed dwell', () => {
@@ -239,11 +239,11 @@ test('terminal loading feedback centers its label without an empty detail slot',
const css = readStylesheet(new URL('../style.css', import.meta.url)); const css = readStylesheet(new URL('../style.css', import.meta.url));
assert.match( assert.match(
css, css,
/#global-loading-status:is\(\[data-state='complete'\], \[data-state='cancelled'\], \[data-state='error'\]\)\s*\{[\s\S]*?justify-content:\s*center;[\s\S]*?text-align:\s*center;/, /#global-loading-status:is\(\s*\[data-state='complete'\],\s*\[data-state='cancelled'\],\s*\[data-state='error'\]\s*\)\s*\{[\s\S]*?justify-content:\s*center;[\s\S]*?text-align:\s*center;/,
); );
assert.match( assert.match(
css, css,
/#global-loading-status:is\(\[data-state='complete'\], \[data-state='cancelled'\], \[data-state='error'\]\) #global-loading-detail\s*\{\s*display:\s*none;/, /#global-loading-status:is\(\s*\[data-state='complete'\],\s*\[data-state='cancelled'\],\s*\[data-state='error'\]\s*\)\s*#global-loading-detail\s*\{\s*display:\s*none;/,
); );
}); });
@@ -656,7 +656,7 @@ test('the loading ticker never runs hidden and stops after loading and notices s
// Persistent ACQUIRING notices remain visible without a 60ms timer. // Persistent ACQUIRING notices remain visible without a 60ms timer.
assert.match( assert.match(
arm, arm,
/const noticeNeedsTicker = Number\.isFinite\(this\._globalStatusNotice\?\.hideAt\);[\s\S]*?if \(this\._loadingFeedbackState\?\.phase === 'idle' && !noticeNeedsTicker\) \{\s*this\._stopLoadingFeedbackTicker\(\);\s*\}/, /const noticeNeedsTicker = Number\.isFinite\(\s*this\._globalStatusNotice\?\.hideAt,?\s*\);[\s\S]*?if \(\s*this\._loadingFeedbackState\?\.phase === 'idle'\s*&&\s*!noticeNeedsTicker\s*\) \{\s*this\._stopLoadingFeedbackTicker\(\);\s*\}/,
'an idle phase with no expiring notice must stop the ticker', 'an idle phase with no expiring notice must stop the ticker',
); );
assert.match( assert.match(
@@ -672,7 +672,7 @@ test('the loading ticker never runs hidden and stops after loading and notices s
// throws on every visibilitychange — and must be torn down with the rest. // throws on every visibilitychange — and must be torn down with the rest.
assert.match( assert.match(
ui, ui,
/this\._loadingVisibilityHandler = \(\) => \{\s*if \(!document\.hidden\) this\._updateGlobalLoadingFeedback\(\);\s*else this\._stopLoadingFeedbackTicker\(\);\s*\};\s*document\.addEventListener\('visibilitychange', this\._loadingVisibilityHandler\);/, /this\._loadingVisibilityHandler = \(\) => \{\s*if \(!document\.hidden\) this\._updateGlobalLoadingFeedback\(\);\s*else this\._stopLoadingFeedbackTicker\(\);\s*\};\s*document\.addEventListener\(\s*'visibilitychange',\s*this\._loadingVisibilityHandler,?\s*\);/,
'visibilitychange must resample the chip on return', 'visibilitychange must resample the chip on return',
); );
const styleManager = ui.slice(ui.indexOf('export class ShellFeedback')); const styleManager = ui.slice(ui.indexOf('export class ShellFeedback'));
@@ -682,7 +682,7 @@ test('the loading ticker never runs hidden and stops after loading and notices s
); );
assert.match( assert.match(
ui, ui,
/document\.removeEventListener\('visibilitychange', this\._loadingVisibilityHandler\);/, /document\.removeEventListener\(\s*'visibilitychange',\s*this\._loadingVisibilityHandler,?\s*\);/,
'the resample handler must be removed on teardown', 'the resample handler must be removed on teardown',
); );
}); });
+9 -8
View File
@@ -1,3 +1,4 @@
import { StyleManager } from './ui/applicationShell.js';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
@@ -24,7 +25,7 @@ test('the ACTIVE STYLE indicator is written from the style name and nothing else
assert.equal(writes.length, 1, 'the style indicator must have exactly one writer'); assert.equal(writes.length, 1, 'the style indicator must have exactly one writer');
assert.match( assert.match(
ui.slice(writes[0].index, writes[0].index + 160), ui.slice(writes[0].index, writes[0].index + 160),
/this\._styleIndicator\.textContent = displayNames\[styleName\] \|\| styleName\.toUpperCase\(\);/, /this\._styleIndicator\.textContent =\s*displayNames\[styleName\] \|\| styleName\.toUpperCase\(\);/,
); );
const handler = locationSearchHandler(); const handler = locationSearchHandler();
@@ -66,15 +67,15 @@ test('any other camera destination clears the search label too', () => {
// Voice navigation, the globe reset, camera takeover and entity selection // Voice navigation, the globe reset, camera takeover and entity selection
// all funnel through _stampNavigation; without a clear there, a searched // all funnel through _stampNavigation; without a clear there, a searched
// label outlives the place it named. // label outlives the place it named.
const start = ui.indexOf(' _stampNavigation({ cancelPendingSelection = true, clearSearchedLocation = true } = {}) {'); const start = ui.indexOf(' _stampNavigation(');
assert.ok(start > 0, '_stampNavigation is missing'); assert.ok(start > 0, '_stampNavigation is missing');
assert.match(ui.slice(start, start + 700), /if \(clearSearchedLocation\) this\.clearSearchedLocation\(\);/); assert.match(StyleManager.prototype._stampNavigation.toString(), /if \(clearSearchedLocation\) this\.clearSearchedLocation\(\);/);
// The shared funnel is what the reset and voice seams actually reach. // The shared funnel is what the reset and voice seams actually reach.
for (const seam of ['resetToGlobeView() {', 'beginLocationNavigation() {', '_runExplicitNavigation(noun, navigate']) { for (const seam of ['resetToGlobeView() {', 'beginLocationNavigation() {', '_runExplicitNavigation(']) {
const at = ui.indexOf(seam); const at = ui.indexOf(seam);
assert.ok(at > 0, `missing navigation seam "${seam}"`); assert.ok(at > 0, `missing navigation seam "${seam}"`);
assert.match(ui.slice(at, at + 900), /_stampNavigation\(/, `"${seam}" must stamp navigation`); assert.match(StyleManager.prototype[seam.match(/^(\w+)/)[1]].toString(), /_stampNavigation\(/, `"${seam}" must stamp navigation`);
} }
// Public seam, so a camera owner that flies on its own can invalidate it. // Public seam, so a camera owner that flies on its own can invalidate it.
@@ -86,11 +87,11 @@ test('a deferred lookup that never flies leaves the readout standing', () => {
// the stamp blanked a still-true readout whenever the lookup failed, was // the stamp blanked a still-true readout whenever the lookup failed, was
// superseded, or was refused — no camera ever moved. The deferred begin opts // superseded, or was refused — no camera ever moved. The deferred begin opts
// out; the reassert seam, reached only once the flight is granted, clears. // out; the reassert seam, reached only once the flight is granted, clears.
const begin = ui.indexOf(' _beginDeferredNavigation(noun = '); const begin = ui.indexOf(' _beginDeferredNavigation(');
assert.ok(begin > 0, '_beginDeferredNavigation is missing'); assert.ok(begin > 0, '_beginDeferredNavigation is missing');
assert.match( assert.match(
ui.slice(begin, begin + 700), StyleManager.prototype._beginDeferredNavigation.toString(),
/stamp: \(\) => this\._stampNavigation\(\{ cancelPendingSelection, clearSearchedLocation: false \}\)/, /stamp: \(\) =>\s*this\._stampNavigation\(\{\s*cancelPendingSelection,\s*clearSearchedLocation: false,?\s*\}\)/,
); );
const reassert = ui.indexOf(' _reassertNavigationHandoff(generation) {'); const reassert = ui.indexOf(' _reassertNavigationHandoff(generation) {');
+2 -12
View File
@@ -1,3 +1,4 @@
import { StyleManager } from './ui/applicationShell.js';
import { createHoverDisclosure, collapsePanelOnEscape } from './ui/panelDisclosure.js'; import { createHoverDisclosure, collapsePanelOnEscape } from './ui/panelDisclosure.js';
import test from 'node:test'; import test from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
@@ -11,17 +12,6 @@ const locationToggleMarkup = locationMarkup.match(/<button\b([^>]*\bid="location
const locationToggleAttributes = Object.fromEntries( const locationToggleAttributes = Object.fromEntries(
[...(locationToggleMarkup?.[1] || '').matchAll(/([\w-]+)="([^"]*)"/g)].map((match) => [match[1], match[2]]), [...(locationToggleMarkup?.[1] || '').matchAll(/([\w-]+)="([^"]*)"/g)].map((match) => [match[1], match[2]]),
); );
const initStart = source.indexOf(' _initAutoHoverPanel(');
const initEnd = source.indexOf(' /**\n * Sets up drag-to-reposition', initStart);
const escapeStart = source.indexOf(' _collapsePanelOnEscape(');
const escapeEnd = source.indexOf(' _initCommandDockPins(', escapeStart);
const closeStart = source.indexOf(' setPanelCollapsed(panelId, collapsed, {');
const closeEnd = source.indexOf(' /**\n * Toggles "clean view"', closeStart);
const syncStart = source.indexOf(' _syncPanelCollapseButton(panelEl) {');
const syncEnd = source.indexOf(' /**\n * Converts a panel', syncStart);
assert.ok(initStart >= 0 && initEnd > initStart && escapeStart >= 0 && escapeEnd > escapeStart
&& closeStart >= 0 && closeEnd > closeStart);
assert.ok(syncStart >= 0 && syncEnd > syncStart);
function harness({ hidden = false, selected = true, noChips = false } = {}) { function harness({ hidden = false, selected = true, noChips = false } = {}) {
let now = 0; let now = 0;
@@ -116,7 +106,7 @@ function harness({ hidden = false, selected = true, noChips = false } = {}) {
window.performance = { now: () => now }; window.performance = { now: () => now };
document.defaultView = window; document.defaultView = window;
const methods = new Function('createHoverDisclosure', 'collapsePanelOnEscape', 'document', 'window', 'clearTimeout', 'performance', 'requestAnimationFrame', const methods = new Function('createHoverDisclosure', 'collapsePanelOnEscape', 'document', 'window', 'clearTimeout', 'performance', 'requestAnimationFrame',
`return ({${source.slice(initStart, initEnd)},\n${source.slice(escapeStart, escapeEnd)},\n${source.slice(closeStart, closeEnd)},\n${source.slice(syncStart, syncEnd)}});`)( `return ({${StyleManager.prototype._initAutoHoverPanel.toString()},\n${StyleManager.prototype._collapsePanelOnEscape.toString()},\n${StyleManager.prototype.setPanelCollapsed.toString()},\n${StyleManager.prototype._syncPanelCollapseButton.toString()}});`)(
createHoverDisclosure, collapsePanelOnEscape, document, window, (id) => timers.delete(id), { now: () => now }, () => {}, createHoverDisclosure, collapsePanelOnEscape, document, window, (id) => timers.delete(id), { now: () => now }, () => {},
); );
const saves = []; const saves = [];
+1 -1
View File
@@ -346,7 +346,7 @@ test('the Visual Presets tray owns Map Source and the retired left panel is abse
); );
const panels = readFileSync(new URL('./ui/panelDisclosure.js', import.meta.url), 'utf8'); const panels = readFileSync(new URL('./ui/panelDisclosure.js', import.meta.url), 'utf8');
assert.match(panels, /event\.key !== 'Escape'[\s\S]*?disclosure\?\.focus/); assert.match(panels, /event\.key !== 'Escape'[\s\S]*?disclosure\?\.focus/);
assert.match(ui, /querySelector\('\.map-stack-chip\.active'\)\s*\|\| panel\.querySelector\('\.map-stack-chip'\)/); assert.match(ui, /querySelector\(\s*'\.map-stack-chip\.active',?\s*\)\s*\|\|\s*panel\.querySelector\('\.map-stack-chip'\)/);
const controls = readFileSync(new URL('./ui/mapSourceControls.js', import.meta.url), 'utf8'); const controls = readFileSync(new URL('./ui/mapSourceControls.js', import.meta.url), 'utf8');
assert.match(ui, /return this\._mapSourceControls\.select\(stackId, \{ syncShare \}\)/, assert.match(ui, /return this\._mapSourceControls\.select\(stackId, \{ syncShare \}\)/,
+7 -7
View File
@@ -178,8 +178,8 @@ test('desktop panel lanes use per-panel allocations and presentation-only auto-c
/panel !== displayPanel[\s\S]*?removeProperty\('--right-panel-allocated-height'\)[\s\S]*?const naturalHeight/, /panel !== displayPanel[\s\S]*?removeProperty\('--right-panel-allocated-height'\)[\s\S]*?const naturalHeight/,
'right intrinsic measurement must retain Display allocation while clearing other panel allocations', 'right intrinsic measurement must retain Display allocation while clearing other panel allocations',
); );
assert.match(css, /var\(--left-panel-allocated-height/); assert.match(css, /var\(\s*--left-panel-allocated-height/);
assert.match(css, /var\(--right-panel-allocated-height/); assert.match(css, /var\(\s*--right-panel-allocated-height/);
assert.match( assert.match(
css, css,
/#left-panel-stack\.layout-focus > \[data-panel-id\]\.collapsed\s*\{\s*display:\s*none;/, /#left-panel-stack\.layout-focus > \[data-panel-id\]\.collapsed\s*\{\s*display:\s*none;/,
@@ -217,7 +217,7 @@ test('parameterized Display presets keep one stable scroll owner', () => {
const css = readStylesheet(new URL('../style.css', import.meta.url)); const css = readStylesheet(new URL('../style.css', import.meta.url));
assert.match(css, /#pp-toggles:not\(\.collapsed\) > #param-slider-panel\.active\s*\{[\s\S]*?flex:\s*0 0 auto;[\s\S]*?max-height:\s*none;[\s\S]*?overflow-y:\s*visible;/); assert.match(css, /#pp-toggles:not\(\.collapsed\) > #param-slider-panel\.active\s*\{[\s\S]*?flex:\s*0 0 auto;[\s\S]*?max-height:\s*none;[\s\S]*?overflow-y:\s*visible;/);
assert.match(ui, /readDisplayScrollTop: \(\) => this\._displayPortalScrollRestoreOwner === 'standard'[\s\S]*?this\._standardDisplayScrollTop[\s\S]*?this\._ppToggles\?\.scrollTop \|\| 0/); assert.match(ui, /readDisplayScrollTop: \(\) =>\s*this\._displayPortalScrollRestoreOwner === 'standard'[\s\S]*?this\._standardDisplayScrollTop[\s\S]*?this\._ppToggles\?\.scrollTop \|\| 0/);
assert.match(rightRail, /displayPanel\.scrollTop = Math\.min\(displayScrollTop, maxScrollTop\);/); assert.match(rightRail, /displayPanel\.scrollTop = Math\.min\(displayScrollTop, maxScrollTop\);/);
assert.match( assert.match(
ui, ui,
@@ -243,7 +243,7 @@ test('expanded Display uses its container shell instead of a nested header card'
); );
assert.match( assert.match(
css, css,
/#pp-toggles\.collapsed \.pp-header-row\s*\{[\s\S]*?width:\s*var\(--right-collapsed-width, 132px\);/, /#pp-toggles\.collapsed \.pp-header-row\s*\{[\s\S]*?width:\s*var\(\s*--right-collapsed-width, 132px\);/,
'collapsed Display must retain its standalone launcher sizing', 'collapsed Display must retain its standalone launcher sizing',
); );
}); });
@@ -257,7 +257,7 @@ test('expanded left panels integrate their headers with the container shell', ()
); );
assert.match( assert.match(
css, css,
/#left-panel-stack > \[data-panel-id\]:not\(\.collapsed\) \.panel-divider\s*\{[\s\S]*?linear-gradient\(90deg, rgb\(0 212 255 \/ 28%\), rgba\(0, 212, 255, 0\.18\) 58%, transparent\)[\s\S]*?box-shadow:\s*0 0 7px rgba\(0, 212, 255, 0\.22\);/, /#left-panel-stack > \[data-panel-id\]:not\(\.collapsed\) \.panel-divider\s*\{[\s\S]*?linear-gradient\(\s*90deg,\s*rgb\(0 212 255 \/ 28%\),\s*rgba\(0, 212, 255, 0\.18\) 58%,\s*transparent\s*\)[\s\S]*?box-shadow:\s*0 0 7px rgba\(0, 212, 255, 0\.22\);/,
); );
}); });
@@ -284,10 +284,10 @@ test('expanded right panels highlight the title divider without changing collaps
); );
assert.match( assert.match(
css, css,
/#right-context-rail \[data-panel-id\]:not\(\.collapsed\) \.panel-divider\s*\{[\s\S]*?linear-gradient\(90deg, rgb\(0 212 255 \/ 28%\), rgba\(0, 212, 255, 0\.18\) 58%, transparent\)[\s\S]*?box-shadow:\s*0 0 7px rgba\(0, 212, 255, 0\.22\);/, /#right-context-rail \[data-panel-id\]:not\(\.collapsed\) \.panel-divider\s*\{[\s\S]*?linear-gradient\(\s*90deg,\s*rgb\(0 212 255 \/ 28%\),\s*rgba\(0, 212, 255, 0\.18\) 58%,\s*transparent\s*\)[\s\S]*?box-shadow:\s*0 0 7px rgba\(0, 212, 255, 0\.22\);/,
); );
assert.match( assert.match(
css, css,
/#param-slider-panel:not\(\.collapsed\) \.param-panel-divider\s*\{[\s\S]*?linear-gradient\(90deg, rgb\(0 212 255 \/ 28%\), rgba\(0, 212, 255, 0\.18\) 58%, transparent\);/, /#param-slider-panel:not\(\.collapsed\) \.param-panel-divider\s*\{[\s\S]*?linear-gradient\(\s*90deg,\s*rgb\(0 212 255 \/ 28%\),\s*rgba\(0, 212, 255, 0\.18\) 58%,\s*transparent\s*\);/,
); );
}); });
+3 -3
View File
@@ -207,7 +207,7 @@ test('Radio volume and mission speed share the Sharpen slider visual language',
assert.match(css, /\.gev-quantitative-slider::-webkit-slider-runnable-track\s*\{[\s\S]*?height: 3px;[\s\S]*?background: rgba\(255, 255, 255, 0\.08\);/); assert.match(css, /\.gev-quantitative-slider::-webkit-slider-runnable-track\s*\{[\s\S]*?height: 3px;[\s\S]*?background: rgba\(255, 255, 255, 0\.08\);/);
assert.match(css, /\.gev-quantitative-slider::-webkit-slider-thumb\s*\{[\s\S]*?width: 10px;[\s\S]*?height: 10px;[\s\S]*?border-radius: 50%;[\s\S]*?background: var\(--accent\);/); assert.match(css, /\.gev-quantitative-slider::-webkit-slider-thumb\s*\{[\s\S]*?width: 10px;[\s\S]*?height: 10px;[\s\S]*?border-radius: 50%;[\s\S]*?background: var\(--accent\);/);
assert.match(css, /\.gev-quantitative-slider:focus-visible\s*\{[\s\S]*?outline: 1px solid/); assert.match(css, /\.gev-quantitative-slider:focus-visible\s*\{[\s\S]*?outline: 1px solid/);
assert.match(css, /\.gev-quantitative-slider:disabled\s*\{[\s\S]*?opacity: \.42;[\s\S]*?cursor: not-allowed;/); assert.match(css, /\.gev-quantitative-slider:disabled\s*\{[\s\S]*?opacity: 0\.42;[\s\S]*?cursor: not-allowed;/);
assert.match(css, /\.gev-slider-value\s*\{[\s\S]*?color: var\(--accent\);[\s\S]*?font-size: 9px;/); assert.match(css, /\.gev-slider-value\s*\{[\s\S]*?color: var\(--accent\);[\s\S]*?font-size: 9px;/);
assert.doesNotMatch(css, /#space-mission-panel \[data-mission-replay-speed\]::-webkit-slider-thumb/); assert.doesNotMatch(css, /#space-mission-panel \[data-mission-replay-speed\]::-webkit-slider-thumb/);
}); });
@@ -245,13 +245,13 @@ test('Radio is nested inside Context with separate disclosure and power controls
assert.match(css, /\.radio-tuner\.is-static/); assert.match(css, /\.radio-tuner\.is-static/);
assert.match(css, /\.radio-tuner-tick\s*\{/); assert.match(css, /\.radio-tuner-tick\s*\{/);
assert.doesNotMatch(css, /radio-tuner-scale-(?:left|right)/); assert.doesNotMatch(css, /radio-tuner-scale-(?:left|right)/);
assert.match(css, /\.radio-tuner-needle\s*\{[\s\S]*?transition: left \.18s ease-out;/); assert.match(css, /\.radio-tuner-needle\s*\{[\s\S]*?transition: left 0\.18s ease-out;/);
assert.match(css, /\.radio-tuner\.is-dragging \.radio-tuner-needle,[\s\S]*?\.radio-tuner\.is-dragging \.radio-tuner-tick\s*\{\s*transition: none;/); assert.match(css, /\.radio-tuner\.is-dragging \.radio-tuner-needle,[\s\S]*?\.radio-tuner\.is-dragging \.radio-tuner-tick\s*\{\s*transition: none;/);
assert.match(css, /\.radio-tuner\s*\{[\s\S]*?max-width: 100%;[\s\S]*?overflow: hidden;/); assert.match(css, /\.radio-tuner\s*\{[\s\S]*?max-width: 100%;[\s\S]*?overflow: hidden;/);
assert.match(css, /#radio-tuner-slider\s*\{[\s\S]*?max-width: 100%;[\s\S]*?touch-action: none;/); assert.match(css, /#radio-tuner-slider\s*\{[\s\S]*?max-width: 100%;[\s\S]*?touch-action: none;/);
assert.match(css, /#title-bar\.radio-broadcasting \.title-logo::before/); assert.match(css, /#title-bar\.radio-broadcasting \.title-logo::before/);
assert.match(css, /#title-bar\.radio-broadcasting \.title-logo::after/); assert.match(css, /#title-bar\.radio-broadcasting \.title-logo::after/);
assert.match(css, /--radio-broadcast-opacity: \.17/); assert.match(css, /--radio-broadcast-opacity: 0\.17/);
assert.match(css, /@media \(prefers-reduced-motion: reduce\)[\s\S]*?\.radio-tuner-needle,[\s\S]*?\.radio-tuner-tick\s*\{\s*transition: none;/); assert.match(css, /@media \(prefers-reduced-motion: reduce\)[\s\S]*?\.radio-tuner-needle,[\s\S]*?\.radio-tuner-tick\s*\{\s*transition: none;/);
assert.doesNotMatch(ui, /_radioTunerCameraRemove = this\.viewer\?\.camera\?\.changed/); assert.doesNotMatch(ui, /_radioTunerCameraRemove = this\.viewer\?\.camera\?\.changed/);
assert.match(radioPresentation, /classList\.toggle\('radio-broadcasting', state\.audioState === 'playing'\)/); assert.match(radioPresentation, /classList\.toggle\('radio-broadcasting', state\.audioState === 'playing'\)/);
+5 -2
View File
@@ -1,3 +1,4 @@
import { StyleManager } from './ui/applicationShell.js';
import { _claimContextVisualAuthority, setContextMode } from './ui/contextActions.js'; import { _claimContextVisualAuthority, setContextMode } from './ui/contextActions.js';
import { _initGlobalContextPanel } from './ui/contextBindings.js'; import { _initGlobalContextPanel } from './ui/contextBindings.js';
import { test } from 'node:test'; import { test } from 'node:test';
@@ -9,6 +10,8 @@ import { createDefaultLayerState } from './data/layerState.js';
const uiSource = fs.readFileSync(new URL('./ui/applicationShell.js', import.meta.url), 'utf8'); const uiSource = fs.readFileSync(new URL('./ui/applicationShell.js', import.meta.url), 'utf8');
function sourceBlock(start, end) { function sourceBlock(start, end) {
const name = start.trim().match(/^(?:async )?(\w+)\(/)?.[1];
if (typeof StyleManager.prototype[name] === 'function') return StyleManager.prototype[name].toString();
const startIndex = uiSource.indexOf(start); const startIndex = uiSource.indexOf(start);
const endIndex = uiSource.indexOf(end, startIndex + start.length); const endIndex = uiSource.indexOf(end, startIndex + start.length);
assert.ok(startIndex >= 0, `missing source block start: ${start}`); assert.ok(startIndex >= 0, `missing source block start: ${start}`);
@@ -194,7 +197,7 @@ test('a shared view reserves its own camera without cancelling its saved Follow'
); );
// The shared view's own `cancelPendingSelection` must reach the stamp; other // The shared view's own `cancelPendingSelection` must reach the stamp; other
// stamp options may ride alongside it. // stamp options may ride alongside it.
assert.match(deferred, /_stampNavigation\(\{ cancelPendingSelection[^)]*\}\)/); assert.match(deferred, /_stampNavigation\(\{\s*cancelPendingSelection[^)]*\}\)/);
}); });
test('copy timestamp parsing is strict and rejects malformed or future values', () => { test('copy timestamp parsing is strict and rejects malformed or future values', () => {
@@ -472,7 +475,7 @@ test('explicit Context transitions claim the visual restore lane before transiti
'the Context authority helper must claim the visual lane', 'the Context authority helper must claim the visual lane',
); );
assert.match(uiSource, /claimVisualAuthority: \(\) => this\.shareLinkManager\?\.claimRestoreLane\?\.\('visual'\)/); assert.match(uiSource, /claimVisualAuthority: \(\) =>\s*this\.shareLinkManager\?\.claimRestoreLane\?\.\('visual'\)/);
const contextPanel = _initGlobalContextPanel.toString(); const contextPanel = _initGlobalContextPanel.toString();
for (const [start, end, label] of [ for (const [start, end, label] of [
["this.listen(this._globalContextFlightsBtn, 'click'", "this.listen(this._globalContextMissionsBtn, 'click'", 'Contacts tab'], ["this.listen(this._globalContextFlightsBtn, 'click'", "this.listen(this._globalContextMissionsBtn, 'click'", 'Contacts tab'],