import { afterEach, describe, expect, it, vi } from "vitest"; import { VoiceCallPlayer } from "@/app/call/voice-call-player"; describe("VoiceCallPlayer", () => { afterEach(() => { vi.unstubAllGlobals(); }); it("schedules decoded sentence audio on one continuous Web Audio timeline", async () => { const starts: number[] = []; const sources: Array<{ onended: (() => void) | null }> = []; const context = { state: "running", currentTime: 0, destination: {}, resume: vi.fn(async () => undefined), close: vi.fn(async () => undefined), decodeAudioData: vi.fn(async () => ({ duration: 1 })), createBufferSource: vi.fn(() => { const source = { buffer: null, onended: null as (() => void) | null, connect: vi.fn(), disconnect: vi.fn(), stop: vi.fn(), start: vi.fn((at: number) => starts.push(at)), }; sources.push(source); return source; }), }; vi.stubGlobal("AudioContext", function AudioContextMock() { return context; }); const player = new VoiceCallPlayer(); const gaps: number[] = []; player.onGap = (milliseconds) => gaps.push(milliseconds); const meta = { callId: "call-1", turnId: "turn-1", index: 0, mimeType: "audio/mpeg", byteLength: 3, }; player.enqueue(meta, new Uint8Array([1, 2, 3]).buffer); player.enqueue({ ...meta, index: 1 }, new Uint8Array([4, 5, 6]).buffer); await vi.waitFor(() => expect(starts).toHaveLength(2)); expect(starts).toEqual([0.025, 1.025]); expect(gaps).toEqual([0]); expect(sources).toHaveLength(2); player.dispose(); }); });