import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { type OoSoundCue, ooSound } from "./oo-sound"; import { readOoSoundEnabled, writeOoSoundEnabled } from "./oo-sound-pref"; describe("oo-sound-pref", () => { const storage = new Map(); beforeEach(() => { storage.clear(); const localStorage = { getItem: (key: string) => storage.get(key) ?? null, setItem: (key: string, value: string) => { storage.set(key, value); }, removeItem: (key: string) => { storage.delete(key); }, }; vi.stubGlobal("localStorage", localStorage); vi.stubGlobal("window", { localStorage }); }); afterEach(() => { vi.unstubAllGlobals(); }); it("defaults sound to enabled", () => { expect(readOoSoundEnabled()).toBe(true); }); it("persists disabled preference", () => { writeOoSoundEnabled(false); expect(readOoSoundEnabled()).toBe(false); writeOoSoundEnabled(true); expect(readOoSoundEnabled()).toBe(true); }); }); describe("ooSound", () => { class FakeContext { state: AudioContextState = "running"; currentTime = 0; destination = {}; createGain() { return { gain: { value: 0, setValueAtTime: vi.fn(), exponentialRampToValueAtTime: vi.fn() }, connect: vi.fn(), }; } createOscillator() { return { type: "sine", frequency: { setValueAtTime: vi.fn() }, connect: vi.fn(), start: vi.fn(), stop: vi.fn(), }; } resume = vi.fn(async () => undefined); } beforeEach(() => { vi.stubGlobal("AudioContext", FakeContext); vi.stubGlobal("window", { matchMedia: () => ({ matches: false, addEventListener: vi.fn(), removeEventListener: vi.fn(), }), }); vi.stubGlobal("document", { visibilityState: "visible" }); writeOoSoundEnabled(true); ooSound.setEnabled(true); }); afterEach(() => { vi.unstubAllGlobals(); }); it("plays procedural cues when enabled", () => { const cues: OoSoundCue[] = [ "work-complete", "listening-start", "listening-stop", "speaking-start", "speaking-done", "attention", "message-sent", "handoff", ]; for (const cue of cues) { expect(() => ooSound.play(cue)).not.toThrow(); } }); it("skips playback when disabled", () => { ooSound.setEnabled(false); expect(() => ooSound.play("work-complete")).not.toThrow(); }); });