import test from 'node:test'; import assert from 'node:assert/strict'; import { createSound } from '../src/client/sound.js'; test('audio starts only after unlock, deduplicates events, stops on mute and persists preference', async () => { let created = 0, started = 0, stopped = 0; const values = new Map(); const param = () => ({ value: 0, setValueAtTime() {}, exponentialRampToValueAtTime() {}, linearRampToValueAtTime() {} }); const node = () => ({ connect() {}, disconnect() {}, gain: param(), frequency: param(), pan: param(), start() { started++; }, stop() { stopped++; }, onended: null }); const context = { state: 'suspended', currentTime: 0, sampleRate: 44100, destination: {}, async resume() { this.state = 'running'; }, createGain: node, createOscillator: node, createStereoPanner: node, createDynamicsCompressor: node, }; const sound = createSound({ context: () => { created++; return context as unknown as AudioContext; }, storage: { getItem: key => values.get(key) ?? null, setItem: (key, value) => { values.set(key, value); } } }); sound.play('click'); assert.equal(created, 0); await sound.unlock(); assert.equal(created, 1); sound.play('click', 'one'); const count = started; sound.play('click', 'one'); assert.equal(started, count); sound.play('click', 'two'); assert.ok(started > count); sound.setMuted(true); assert.ok(stopped > 0); const mutedCount = started; sound.play('click'); assert.equal(started, mutedCount); assert.ok([...values.values()].includes('true')); });