Files
joywayer 35216f75e7 feat: integrate room framework and isolated subgame bundles
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.
2026-09-09 03:13:18 +08:00

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);
});