import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { renderHook, act, waitFor } from '@testing-library/react' import { useRecorder } from './useRecorder' function setupAudio() { const tracks = [{ stop: vi.fn() }] const stream = { getTracks: () => tracks } as unknown as MediaStream Object.defineProperty(navigator, 'mediaDevices', { configurable: true, value: { getUserMedia: vi.fn().mockResolvedValue(stream) }, }) const node = { connect: vi.fn(), disconnect: vi.fn(), onaudioprocess: null as ((e: unknown) => void) | null } const source = { connect: vi.fn(), disconnect: vi.fn() } class FakeAC { sampleRate = 16000 destination = {} createMediaStreamSource = () => source createScriptProcessor = () => node close = vi.fn() } const w = window as unknown as Record w.AudioContext = FakeAC return { tracks, node } } describe('useRecorder (WAV)', () => { beforeEach(() => setupAudio()) afterEach(() => vi.restoreAllMocks()) it('records PCM and produces a WAV data URI, stopping mic tracks', async () => { const { tracks, node } = setupAudio() const { result } = renderHook(() => useRecorder()) await act(async () => { await result.current.start() }) await waitFor(() => expect(result.current.status).toBe('recording')) // feed one audio buffer, then stop act(() => node.onaudioprocess?.({ inputBuffer: { getChannelData: () => new Float32Array([0.1, -0.2, 0.3]) } })) act(() => result.current.stop()) await waitFor(() => expect(result.current.status).toBe('recorded')) await waitFor(() => expect(result.current.dataUri).toMatch(/^data:audio\/wav/)) expect(tracks[0].stop).toHaveBeenCalled() // mic released }) it('reports unsupported when WebAudio is unavailable', async () => { Object.defineProperty(navigator, 'mediaDevices', { configurable: true, value: { getUserMedia: vi.fn() } }) const w = window as unknown as Record w.AudioContext = undefined w.webkitAudioContext = undefined const { result } = renderHook(() => useRecorder()) await act(async () => { await result.current.start() }) expect(result.current.status).toBe('unsupported') }) })