refactor: isolate Radio controls and tuner presentation

This commit is contained in:
Sameh Khamis committed 2026-09-12 23:23:20 -07:00
1 parent 79a0f09087
commit 7b9aca8597
21 files changed
+1507 -1134

No files matched your search

+30 -30
View File
@@ -1,3 +1,7 @@
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 assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
@@ -249,19 +253,19 @@ test('Radio is nested inside Context with separate disclosure and power controls
assert.match(css, /--radio-broadcast-opacity: \.17/);
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.match(ui, /classList\.toggle\('radio-broadcasting', state\.audioState === 'playing'\)/);
assert.match(ui, /cycleStation\(direction, \{[\s\S]*?rotate,[\s\S]*?stationIds:/);
const cycleStart = ui.indexOf('const cycleRadio = (direction, { rotate = true } = {}) =>');
const cycleMethod = ui.slice(cycleStart, ui.indexOf('const toggleRadio', cycleStart));
assert.match(radioPresentation, /classList\.toggle\('radio-broadcasting', state\.audioState === 'playing'\)/);
assert.match(radioBindings, /cycleStation\(direction, \{[\s\S]*?rotate,[\s\S]*?stationIds:/);
const cycleStart = radioBindings.indexOf('const cycleRadio = (direction, { rotate = true } = {}) =>');
const cycleMethod = radioBindings.slice(cycleStart, radioBindings.indexOf('const toggleRadio', cycleStart));
assert.doesNotMatch(cycleMethod, /refreshTunerBand/);
assert.match(ui, /_radioTunerBandPinnedForNavigation = true/);
assert.match(ui, /viewer\?\.canvas\?\.addEventListener\('pointerdown', releaseNavigationBand/);
assert.match(ui, /previewTuningStation\(station\?\.id \|\| null, \{ rotate \}\)/);
assert.match(ui, /tunerPreview\(\{ coordinate: this\._radioTunerCoordinate, rotate: commit \}\)/);
assert.match(ui, /radioLayer\.cancelTuning\(\)/);
assert.match(ui, /classList\.remove\('radio-broadcasting'\)/);
assert.match(ui, /radioLayer\.getTunerStations\(750\)/);
assert.match(ui, /radioTunerPointerPosition\(/);
assert.match(radioBindings, /_radioTunerBandPinnedForNavigation = true/);
assert.match(radioBindings, /listen\(this\.canvas, 'pointerdown', releaseNavigationBand/);
assert.match(radioBindings, /previewTuningStation\(station\?\.id \|\| null, \{ rotate \}\)/);
assert.match(radioBindings, /tunerPreview\(\{ coordinate: this\._radioTunerCoordinate, rotate: commit \}\)/);
assert.match(radioBindings, /this\.radio\.cancelTuning\(\)/);
assert.match(radioControlsSource, /classList\.remove\('radio-broadcasting'\)/);
assert.match(radioBindings, /this\.radio\.getTunerStations\(750\)/);
assert.match(radioBindings, /radioTunerPointerPosition\(/);
assert.doesNotMatch(css, /#right-context-rail\s*>\s*#radio-panel/);
assert.match(css, /#global-context-panel #radio-panel\.collapsed/);
assert.doesNotMatch(css, /\.context-radio-dock\.active:hover \.context-radio-mini/);
@@ -277,16 +281,15 @@ test('panel collapse is presentation-only and Radio exposes explicit voice playb
assert.match(voice, /'radio'/);
assert.match(voice, /name:\s*'control_radio'/);
assert.deepEqual(realtimeTools().find(tool => tool.name === 'control_radio').parameters.properties.action.enum, ['enable', 'disable', 'play', 'resume', 'pause', 'stop', 'next', 'previous', 'volume', 'select', 'status']);
const enableStart = ui.lastIndexOf('\n _initRadioPanel()');
const enableMethod = ui.slice(enableStart, ui.indexOf('\n _renderRadioState(state)', enableStart));
const enableMethod = radioBindings;
assert.doesNotMatch(enableMethod, /playSelectedRadio|togglePlayback\(\).*radio-enable/i);
assert.match(enableMethod, /contextRadioToggleBtn/);
assert.match(enableMethod, /contextRadioMiniEnableBtn/);
assert.match(enableMethod, /contextRadioDetailsBtn/);
assert.match(enableMethod, /contextRadioMiniCloseBtn/);
assert.match(enableMethod, /contextRadioMiniPlayBtn/);
const disclosureStart = enableMethod.indexOf('this._contextRadioToggleBtn?.addEventListener');
const disclosureEnd = enableMethod.indexOf("this._radioFilter?.addEventListener", disclosureStart);
const disclosureStart = enableMethod.indexOf('this.listen(this._contextRadioToggleBtn,');
const disclosureEnd = enableMethod.indexOf("this.listen(this._radioFilter,", disclosureStart);
const disclosureBindings = enableMethod.slice(disclosureStart, disclosureEnd);
assert.ok(disclosureStart >= 0 && disclosureEnd > disclosureStart, 'compact Radio disclosure bindings are missing');
assert.doesNotMatch(disclosureBindings, /toggleRadio\(this\._contextRadioToggleBtn\)/);
@@ -304,32 +307,29 @@ test('panel collapse is presentation-only and Radio exposes explicit voice playb
});
test('expanded Context routes its Radio icon to the embedded section and keeps collapsed Context compact', () => {
const revealStart = ui.indexOf('\n async _revealRadioPanelInsideContext');
const revealEnd = ui.indexOf('\n /**', revealStart + 10);
const revealMethod = ui.slice(revealStart, revealEnd);
const revealStart = radioControlsSource.indexOf('\n async _revealRadioPanelInsideContext');
const revealEnd = radioControlsSource.indexOf('\n /**', revealStart + 10);
const revealMethod = radioControlsSource.slice(revealStart, revealEnd);
assert.ok(revealStart >= 0 && revealEnd > revealStart, 'embedded Radio reveal helper is missing');
assert.match(revealMethod, /requestAnimationFrame\(\(\) => requestAnimationFrame/);
assert.match(revealMethod, /scroller\.scrollTo\(\{ top: next, behavior: reducedMotion \? 'auto' : 'smooth' \}\)/);
assert.match(revealMethod, /focus\?\.\(\{ preventScroll: true \}\)/);
assert.doesNotMatch(revealMethod, /setEnabled|togglePlayback|selectStation|setContextMode/);
const syncStart = ui.indexOf('\n _syncContextRadioLauncherState()');
const syncEnd = ui.indexOf('\n /**', syncStart + 10);
const syncMethod = ui.slice(syncStart, syncEnd);
const syncStart = radioControlsSource.indexOf('\n _syncContextRadioLauncherState()');
const syncEnd = radioControlsSource.indexOf('\n destroy()', syncStart + 10);
const syncMethod = radioControlsSource.slice(syncStart, syncEnd);
assert.ok(syncStart >= 0 && syncEnd > syncStart, 'Context Radio launcher state sync is missing');
assert.match(syncMethod, /contextExpanded[\s\S]*?aria-controls', 'radio-panel'[\s\S]*?aria-expanded', String\(radioExpanded\)/);
assert.match(syncMethod, /aria-controls', 'context-radio-mini'[\s\S]*?aria-expanded', String\(compactOpen\)/);
const renderStart = ui.indexOf('\n _renderRadioState(state)');
const renderMethod = ui.slice(renderStart, ui.indexOf('\n _renderRadioTuner', renderStart));
const renderMethod = radioPresentation;
assert.match(renderMethod, /this\._syncContextRadioLauncherState\(\)/);
assert.doesNotMatch(renderMethod, /compact Radio controls/);
assert.doesNotMatch(renderMethod, /_contextRadioToggleBtn\.setAttribute\('aria-(?:controls|expanded|label)'/);
});
test('Radio disclosure is explicit, starts closed while off, and preserves playback state', () => {
const renderStart = ui.indexOf('\n _renderRadioState(state)');
const renderMethod = ui.slice(renderStart, ui.indexOf('\n _renderRadioTuner', renderStart));
assert.ok(renderStart >= 0, 'Radio render method is missing');
const renderMethod = radioPresentation;
assert.doesNotMatch(renderMethod, /setPanelCollapsed\('radio-panel', true\).*stopPlayback/s);
assert.doesNotMatch(renderMethod, /_radioMiniExpanded\s*=\s*false.*audioState === 'playing'/s);
assert.match(ui, /contextRadioDetailsBtn/);
@@ -346,8 +346,8 @@ test('successful explicit user playback hands the speaker from voice to Radio',
assert.ok(confirmedPlaying >= 0 && takeoverSignal > confirmedPlaying);
assert.match(radio, /startPlayback: \(\) => playSelectedRadio\(\{ origin: 'voice', attemptId: options\.attemptId \}\)/);
assert.match(radio, /selectRadioStation\(stationId, \{ autoplay: true, origin: 'user' \}\)/);
assert.match(ui, /togglePlayback\(\{ origin: 'user' \}\)/);
assert.match(ui, /cycleStation\(direction, \{[\s\S]*?origin: 'user'/);
assert.match(ui, /commitTuningStation\(station\.id, \{ origin: 'user' \}\)/);
assert.match(radioBindings, /togglePlayback\(\{ origin: 'user' \}\)/);
assert.match(radioBindings, /cycleStation\(direction, \{[\s\S]*?origin: 'user'/);
assert.match(radioBindings, /commitTuningStation\(station\.id, \{ origin: 'user' \}\)/);
assert.match(realtime, /event\.origin === 'user' && event\.action === 'play' && this\.isActive\(\)[\s\S]*?this\.stop\(\{ preserveRadioPlayback: true \}\)/);
});