'use strict'; const {assert}=require('./helpers/app-source'); const {createFrameScheduler}=require('../client/input/frame-scheduler'); const {createDragScheduler,STATES}=require('../client/input/drag'); function fakeClock(){ let now=0,nextId=1; const frames=new Map(),timers=new Map(); return{ now:()=>now, requestFrame:callback=>{const id=nextId++;frames.set(id,callback);return id}, cancelFrame:id=>frames.delete(id), setDelay:(callback,delay)=>{const id=nextId++;timers.set(id,{callback,at:now+delay});return id}, clearDelay:id=>timers.delete(id), advance(milliseconds){ now+=milliseconds; for(const[id,entry]of[...timers])if(entry.at<=now){timers.delete(id);entry.callback()} const pending=[...frames];frames.clear();for(const[,callback]of pending)callback(now); } }; } const clock=fakeClock(),commits=[]; const scheduler=createFrameScheduler({ requestFrame:clock.requestFrame,cancelFrame:clock.cancelFrame,setDelay:clock.setDelay,clearDelay:clock.clearDelay, now:clock.now,interval:1000/60,tolerance:1.25,watchdogDelay:18, commit:(value,timestamp)=>commits.push({value,timestamp}) }); for(let index=0;index<40;index++){scheduler.push(index);clock.advance(2)} clock.advance(20); assert(commits.length<=6,'High-rate input produced more than one presentation commit per nominal 60 Hz interval'); assert(commits.at(-1).value===39,'Latest-value frame scheduling lost the newest input'); for(let index=1;index=15,'Presentation commits exceeded the 60 Hz tolerance'); const dragClock=fakeClock(),states=[]; const drag=createDragScheduler({ requestFrame:dragClock.requestFrame,cancelFrame:dragClock.cancelFrame,setDelay:dragClock.setDelay,clearDelay:dragClock.clearDelay, now:dragClock.now,interval:1000/60,tolerance:1.25,watchdogDelay:18,maxSamples:4, trim:samples=>{while(samples.length>4)samples.splice(1,1)}, onFrame:session=>states.push(session.inspect().state) }); drag.arm(7); assert(drag.inspect().state==='armed','Drag did not enter armed state'); for(let index=0;index<8;index++)drag.push({pointerId:7,clientX:index,clientY:index}); assert(drag.inspect().state==='running'&&drag.inspect().logicalCount===4&&drag.latest().clientX===7,'Drag scheduler did not separate bounded logical samples from latest visual input'); dragClock.advance(17); assert(states[0]==='running','Drag scheduler did not commit through its running frame lane'); assert(drag.beginDrain({pointerId:7,clientX:8,clientY:8})&&drag.inspect().state==='draining','Drag did not enter draining state'); assert(drag.drainLogical().at(-1).clientX===8,'Release drain lost final input intent'); drag.beginSettling();assert(drag.inspect().state==='settling','Drag did not enter settling state'); drag.settle();assert(drag.inspect().state==='idle','Settled drag did not return to idle'); drag.arm(9);drag.cancel();assert(drag.inspect().state==='cancelled','Cancelled drag did not expose its terminal state'); assert(STATES.join(',')==='idle,armed,running,draining,settling,cancelled','Drag lifecycle states changed unexpectedly'); console.log('Shared 60 Hz frame scheduler and explicit drag lifecycle behavior passed');