Skip to content
File

Blob: worker/tests/fixtures/radio.tsx

typescript73 lines
1import { useRef, useState } from 'react';
2import { initialState } from '@/client/radio/session-state.ts';
3import type { SessionState, SignalBuffer } from '@/client/radio/session-state.ts';
4import type { RadioActions } from '@/client/radio/use-radio.ts';
5 
6const track = { title: 'Morning Radio', artist: 'Example Artist', durationMs: 240000 };
7const room = {
8 online: true,
9 viewers: 1,
10 controller: null,
11 generation: 'fixture',
12 track,
13 nowPlaying: { revision: 1, trackIndex: 0, trackCount: 4, track },
14};
15 
16declare global {
17 interface Window {
18 exhibitFixture: { patch: (patch: Partial<SessionState>) => void; calls: () => string[] };
19 }
20}
21 
22// Replaces only the React/session boundary in browser tests; the real App renders.
23export function useFixtureRadio() {
24 const [state, setState] = useState<SessionState>(() => ({
25 ...initialState(),
26 auth: 'ready',
27 room,
28 nowPlaying: room.nowPlaying,
29 message: 'Board online. Start listening.',
30 }));
31 const calls = useRef<string[]>([]);
32 const signal = useRef<SignalBuffer>({ at: 0, count: 0 });
33 const audioRef = useRef<HTMLAudioElement>(null);
34 const patch = (next: Partial<SessionState>) => setState((previous) => ({ ...previous, ...next }));
35 window.exhibitFixture = { patch, calls: () => calls.current };
36 const actions: RadioActions = {
37 login: async () => {},
38 connect: async () => {
39 calls.current.push('connect');
40 patch({ phase: 'connecting', message: 'Opening data channels…' });
41 },
42 disconnect: async () => {
43 calls.current.push('disconnect');
44 patch({
45 phase: 'idle',
46 viewerId: undefined,
47 telemetry: undefined,
48 paused: undefined,
49 led: undefined,
50 busy: false,
51 audioBlocked: false,
52 room,
53 ack: { state: 'idle', text: '', at: 0 },
54 message: 'Disconnected. Shared playback is unchanged.',
55 });
56 },
57 toggleAudio: async () => {
58 calls.current.push('audio');
59 patch({ audioBlocked: false, muted: !state.muted });
60 },
61 setVolume: (volume) => patch({ volume }),
62 toggleControl: async () => {
63 calls.current.push('control');
64 patch({ busy: true });
65 },
66 send: (command) => {
67 calls.current.push(JSON.stringify(command));
68 patch({ ack: { state: 'pending', text: 'Waiting for board confirmation…', at: 10000 } });
69 },
70 };
71 return { state, signal: signal.current, actions, audioRef };
72}