Files
NumberSwipe_New/wechat-campaign-200-r2/audio-copy.test.mjs
T

20 lines
4.3 KiB
JavaScript

import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';import path from 'node:path';import {fileURLToPath} from 'node:url';import {CasualAudio,stopAudio,audioDiagnostics} from './src/audio.mjs';import {tutorials,rulesFor,playerText} from './src/copy.mjs';
const root=path.dirname(fileURLToPath(import.meta.url));
function setup(){const played=[],contexts=[],storage=new Map(),options=[];globalThis.wx={setInnerAudioOption:o=>options.push(o),showToast:()=>{},createInnerAudioContext:()=>{const a={stops:0,stop(){this.stops++;},play(){assert.ok(fs.existsSync(path.join(root,this.src)));played.push(this.src);},onError(fn){this.error=fn;},onPlay(fn){this.playing=fn;}};contexts.push(a);return a;}};const sound=new CasualAudio({}, {getItem:k=>storage.get(k),setItem:(k,v)=>storage.set(k,v)},'sound');return {sound,played,contexts,storage,options};}
test('手机没有浏览器音频接口时,原生播放器仍能播放全部 36 步和六类反馈音',()=>{const h=setup();for(let step=1;step<=36;step++)h.sound.play('move',0,step);for(const kind of ['correct','error','undo','restart','complete','checkpoint'])h.sound.play(kind);assert.equal(h.played.length,42);assert.equal(h.contexts.length,4);assert.ok(h.options.every(o=>o.obeyMuteSwitch===false));assert.ok(h.contexts.every(a=>a.obeyMuteSwitch===false));});
test('关闭音效停止播放;再次开启会试听并保存偏好;后台中断停止所有播放器',()=>{const h=setup();h.sound.play('move');h.sound.toggle();const count=h.played.length;h.sound.play('move');assert.equal(h.played.length,count);assert.equal(h.storage.get('sound'),'off');h.sound.toggle();assert.equal(h.played.at(-1),'audio/correct.wav');assert.equal(h.storage.get('sound'),'on');const stops=h.contexts.map(a=>a.stops);stopAudio();assert.ok(h.contexts.every((a,i)=>a.stops>stops[i]));});
test('延迟通关音在静音或进入后台时取消,不在返回后突然播放',async()=>{const h=setup();h.sound.play('complete',.02);h.sound.toggle();await new Promise(resolve=>setTimeout(resolve,40));assert.equal(h.played.length,0);});
test('音频错误可诊断,不再静默吞掉',()=>{const h=setup();h.sound.unlock();h.contexts[0].error({errCode:10003,errMsg:'test missing audio'});assert.equal(audioDiagnostics.lastError.errCode,10003);});
test('所有音频均为非静音 PCM;原版第13步比第1步高一个八度',()=>{
const crossings=[];for(const file of fs.readdirSync(path.join(root,'audio'))){const b=fs.readFileSync(path.join(root,'audio',file));assert.equal(b.toString('ascii',0,4),'RIFF');assert.equal(b.readUInt32LE(24),22050);assert.equal(b.readUInt16LE(22),1);let energy=0,z=0;for(let i=44;i<b.length;i+=2){const x=b.readInt16LE(i);energy+=x*x;if(i>44&&x>=0&&b.readInt16LE(i-2)<0)z++;}assert.ok(energy>100000,file+' must not be silent');if(file==='move-1.wav')crossings[0]=z;if(file==='move-13.wav')crossings[1]=z;}assert.ok(Math.abs(crossings[1]/crossings[0]-2)<.1);
});
test('教学和组合规则用具体动作及奇偶例子解释,没有未解释的术语',()=>{const levels=JSON.parse(fs.readFileSync(path.join(root,'src/upstream/levels.json'))).levels;for(const l of levels){const text=rulesFor(l);assert.ok(!/共边|落格|访问步号/.test(text),l.id);assert.ok(text.includes('每个格子只能经过一次'));}assert.ok(tutorials.basic.includes('不能斜着走'));assert.ok(tutorials.parity.includes('1、3、5'));assert.ok(tutorials['rhythm-door'].includes('起点也算一步'));assert.ok(!playerText('认清异形共边').includes('共边'));});
test('过关提示同步使用实际播放起点,非请求时间',()=>{const h=setup();h.sound.play('complete');assert.equal(audioDiagnostics.completeStartedAt,null);const requested=audioDiagnostics.completeRequestedAt;h.contexts.find(c=>c.src==='audio/complete.wav').playing();assert.ok(audioDiagnostics.completeStartedAt>=requested);});
test('过关和弦收尾持续衰减,不在后半段重新起音',()=>{
const b=fs.readFileSync(path.join(root,'audio/complete.wav')),rate=b.readUInt32LE(24),rms=[];
for(let t=.4;t<.8;t+=.1){let energy=0,n=0;for(let i=Math.floor(t*rate);i<Math.min(Math.floor((t+.1)*rate),(b.length-44)/2);i++){const v=b.readInt16LE(44+i*2);energy+=v*v;n++;}rms.push(Math.sqrt(energy/n));}
for(let i=1;i<rms.length;i++)assert.ok(rms[i]<rms[i-1],rms.join(','));
});