import test from 'node:test'; import assert from 'node:assert/strict'; import {DeferredNumericToolsPort} from '../../YouleNexus/assets/framework/presentation/deferred-numeric-tools.ts'; import type {NumericToolsPort} from '../../YouleNexus/assets/framework/platform/game-host-adapter.ts'; function target(events:string[]):NumericToolsPort{return { openNumberInput:request=>{events.push('input:'+request.message);},closeNumberInput:()=>{events.push('closeInput');}, showMultiplier:request=>{events.push('multiplier:'+request.values.join(','));},hideMultiplier:()=>{events.push('hideMultiplier');}, close:()=>{events.push('close');}, };} const input=(message:string)=>({message,onConfirm:()=>{}}); const multiplier=(value:number)=>({values:[value],onConfirm:()=>{}}); test('early attach requests flush after binding in latest per-tool call order',()=>{ const port=new DeferredNumericToolsPort(),events:string[]=[]; port.openNumberInput(input('old'));port.showMultiplier(multiplier(2));port.openNumberInput(input('new')); assert.deepEqual(events,[]);port.bind(target(events)); assert.deepEqual(events,['multiplier:2','input:new']); port.showMultiplier(multiplier(3));assert.equal(events[events.length-1],'multiplier:3'); }); test('close and hide cancel pending requests independently',()=>{ const port=new DeferredNumericToolsPort(),events:string[]=[]; port.openNumberInput(input('cancelled'));port.showMultiplier(multiplier(2));port.closeNumberInput(); port.bind(target(events));assert.deepEqual(events,['multiplier:2']); port.unbind();port.showMultiplier(multiplier(3));port.hideMultiplier();port.bind(target(events)); assert.deepEqual(events,['multiplier:2']); }); test('lease close clears pending callbacks and unbinds the previous room',()=>{ const port=new DeferredNumericToolsPort(),events:string[]=[]; port.openNumberInput(input('old'));port.close();port.bind(target(events));assert.deepEqual(events,[]); port.close();assert.deepEqual(events,['close']);port.openNumberInput(input('new')); assert.deepEqual(events,['close']);port.bind(target(events));assert.deepEqual(events,['close','input:new']); }); test('scene unbind retains pending requests while dropping only the current target',()=>{ const port=new DeferredNumericToolsPort(),events:string[]=[]; port.openNumberInput(input('pending'));port.unbind();port.bind(target(events)); port.unbind();port.showMultiplier(multiplier(4));assert.deepEqual(events,['input:pending']); port.bind(target(events));assert.deepEqual(events,['input:pending','multiplier:4']); }); test('bound dismissals forward directly and flushed requests are consumed exactly once',()=>{ const port=new DeferredNumericToolsPort(),events:string[]=[]; port.openNumberInput(input('once'));port.bind(target(events));port.closeNumberInput();port.hideMultiplier(); port.unbind();port.bind(target(events));assert.deepEqual(events,['input:once','closeInput','hideMultiplier']); }); test('lease close unbinds even when target cleanup throws',()=>{ const port=new DeferredNumericToolsPort(),events:string[]=[]; port.bind({...target(events),close(){throw Error('cleanup failed');}}); assert.throws(()=>port.close(),/cleanup failed/);port.openNumberInput(input('next')); assert.deepEqual(events,[]);port.bind(target(events));assert.deepEqual(events,['input:next']); }); test('pending numeric requests receive cancellation on replacement and lease close',()=>{ const port=new DeferredNumericToolsPort();const cancelled:string[]=[]; port.openNumberInput({message:'first',onConfirm(){},onCancel(){cancelled.push('first');}}); port.openNumberInput({message:'second',onConfirm(){},onCancel(){cancelled.push('second');}}); port.showMultiplier({values:[1],onConfirm(){},onCancel(){cancelled.push('multiplier');throw Error('cancel failure');}}); assert.deepEqual(cancelled,['first']);assert.throws(()=>port.close(),/cancel failure/);assert.deepEqual(cancelled,['first','second','multiplier']); assert.doesNotThrow(()=>port.close()); });