import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { createPulseTap, readSwitchboard, setMusicPulseAudible } from '@/audio/music-pulse'; import { TRAFFIC_PASS_GAP_SECONDS, TRAFFIC_PASS_PAN, playTrafficPass } from '@/audio/traffic-pass'; /** A minimal audio graph that records what the swell builds. */ function fakeContext() { const pans: number[] = []; const param = () => ({ setValueAtTime: (v: number) => pans.push(v), exponentialRampToValueAtTime: () => undefined }); const node = (extra: object = {}) => ({ connect: () => undefined, disconnect: () => undefined, ...extra }); const context = { state: 'running', currentTime: 10, sampleRate: 8000, started: 0, createGain: () => node({ context, gain: { setValueAtTime: () => undefined, exponentialRampToValueAtTime: () => undefined } }), createAnalyser: () => { throw new Error('no analyser'); }, createBuffer: (_channels: number, length: number) => ({ sampleRate: 8000, getChannelData: () => new Float32Array(length) }), createBufferSource: () => node({ buffer: null, onended: null, start: () => { context.started += 1; }, stop: () => undefined, }), createBiquadFilter: () => node({ type: '', Q: { setValueAtTime: () => undefined }, frequency: { setValueAtTime: () => undefined, exponentialRampToValueAtTime: () => undefined }, }), createStereoPanner: () => node({ pan: param() }), }; return { context, pans }; } describe('traffic pass swell', () => { afterEach(() => setMusicPulseAudible(false)); it('is silent with no audible soundtrack', () => { expect(readSwitchboard()).toBeNull(); expect(playTrafficPass(1)).toBe(false); }); it('plays once per gap, panned to the passing side', () => { const { context, pans } = fakeContext(); createPulseTap(context as unknown as BaseAudioContext, { connect: () => undefined } as unknown as AudioNode); setMusicPulseAudible(true); expect(playTrafficPass(-1)).toBe(true); expect(pans).toContain(-TRAFFIC_PASS_PAN); expect(playTrafficPass(1)).toBe(false); context.currentTime += TRAFFIC_PASS_GAP_SECONDS + 0.01; expect(playTrafficPass(1)).toBe(true); expect(pans).toContain(TRAFFIC_PASS_PAN); expect(context.started).toBe(2); }); it('never plays on a suspended context', () => { const { context } = fakeContext(); context.state = 'suspended'; createPulseTap(context as unknown as BaseAudioContext, { connect: () => undefined } as unknown as AudioNode); setMusicPulseAudible(true); expect(playTrafficPass(1)).toBe(false); }); it('stays out of the initial bundle: only the scene chunk imports it, lazily, never on lite', () => { const environment = readFileSync(resolve(process.cwd(), 'src/scene/world/environment.ts'), 'utf8'); expect(environment).toContain("import('@/audio/traffic-pass')"); expect(environment).not.toMatch(/from '@\/audio\/traffic-pass'/u); const budgets = readFileSync(resolve(process.cwd(), 'scripts/lib/bundle-budgets.mjs'), 'utf8'); expect(budgets).toContain('traffic-pass-'); }); });