159 lines
5.4 KiB
JavaScript
159 lines
5.4 KiB
JavaScript
/**
|
|
* SFX voice test (dev tool, run with Node — no browser needed):
|
|
*
|
|
* node dev/sfx.test.mjs
|
|
*
|
|
* Exercises the shared SFX voice (js/utils/Sfx.js) with a stub scene:
|
|
* playSfxOn() guard rails (master switch off, missing sound manager,
|
|
* asset not in the audio cache, the happy path, extra play options like
|
|
* loop) and the stop/status helpers (stopSfxOn / sfxPlayingOn). Also
|
|
* checks the sfx.json keys follow the play-name convention (sfx_<key>
|
|
* cache key) and point at files that exist.
|
|
*/
|
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
import fs from 'node:fs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const SFX = JSON.parse(fs.readFileSync(join(__dirname, '../data/sfx.json'), 'utf8'));
|
|
|
|
const { config } = await import(pathToFileURL(join(__dirname, '../js/config/Config.js')).href);
|
|
config.init({ sfx: { ...SFX } });
|
|
|
|
const { playSfxOn, stopSfxOn, sfxPlayingOn } = await import(pathToFileURL(join(__dirname, '../js/utils/Sfx.js')).href);
|
|
|
|
let failures = 0;
|
|
const check = (label, cond) => {
|
|
console.log(`${cond ? '✔' : '✘ FAIL'} ${label}`);
|
|
if (!cond) failures++;
|
|
};
|
|
|
|
/** A stub scene: sound manager + audio cache, recording play() calls. */
|
|
const makeScene = (o = {}) => {
|
|
const calls = [];
|
|
const stops = [];
|
|
let playing = false;
|
|
return {
|
|
calls,
|
|
stops,
|
|
sound:
|
|
o.sound === false
|
|
? null
|
|
: {
|
|
play: (key, opts) => {
|
|
calls.push({ key, opts });
|
|
playing = true;
|
|
},
|
|
stopByKey: (key) => {
|
|
stops.push(key);
|
|
playing = false;
|
|
},
|
|
isPlaying: (key) => playing && key === calls[calls.length - 1]?.key,
|
|
},
|
|
cache: o.noCache
|
|
? undefined
|
|
: { audio: o.noAudio ? undefined : { has: (k) => (o.inCache === false ? false : true) } },
|
|
};
|
|
};
|
|
|
|
// 1) Happy path: plays the sfx_ key at sfx.volume.
|
|
{
|
|
const scene = makeScene();
|
|
playSfxOn(scene, 'ui_click');
|
|
check('plays sfx_ui_click at sfx.volume',
|
|
scene.calls.length === 1 &&
|
|
scene.calls[0].key === 'sfx_ui_click' &&
|
|
scene.calls[0].opts.volume === SFX.volume);
|
|
}
|
|
|
|
// 2) Master switch off → silent, no throw.
|
|
{
|
|
const scene = makeScene();
|
|
config.init({ sfx: { ...SFX, enabled: false } });
|
|
playSfxOn(scene, 'ui_hover');
|
|
check('sfx.enabled=false → nothing plays', scene.calls.length === 0);
|
|
config.init({ sfx: { ...SFX } });
|
|
}
|
|
|
|
// 3) No sound manager (headless) → silent, no throw.
|
|
{
|
|
const scene = makeScene({ sound: false });
|
|
playSfxOn(scene, 'ui_click');
|
|
check('no sound manager → nothing plays', scene.calls.length === 0);
|
|
}
|
|
|
|
// 4) Asset not in the audio cache → silent, no throw (v4 play() would throw).
|
|
{
|
|
const scene = makeScene({ inCache: false });
|
|
playSfxOn(scene, 'ui_hover');
|
|
check('asset missing from cache → nothing plays', scene.calls.length === 0);
|
|
}
|
|
|
|
// 5) Cache object without an audio section → still plays (defensive guard).
|
|
{
|
|
const scene = makeScene({ noAudio: true });
|
|
playSfxOn(scene, 'ui_click');
|
|
check('cache without audio section → plays', scene.calls.length === 1);
|
|
}
|
|
|
|
// 6) data/sfx.json carries the UI ticks + the window whoosh + the close tick, pointing at real files.
|
|
{
|
|
const ok = ['ui_hover', 'ui_click', 'ui_window', 'ui_close'].every((k) => {
|
|
const p = SFX[k];
|
|
return typeof p === 'string' && fs.existsSync(join(__dirname, '..', p));
|
|
});
|
|
check('sfx.json ui_hover/ui_click/ui_window/ui_close point at files that exist', ok);
|
|
}
|
|
|
|
// 7) Extra play options (the mining hum's loop) pass through after the
|
|
// configured volume.
|
|
{
|
|
const scene = makeScene();
|
|
playSfxOn(scene, 'mining_loop', { loop: true });
|
|
check('playSfxOn passes extra options (loop) through with the sfx.volume default',
|
|
scene.calls.length === 1 &&
|
|
scene.calls[0].key === 'sfx_mining_loop' &&
|
|
scene.calls[0].opts.loop === true &&
|
|
scene.calls[0].opts.volume === SFX.volume);
|
|
}
|
|
|
|
// 8) stopSfxOn stops by the sfx_ cache key (v4 has no sound.stop()).
|
|
{
|
|
const scene = makeScene();
|
|
playSfxOn(scene, 'mining_loop', { loop: true });
|
|
stopSfxOn(scene, 'mining_loop');
|
|
check('stopSfxOn stops the sfx_ key', scene.stops.length === 1 && scene.stops[0] === 'sfx_mining_loop');
|
|
|
|
const noMgr = makeScene({ sound: false });
|
|
let threw = false;
|
|
try {
|
|
stopSfxOn(noMgr, 'mining_loop');
|
|
} catch (e) {
|
|
threw = true;
|
|
}
|
|
check('stopSfxOn with no sound manager → no throw', !threw);
|
|
}
|
|
|
|
// 9) sfxPlayingOn mirrors the manager's isPlaying (false when unsure).
|
|
{
|
|
const scene = makeScene();
|
|
check('sfxPlayingOn is false before anything plays', sfxPlayingOn(scene, 'mining_loop') === false);
|
|
playSfxOn(scene, 'mining_loop', { loop: true });
|
|
check('sfxPlayingOn is true while it plays', sfxPlayingOn(scene, 'mining_loop') === true);
|
|
stopSfxOn(scene, 'mining_loop');
|
|
check('sfxPlayingOn is false after stop', sfxPlayingOn(scene, 'mining_loop') === false);
|
|
|
|
const noMgr = makeScene({ sound: false });
|
|
check('sfxPlayingOn with no sound manager → false (no throw)', sfxPlayingOn(noMgr, 'mining_loop') === false);
|
|
}
|
|
|
|
// 10) The mining_loop key points at a real file.
|
|
{
|
|
const p = SFX.mining_loop;
|
|
check('sfx.json mining_loop points at a file that exists',
|
|
typeof p === 'string' && fs.existsSync(join(__dirname, '..', p)));
|
|
}
|
|
|
|
console.log(failures === 0 ? '\nAll SFX voice tests passed ✔' : `\n${failures} test(s) FAILED ✘`);
|
|
process.exit(failures === 0 ? 0 : 1);
|