import { TestCase } from "./framework/test_case.js"; import { setupDom, teardownDom, FakeAudio } from "./framework/dom.js"; import { AudioManager, EFFECT_PRESETS } from "../client/js/audio.js"; // A minimal Web Audio stand-in that records every node it hands out and the // connections made between them. function fakeAudioContext() { const nodes = []; const node = (kind) => { const created = { kind, connected: [], connect(target) { this.connected.push(target); return target; }, disconnect() { this.disconnected = true; }, }; nodes.push(created); return created; }; return { nodes, state: "running", destination: node("destination"), resume() { return Promise.resolve(); }, createMediaElementSource: () => node("source"), createBiquadFilter: () => Object.assign(node("biquad"), { type: "", frequency: { value: 0 }, Q: { value: 0 } }), createGain: () => Object.assign(node("gain"), { gain: { value: 0 } }), createDelay: () => Object.assign(node("delay"), { delayTime: { value: 0 } }), }; } export class AudioTest extends TestCase { test_effect_sources_pick_the_chosen_library_files() { const manager = new AudioManager(); this.assertEqual(manager.effects.click, "assets/sounds/button_click.mp3"); this.assertEqual(manager.effects.complete, "assets/sounds/build_complete.mp3"); this.assertEqual(manager.effects.notification, "assets/sounds/notification.mp3"); this.assertEqual(manager.effects.bomb, "assets/sounds/death_flash.flac"); this.assertEqual(manager.musicSource, "assets/sounds/crashed_ship.ogg"); } async test_plays_effects_and_mutes() { const env = await setupDom(); try { FakeAudio.reset(); const manager = new AudioManager(); manager.play("click"); this.assertEqual(FakeAudio.playedSources, ["assets/sounds/button_click.mp3"]); // The bombardment passes an explicit, zoom-scaled volume. manager.play("bomb", { volume: 0.25 }); const bomb = FakeAudio.instances[FakeAudio.instances.length - 1]; this.assertEqual(bomb.src, "assets/sounds/death_flash.flac"); this.assertApprox(bomb.volume, 0.25, 1e-9, "the volume option overrides the default"); manager.setEnabled(false); const muted = FakeAudio.playedSources.length; manager.play("click"); manager.play("bomb", { volume: 0.9 }); this.assertEqual(FakeAudio.playedSources.length, muted, "muted effects are silent"); let changed = null; manager.onChange = (value) => { changed = value; }; manager.setEnabled(true); this.assertEqual(changed, true, "the toggle reports its new state"); } finally { teardownDom(env); } } async test_bombardment_uses_a_lowpass_and_echo() { const env = await setupDom(); try { FakeAudio.reset(); const context = fakeAudioContext(); const manager = new AudioManager({ contextFactory: () => context }); // Keep the graph-cleanup timer short so the test does not linger. manager.presets.bomb.echoTail = 0.01; manager.play("bomb", { volume: 0.4, duration: 0.01 }); const filter = context.nodes.find((node) => node.kind === "biquad"); this.assertNotNull(filter, "the clip runs through a biquad filter"); this.assertEqual(filter.type, "lowpass", "the blasts are muffled"); this.assertApprox(filter.frequency.value, EFFECT_PRESETS.bomb.lowpassHz); const delay = context.nodes.find((node) => node.kind === "delay"); this.assertNotNull(delay, "an echo delay is built"); this.assertApprox(delay.delayTime.value, EFFECT_PRESETS.bomb.echoDelay); const feedback = delay.connected.find( (node) => node.kind === "gain" && node.connected.includes(delay) ); this.assertNotNull(feedback, "the delay feeds back on itself"); this.assertApprox(feedback.gain.value, EFFECT_PRESETS.bomb.echoFeedback); const played = FakeAudio.instances.find((audio) => audio.src === "assets/sounds/death_flash.flac"); this.assertNotNull(played, "the clip is played through the graph"); } finally { teardownDom(env); } } async test_music_loops_and_stops() { const env = await setupDom(); try { FakeAudio.reset(); const manager = new AudioManager(); manager.requestMusic(); const music = FakeAudio.instances.find((audio) => audio.src === manager.musicSource); this.assertNotNull(music, "the music element was created"); this.assertTrue(music.loop, "the music loops"); this.assertEqual(music.plays, 1, "the music was started once"); manager.stopMusic(); this.assertTrue(music.paused, "stopping pauses the music"); // Muting while music is wanted stops it; unmuting starts it again. manager.requestMusic(); manager.setEnabled(false); manager.setEnabled(true); this.assertGreater(FakeAudio.instances.length, 1, "music restarts when unmuted"); } finally { teardownDom(env); } } }