Files

9 lines
4.4 KiB
JavaScript

import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';import crypto from 'node:crypto';import {Music} from './src/music.mjs';import {boot} from './qa/runtime-harness.mjs';import {solve} from './src/upstream/rules.mjs';import {titleLayout,selectionRow} from './src/selection-title.mjs';
const stub=()=>{const calls=[];return {calls,api:{getStorageSync:()=>'',setStorageSync(){},createInnerAudioContext:()=>({stop(){calls.push('stop');},play(){calls.push(this.src);},onError(){}})}};};
test('BGMA stays continuous across level changes and completion redraws',()=>{const s=stub(),m=new Music(s.api);m.scene('play','one');const calls=s.calls.length;for(let i=0;i<20;i++)m.scene('play','one');m.scene('complete','one');m.scene('play','two');assert.equal(s.calls.length,calls);assert.equal(m.audio.src,'music/play-a.wav');assert.equal(m.audio.loop,true);assert.equal(m.audio.volume,.119);m.duck();assert.equal(m.audio.volume,.02975);m.scene('play','three');assert.equal(s.calls.length,calls);m.unduck();assert.equal(m.audio.volume,.119);});
test('subpackage completion never plays a stale scene after hiding, muting, or clearing',()=>{const s=stub();let task;s.api.loadSubpackage=o=>task=o;const m=new Music(s.api,{random:()=>.1});m.scene('play','one');assert.equal(s.calls.length,0);m.suspend();task.success();assert.equal(s.calls.length,0);m.resume();assert.equal(m.audio.src,'music/play-a.wav');m.scene('complete','one');assert.equal(m.playing,'play');m.resume();assert.equal(m.playing,'play');m.scene('select','one');assert.equal(m.audio.src,'music/select.wav');});
test('music load failure is silent and one real resume can retry; muted pending loads stay silent',()=>{const s=stub();let task,calls=0;s.api.loadSubpackage=o=>{task=o;calls++;};const m=new Music(s.api);m.scene('select');task.fail();m.scene('select');assert.equal(calls,1);m.resume();assert.equal(calls,2);m.toggle();task.success();assert.equal(s.calls.length,0);m.toggle();assert.equal(m.audio.src,'music/select.wav');});
test('runtime uses supplied WAV hashes and ships music as a subpackage with a small main bundle',()=>{const meta=JSON.parse(fs.readFileSync(new URL('./.audio-sources/manifest.json',import.meta.url)));for(const [name,item]of Object.entries(meta)){for(const [file,hash]of [[item.output,item.sha256],['.audio-sources/'+name+'.wav',item.sourceSHA256]])assert.equal(crypto.createHash('sha256').update(fs.readFileSync(new URL('./'+file,import.meta.url))).digest('hex'),hash);}const config=JSON.parse(fs.readFileSync(new URL('./game.json',import.meta.url)));assert.ok(config.subpackages.some(p=>p.name==='music'&&p.root==='music/'));assert.ok(fs.existsSync(new URL('./music/game.js',import.meta.url)));let main=0;for(const folder of ['audio','audio-original','fonts','images'])for(const file of fs.readdirSync(new URL('./'+folder,import.meta.url)))main+=fs.statSync(new URL('./'+folder+'/'+file,import.meta.url)).size;main+=fs.statSync(new URL('./game.js',import.meta.url)).size;assert.ok(main<4*1024*1024,main);});
test('title is centered and selection settings share the progress and level-range row',()=>{for(const [w,h]of [[320,568],[375,667],[390,844]]){const ui=boot(new Map(),w,h);ui.select(3);ui.button('选关');const image=ui.images.find(i=>i.source==='images/title-reference.png'),settings=ui.api.buttons().find(b=>b.label==='设置');assert.equal(image.x+image.w/2,w/2);assert.equal(settings.y,selectionRow(w,92).y);assert.ok(settings.y>image.y+image.h);const cards=ui.api.buttons().filter(b=>/^\d+( ✓)?$/.test(b.label));assert.equal(cards.length,15);assert.ok(cards.every(b=>b.h>=44));}});
test('completion ducks BGMA until jingle ends and next level never restarts it',()=>{const pack=JSON.parse(fs.readFileSync(new URL('./src/upstream/levels.json',import.meta.url))),ui=boot();ui.select(3);const level=pack.levels.find(l=>l.id===pack.playOrder[3]);const plays=()=>ui.played.filter(f=>f==='music/play-a.wav').length;const before=plays();solve(level)[0].forEach(id=>{ui.advance(80);ui.cell(id);});assert.equal(ui.api.snapshot().music.playing,'play');assert.equal(ui.api.snapshot().music.ducked,true);assert.equal(ui.api.snapshot().music.volume,.02975);assert.equal(ui.played.filter(f=>f==='audio/complete.wav').length,1);ui.audioContexts.find(a=>a.src==='audio/complete.wav').ended();assert.equal(ui.api.snapshot().music.ducked,false);assert.equal(ui.api.snapshot().music.volume,.119);ui.button('下一关 →');assert.equal(ui.api.snapshot().music.track,'music/play-a.wav');assert.equal(plays(),before);});