import { afterEach, describe, expect, it, vi } from 'vitest'; import { AudioAnalysis } from './analysis'; import { AudioEngine } from './engine'; afterEach(() => vi.restoreAllMocks()); function fixture() { const sample = vi.fn((data: Uint8Array) => { data.fill(0); // 48 kHz / 1024: bins 1..4 cover the bass band. data.fill(255, 1, 5); }); const analyser = { context: { sampleRate: 48000 }, fftSize: 1024, getByteFrequencyData: sample } as unknown as AnalyserNode; const analysis = new AudioAnalysis(() => analyser); return { analysis, sample }; } describe('audio reactivity', () => { it('separates bass from the other frequency bands and shares the frame', () => { vi.spyOn(performance, 'now').mockReturnValue(100); const { analysis, sample } = fixture(); expect(analysis.read(true)).toEqual({ bass: 1, mid: 0, treble: 0 }); analysis.read(true); expect(sample).toHaveBeenCalledTimes(1); }); it('returns silence after pausing or muting', () => { const clock = vi.spyOn(performance, 'now').mockReturnValue(100); const { analysis, sample } = fixture(); analysis.read(true); clock.mockReturnValue(120); expect(analysis.read(false)).toEqual({ bass: 0, mid: 0, treble: 0 }); clock.mockReturnValue(140); expect(analysis.read(false)).toEqual({ bass: 0, mid: 0, treble: 0 }); expect(sample).toHaveBeenCalledTimes(1); }); it('works when Web Audio is unavailable', () => { const analysis = new AudioAnalysis(() => null); expect(analysis.read(false)).toEqual({ bass: 0, mid: 0, treble: 0 }); }); }); describe('audio engine lifetime', () => { it('removes audio listeners on disposal', () => { const audio = document.createElement('audio'); const engine = new AudioEngine(audio); const playing = vi.fn(); engine.on('playing', playing); audio.dispatchEvent(new Event('playing')); expect(playing).toHaveBeenCalledTimes(1); engine.dispose(); audio.dispatchEvent(new Event('playing')); expect(playing).toHaveBeenCalledTimes(1); }); });