import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { AudioService } from "./audioService"; // ── WaveSurfer mock ──────────────────────────────────────────────────────────── const mockLoad = vi.fn(); const mockOn = vi.fn(); const mockUnAll = vi.fn(); const mockDestroy = vi.fn(); const mockGetDuration = vi.fn(() => 180); const mockSetOptions = vi.fn(); const mockCreate = vi.fn(); vi.mock("wavesurfer.js", () => ({ default: { create: (opts: unknown) => { mockCreate(opts); return { load: mockLoad, on: mockOn, unAll: mockUnAll, destroy: mockDestroy, getDuration: mockGetDuration, setOptions: mockSetOptions, isPlaying: vi.fn(() => false), }; }, }, })); // ── Zustand store mock ───────────────────────────────────────────────────────── vi.mock("../stores/playerStore", () => ({ usePlayerStore: { getState: vi.fn(() => ({ isPlaying: false, currentTime: 0, duration: 0, currentSongId: null, currentBandId: null, batchUpdate: vi.fn(), setDuration: vi.fn(), setCurrentSong: vi.fn(), })), subscribe: vi.fn(), }, })); // ── Helpers ──────────────────────────────────────────────────────────────────── function makeContainer(): HTMLDivElement { return document.createElement("div"); } function triggerWaveSurferReady() { // WaveSurfer fires the "ready" event via ws.on("ready", cb) const readyCb = mockOn.mock.calls.find(([event]) => event === "ready")?.[1]; readyCb?.(); } // ── Tests ────────────────────────────────────────────────────────────────────── describe("AudioService.initialize()", () => { let service: AudioService; beforeEach(() => { AudioService.resetInstance(); service = AudioService.getInstance(); vi.clearAllMocks(); mockGetDuration.mockReturnValue(180); }); afterEach(() => { AudioService.resetInstance(); }); it("calls ws.load(url) without peaks when no peaks provided", async () => { const container = makeContainer(); const url = "http://localhost/audio/song.mp3"; const initPromise = service.initialize(container, url); triggerWaveSurferReady(); await initPromise; expect(mockLoad).toHaveBeenCalledOnce(); const [calledUrl, calledPeaks] = mockLoad.mock.calls[0]; expect(calledUrl).toBe(url); expect(calledPeaks).toBeUndefined(); }); it("calls ws.load(url, [Float32Array]) when peaks are provided", async () => { const container = makeContainer(); const url = "http://localhost/audio/song.mp3"; const peaks = Array.from({ length: 500 }, (_, i) => i / 500); const initPromise = service.initialize(container, url, peaks); triggerWaveSurferReady(); await initPromise; expect(mockLoad).toHaveBeenCalledOnce(); const [calledUrl, calledChannelData] = mockLoad.mock.calls[0]; expect(calledUrl).toBe(url); expect(calledChannelData).toHaveLength(1); expect(calledChannelData[0]).toBeInstanceOf(Float32Array); expect(calledChannelData[0]).toHaveLength(500); expect(calledChannelData[0][0]).toBeCloseTo(0); expect(calledChannelData[0][499]).toBeCloseTo(499 / 500); }); it("does not re-initialize for the same url and container", async () => { const container = makeContainer(); const url = "http://localhost/audio/song.mp3"; const peaks = [0.5, 0.5, 0.5]; const p1 = service.initialize(container, url, peaks); triggerWaveSurferReady(); await p1; vi.clearAllMocks(); // Second call with same URL + container: should no-op await service.initialize(container, url, peaks); expect(mockLoad).not.toHaveBeenCalled(); }); });