Complete room UI and protocol integration, move game definitions and resources behind bundle entries, publish authoritative version XML, and document single-game builds. Include all current resource changes and experiment artifacts.
70 lines
4.9 KiB
TypeScript
70 lines
4.9 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {RoomVoiceAdapter,createRoomVoiceSubscriptions} from '../../YouleNexus/assets/framework/adapters/native/room-voice.ts';
|
|
import type {PlatformNativeHost} from '../../YouleNexus/assets/framework/adapters/native/platform-native.ts';
|
|
import type {NativeHandler} from '../../YouleNexus/assets/framework/adapters/native/native-contracts.ts';
|
|
|
|
function setup(host:PlatformNativeHost) {
|
|
const handlers=new Map<string,NativeHandler>();const sent:unknown[]=[];const played:unknown[]=[];
|
|
const adapter=new RoomVoiceAdapter({host,subscribe:(name,handler)=>{handlers.set(name,handler);return()=>{handlers.delete(name);};},
|
|
sendVoice:(url,time)=>sent.push([url,time]),onPlayback:(user,playing)=>played.push([user,playing])});
|
|
return {adapter,handlers,sent,played};
|
|
}
|
|
test('settings recording and history playback retain exact positional arguments',()=>{
|
|
const calls:unknown[]=[];
|
|
const {adapter,handlers,sent}=setup({kind:'settings',settings:{prepareaudio(...args:unknown[]){calls.push(args);},mediaTypeAudio(...args:unknown[]){calls.push(args);}}});
|
|
adapter.startRecording();adapter.stopRecording();
|
|
handlers.get('getaudiourl')!('https://audio/real.aac','3');
|
|
adapter.playVoice('https://audio/real.aac',1,10003);
|
|
assert.deepEqual(calls,[[],['https://audio/real.aac',1,10003]]);
|
|
assert.deepEqual(sent,[['https://audio/real.aac','3']]);
|
|
});
|
|
test('WVJB empty start payload, structured playback and JSON upload are unchanged',()=>{
|
|
const calls:unknown[]=[];
|
|
const {adapter,handlers,sent,played}=setup({kind:'bridge',call:(name,data)=>{calls.push([name,data]);}});
|
|
adapter.startRecording();adapter.stopRecording();
|
|
handlers.get('getaudiourl')!(JSON.stringify({audiourl:'actual.aac',time:2}));
|
|
adapter.playVoice('actual.aac',0,10001);
|
|
handlers.get('gameui_play_voice')!('10001');handlers.get('gameui_stop_voice')!(10001);
|
|
assert.deepEqual(calls,[['prepareaudio',''],['mediaTypeAudio',{audiourl:'actual.aac',type:0,user:10001}]]);
|
|
assert.deepEqual(sent,[['actual.aac',2]]);assert.deepEqual(played,[[10001,true],[10001,false]]);
|
|
});
|
|
test('browser reports unsupported without creating uploads or playback',()=>{
|
|
const {adapter,sent,played}=setup({kind:'browser'});
|
|
assert.throws(()=>adapter.startRecording(),/不支持/);assert.throws(()=>adapter.playVoice('x',0,1),/不支持/);
|
|
assert.deepEqual(sent,[]);assert.deepEqual(played,[]);
|
|
});
|
|
test('cancelled, zero duration and duplicate uploads never send; dispose removes callbacks',()=>{
|
|
const {adapter,handlers,sent}=setup({kind:'bridge',call(){}});
|
|
adapter.startRecording();adapter.cancelRecording();
|
|
assert.throws(()=>adapter.startRecording(),/pending/);
|
|
handlers.get('getaudiourl')!({audiourl:'cancelled',time:3});
|
|
adapter.startRecording();handlers.get('getaudiourl')!({audiourl:'',time:0});
|
|
adapter.startRecording();handlers.get('getaudiourl')!({audiourl:'once',time:1});
|
|
handlers.get('getaudiourl')!({audiourl:'duplicate',time:1});
|
|
assert.deepEqual(sent,[['once',1]]);
|
|
adapter.startRecording();const late=handlers.get('getaudiourl')!;adapter.dispose();late({audiourl:'late',time:3});
|
|
assert.equal(handlers.size,0);assert.equal(sent.length,1);
|
|
});
|
|
test('invalid upload source and missing native method are exposed',()=>{
|
|
const {adapter,handlers}=setup({kind:'bridge',call(){}});
|
|
adapter.startRecording();assert.throws(()=>handlers.get('getaudiourl')!({audiourl:'x'}),/time/);
|
|
assert.throws(()=>setup({kind:'settings',settings:{}}).adapter.startRecording(),/prepareaudio/);
|
|
});
|
|
test('settings subscriptions own globals and restore previous callbacks on dispose',()=>{
|
|
const original=()=>{};const globals:Record<string,unknown>={getaudiourl:original};let count=0;
|
|
const subscribe=createRoomVoiceSubscriptions({kind:'settings',settings:{}},globals);
|
|
const off=subscribe('getaudiourl',()=>{count++;});const callback=globals.getaudiourl as NativeHandler;
|
|
callback('actual',1);off();callback('late',1);
|
|
assert.equal(count,1);assert.equal(globals.getaudiourl,original);
|
|
const offNew=subscribe('gameui_play_voice',()=>{});offNew();assert.equal(Object.prototype.hasOwnProperty.call(globals,'gameui_play_voice'),false);
|
|
});
|
|
test('bridge subscriptions use legacy names, neutralize retained callbacks and preserve newer owners',()=>{
|
|
const handlers=new Map<string,NativeHandler>();let count=0;
|
|
const subscribe=createRoomVoiceSubscriptions({kind:'bridge',call(){}},{},{register:(name,handler)=>{handlers.set(name,handler);}});
|
|
const off=subscribe('gameui_play_voice',()=>{count++;});const retained=handlers.get('gameui_play_voice')!;
|
|
retained(1);off();retained(1);assert.equal(count,1);
|
|
const globals:Record<string,unknown>={};const settingsSubscribe=createRoomVoiceSubscriptions({kind:'settings',settings:{}},globals);
|
|
const release=settingsSubscribe('getaudiourl',()=>{});const newer=()=>{};globals.getaudiourl=newer;release();assert.equal(globals.getaudiourl,newer);
|
|
});
|