2026-09-05 09:41:44 +00:00
import { test } from 'node:test' ;
import assert from 'node:assert/strict' ;
import fs from 'node:fs' ;
import vm from 'node:vm' ;
import ts from '../apps/web/node_modules/typescript/lib/typescript.js' ;
2026-09-06 05:39:06 +00:00
function loadCatalog ( file ) {
const code = ts . transpileModule ( fs . readFileSync ( file , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS } } ) . outputText ;
const sandbox = { exports : { } , require : ( ) => ( { } ) } ;
vm . runInNewContext ( code , sandbox ) ;
return sandbox . exports . zhTW || sandbox . exports . en ;
}
const zhTW = loadCatalog ( 'apps/web/src/locales/zh-TW.ts' ) ;
const en = loadCatalog ( 'apps/web/src/locales/en.ts' ) ;
const catalogs = { 'zh-TW' : zhTW , en } ;
const i18n = { locale : 'en' , t : ( key , params ) => { const message = catalogs [ i18n . locale ] ? . [ key ] ? ? key ; return params ? message . replace ( /\{(\w+)\}/g , ( m , name ) => Object . hasOwn ( params , name ) ? String ( params [ name ] ) : m ) : message } , getLocale : ( ) => i18n . locale } ;
2026-09-05 09:41:44 +00:00
const raw = fs . readFileSync ( 'apps/web/src/schedule.tsx' , 'utf8' ) ;
const js = ts . transpileModule ( raw , { compilerOptions : { module : ts . ModuleKind . CommonJS , jsx : ts . JsxEmit . React } } ) . outputText ;
2026-09-06 05:39:06 +00:00
const box = { exports : { } , require : ( ) => i18n } ; vm . runInNewContext ( js , box ) ; const { cronFromPreset , presetFromCron , defaultCronPreset , describeCron , describeCronHuman } = box . exports ;
2026-09-05 09:41:44 +00:00
test ( 'fixed intervals retain exact elapsed units' , ( ) => {
for ( const unit of [ 'minutes' , 'hours' , 'days' ] ) { const p = { ... defaultCronPreset ( ) , freq : 'Interval' , n : 45 , unit } ; const cron = cronFromPreset ( p ) ; const restored = presetFromCron ( cron ) ; assert . equal ( restored . n , 45 ) ; assert . equal ( restored . unit , unit ) ; }
} ) ;
test ( 'unknown cron is preserved verbatim instead of silently rewritten' , ( ) => {
for ( const cron of [ '0 0 9 * * 1' , '0 25 * * *' , '99 9 * * *' , '0 0 */3 * *' , '*/45 * * * *' ] ) { const p = presetFromCron ( cron ) ; assert . equal ( p . freq , 'Advanced' ) ; assert . equal ( cronFromPreset ( p ) , cron ) ; }
} ) ;
test ( 'blank advanced expressions are never converted to a scheduled job' , ( ) => assert . equal ( cronFromPreset ( { ... defaultCronPreset ( ) , freq : 'Advanced' , cron : '' } ) , '' ) ) ;
test ( 'VNC paste delegates once to confirmed backend and rejects another source' , async ( ) => {
const source = fs . readFileSync ( 'apps/web/vnc.html' , 'utf8' ) . match ( /<script type="module">([\s\S]*?)<\/script>/ ) [ 1 ] . replace ( /import RFB[^;]+;/ , '' ) ;
const handlers = { } , sent = [ ] ; class RFB { addEventListener ( ) { } focus ( ) { } sendKey ( ) { } }
const window = { location : { pathname : '/vnc.html' , protocol : 'http:' , host : 'localhost' , origin : 'http://localhost' , hash : '' } , parent : { postMessage : x => sent . push ( x ) } , addEventListener : ( n , f ) => handlers [ n ] = f } ;
vm . runInNewContext ( source , { window , document : { location : { href : 'http://localhost/vnc.html?view_only=false' } , getElementById : ( ) => ( { } ) , querySelector : ( ) => null } , navigator : { clipboard : { readText : async ( ) => '中文\nhello' } } , RFB , setTimeout ( ) { } , clearTimeout ( ) { } } ) ;
await handlers . keydown ( { ctrlKey : true , code : 'KeyV' , preventDefault ( ) { } , stopImmediatePropagation ( ) { } } ) ;
assert . equal ( sent . filter ( x => x . type === 'lazyboy-paste-text' ) . length , 1 ) ;
assert . equal ( sent . at ( - 1 ) . text , '中文\nhello' ) ;
handlers . message ( { origin : 'http://localhost' , source : { } , data : { type : 'lazyboy-host-clipboard' , text : 'bad' } } ) ; assert . equal ( sent . at ( - 1 ) . text , '中文\nhello' ) ;
} ) ;
test ( 'saved login rejects HTTP, lookalike hosts, and missing host before touching fields' , ( ) => {
const py = fs . readFileSync ( 'crates/control/src/cdp.py' , 'utf8' ) ; const expression = py . match ( /FILL_LOGIN_JS = r"""([\s\S]*?)"""/ ) [ 1 ] ;
for ( const [ protocol , hostname , expectedHost ] of [ [ 'https:' , 'evil.example' , 'bank.example' ] , [ 'http:' , 'bank.example' , 'bank.example' ] , [ 'https:' , 'bank.example.evil' , 'bank.example' ] , [ 'https:' , 'bank.example' , '' ] ] ) {
const evaluate = vm . runInNewContext ( ` ( ${ expression } ) ` , { location : { protocol , hostname } , document : { querySelectorAll ( ) { throw Error ( 'must not touch fields' ) } } } ) ;
assert . equal ( evaluate ( { username : 'u' , password : 'secret' , expectedHost } ) . ok , false ) ;
}
} ) ;
2026-09-05 14:59:07 +00:00
const mdJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/markdown.tsx' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS , jsx : ts . JsxEmit . ReactJSX } } ) . outputText ;
const mdBox = { exports : { } , require : ( name ) => {
if ( name === 'react' ) return { useCallback : fn => fn , useRef : ( ) => ( { current : null } ) , useState : ( ) => [ false , ( ) => { } ] , memo : fn => fn } ;
if ( name === 'react/jsx-runtime' ) return { jsx : ( ) => null , jsxs : ( ) => null , Fragment : 'Fragment' } ;
if ( name === 'react-markdown' || name === 'remark-gfm' || name === 'remark-breaks' ) return { default : ( ) => null } ;
if ( name === './i18n' ) return { t : key => key } ;
if ( name . endsWith ( '.css' ) ) return { } ;
throw new Error ( 'unexpected import ' + name ) ;
} } ;
vm . runInNewContext ( mdJs , mdBox ) ;
const { sanitizeMarkdownUrl } = mdBox . exports ;
2026-09-06 03:43:38 +00:00
const audioJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/call-audio.ts' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS } } ) . outputText ;
const audioBox = { exports : { } , require : ( ) => { throw new Error ( 'unexpected import' ) } } ;
vm . runInNewContext ( audioJs , audioBox ) ;
2026-09-06 05:39:06 +00:00
const { floatToPcm16 , pcm16ToFloat , resample , VOICE _SAMPLE _RATE , CAPTURE _CHUNK _SAMPLES } = audioBox . exports ;
function fakePlaybackContext ( rendered ) {
return {
sampleRate : VOICE _SAMPLE _RATE , currentTime : 0 ,
createBuffer : ( _channels , length , rate ) => { const data = new Float32Array ( length ) ; rendered . push ( data ) ; return { length , duration : length / rate , getChannelData : ( ) => data } } ,
createBufferSource : ( ) => ( { buffer : null , connect ( ) { } , start ( ) { } , stop ( ) { } } ) ,
} ;
}
2026-09-06 03:43:38 +00:00
test ( 'pcm16 roundtrip keeps amplitude sign and resample identity at 24 kHz' , ( ) => {
const source = new Float32Array ( [ 0 , 0.5 , - 0.5 , 1 , - 1 ] ) ;
const bytes = floatToPcm16 ( source ) ;
const back = pcm16ToFloat ( bytes . buffer ) ;
assert . equal ( back . length , source . length ) ;
assert . ok ( back [ 1 ] > 0.49 && back [ 1 ] < 0.51 ) ;
assert . ok ( back [ 2 ] < - 0.49 && back [ 2 ] > - 0.51 ) ;
const same = resample ( source , VOICE _SAMPLE _RATE , VOICE _SAMPLE _RATE ) ;
assert . equal ( same , source ) ;
const down = resample ( new Float32Array ( [ 0 , 1 , 0 , 1 ] ) , 48000 , 24000 ) ;
assert . equal ( down . length , 2 ) ;
} ) ;
2026-09-05 14:59:07 +00:00
test ( 'markdown links only keep http(s), mailto, tel, and in-page hashes' , ( ) => {
assert . equal ( sanitizeMarkdownUrl ( 'https://example.com/docs' ) , 'https://example.com/docs' ) ;
assert . equal ( sanitizeMarkdownUrl ( 'mailto:hi@example.com' ) , 'mailto:hi@example.com' ) ;
assert . equal ( sanitizeMarkdownUrl ( '#section' ) , '#section' ) ;
assert . equal ( sanitizeMarkdownUrl ( 'javascript:alert(1)' ) , undefined ) ;
assert . equal ( sanitizeMarkdownUrl ( 'data:text/html,<script>alert(1)</script>' ) , undefined ) ;
assert . equal ( sanitizeMarkdownUrl ( '/relative' ) , undefined ) ;
} ) ;
2026-09-06 03:43:38 +00:00
2026-09-06 05:39:06 +00:00
test ( 'zh-TW and en catalogs share keys and placeholders' , ( ) => {
const zhKeys = Object . keys ( zhTW ) . sort ( ) ;
const enKeys = Object . keys ( en ) . sort ( ) ;
assert . deepEqual ( enKeys , zhKeys ) ;
for ( const key of zhKeys ) {
const slots = s => [ ... String ( s ) . matchAll ( /\{(\w+)\}/g ) ] . map ( m => m [ 1 ] ) . sort ( ) ;
assert . deepEqual ( slots ( en [ key ] ) , slots ( zhTW [ key ] ) , key ) ;
assert . notEqual ( String ( en [ key ] ) . trim ( ) , '' ) ;
assert . notEqual ( String ( zhTW [ key ] ) . trim ( ) , '' ) ;
}
} ) ;
test ( 'describeCron and stored Chinese human strings follow the UI locale' , ( ) => {
i18n . locale = 'en' ;
assert . equal ( describeCron ( '0 9 * * *' ) , 'Daily at 9:00 AM' ) ;
assert . equal ( describeCron ( '0 9 * * 1-5' ) , 'Weekdays at 9:00 AM' ) ;
assert . equal ( describeCronHuman ( '每天 09:00' ) , 'Daily at 9:00 AM' ) ;
assert . equal ( describeCronHuman ( '每隔 45 分鐘(固定間隔)' ) , 'Every 45 minutes (elapsed)' ) ;
i18n . locale = 'zh-TW' ;
assert . equal ( describeCron ( '0 9 * * *' ) , '每天 09:00' ) ;
assert . equal ( describeCronHuman ( 'Daily at 9:00 AM' ) , '每天 09:00' ) ;
} ) ;
2026-09-06 03:43:38 +00:00
test ( 'hanging up before microphone permission resolves releases the late stream' , async ( ) => {
let grant ; let stopped = 0 ;
const context = { exports : { } , navigator : { mediaDevices : { getUserMedia : ( ) => new Promise ( resolve => grant = resolve ) } } } ;
vm . runInNewContext ( audioJs , context ) ;
const audio = new context . exports . CallAudio ( { onCapture ( ) { } , onError ( ) { } } ) ;
const starting = audio . start ( ) ;
audio . stop ( ) ;
grant ( { getTracks : ( ) => [ { stop ( ) { stopped ++ ; } } ] } ) ;
await starting ;
assert . equal ( stopped , 1 ) ;
} ) ;
2026-09-06 05:39:06 +00:00
test ( 'odd-sized audio frames stay sample-aligned instead of turning into noise' , ( ) => {
const rendered = [ ] ;
const audio = new audioBox . exports . CallAudio ( { onCapture ( ) { } , onError ( ) { } } ) ;
audio . context = fakePlaybackContext ( rendered ) ;
const source = new Int16Array ( [ 1000 , - 1000 , 2000 , - 2000 , 3000 , - 3000 , 4000 , - 4000 ] ) ;
const bytes = new Uint8Array ( source . buffer ) ;
let offset = 0 ;
for ( const size of [ 3 , 5 , 7 , 1 ] ) { audio . play ( bytes . slice ( offset , offset + size ) . buffer ) ; offset += size }
const played = rendered . flatMap ( chunk => [ ... chunk ] ) ;
const expected = [ ... pcm16ToFloat ( bytes . slice ( ) . buffer ) ] ;
assert . equal ( played . length , expected . length ) ;
expected . forEach ( ( value , index ) => assert . ok ( Math . abs ( played [ index ] - value ) < 1e-6 , ` sample ${ index } ` ) ) ;
} ) ;
test ( 'microphone frames are batched instead of sent one websocket message per block' , ( ) => {
const sent = [ ] ;
const audio = new audioBox . exports . CallAudio ( { onCapture : pcm => sent . push ( pcm . length ) , onError ( ) { } } ) ;
const block = new Float32Array ( 64 ) ;
const blocksPerChunk = CAPTURE _CHUNK _SAMPLES / block . length ;
for ( let i = 0 ; i < blocksPerChunk - 1 ; i ++ ) audio . queueCapture ( block ) ;
assert . deepEqual ( sent , [ ] ) ;
audio . queueCapture ( block ) ;
assert . deepEqual ( sent , [ CAPTURE _CHUNK _SAMPLES * 2 ] ) ;
} ) ;
2026-09-06 14:09:17 +00:00
function mobileViewer ( ) {
const source = fs . readFileSync ( 'apps/web/vnc.html' , 'utf8' ) . match ( /<script type="module">([\s\S]*?)<\/script>/ ) [ 1 ] . replace ( /import RFB[^;]+;/ , '' ) ;
const handlers = { } , options = { } , sent = [ ] ;
const keyboard = { value : '' , focused : false , focus ( ) { this . focused = true } , blur ( ) { this . focused = false } , setSelectionRange ( ) { } } ;
const pointerEvents = [ ] ;
const canvas = { style : { } , closest : selector => selector === '#screen' ? { } : null , getBoundingClientRect : ( ) => ( { left : 0 , top : 0 , width : 300 , height : 200 } ) } ;
const elements = { } ;
const element = id => elements [ id ] || ( elements [ id ] = { style : { } , attributes : { } , setAttribute ( k , v ) { this . attributes [ k ] = v } , querySelectorAll ( ) { return [ ] } } ) ;
let rfb ;
class RFB { constructor ( ) { rfb = this } addEventListener ( ) { } focus ( ) { keyboard . focused = false }
_handleMouseMove ( x , y ) { pointerEvents . push ( { type : 'move' , x , y } ) }
_handleMouseButton ( x , y , mask ) { pointerEvents . push ( { type : 'button' , x , y , mask } ) }
_handleWheel ( event ) { pointerEvents . push ( { type : 'wheel' , dx : event . deltaX , dy : event . deltaY } ) }
}
2026-09-07 09:05:14 +00:00
const body = { className : '' , classList : { tokens : new Set ( ) , toggle ( name , force ) { if ( force === undefined ) force = ! this . tokens . has ( name ) ; if ( force ) this . tokens . add ( name ) ; else this . tokens . delete ( name ) ; body . className = [ ... this . tokens ] . join ( ' ' ) } } } ;
const window = { location : { pathname : '/vnc.html' , protocol : 'http:' , host : 'localhost' , origin : 'http://localhost' , hash : '' } , parent : { postMessage : x => sent . push ( x ) } , addEventListener : ( n , f , o ) => { handlers [ n ] = f ; options [ n ] = o } , matchMedia : ( ) => ( { matches : false , addEventListener ( ) { } } ) } ;
vm . runInNewContext ( source , { window , document : { body , location : { href : 'http://localhost/vnc.html?view_only=false' } , getElementById : id => id === 'mobile-keyboard' ? keyboard : element ( id ) , querySelector : selector => selector === '#screen canvas' ? canvas : null } , navigator : { } , RFB , setTimeout ( ) { } , clearTimeout ( ) { } } ) ;
return { handlers , options , sent , keyboard , canvas , rfb , window , pointerEvents , elements , body } ;
2026-09-06 14:09:17 +00:00
return { handlers , options , sent , keyboard , canvas , rfb , window , pointerEvents , elements } ;
}
2026-09-07 09:05:14 +00:00
test ( 'direct mobile taps position without unexpectedly opening the keyboard' , ( ) => {
2026-09-06 14:09:17 +00:00
const { handlers : h , options , keyboard , canvas , rfb } = mobileViewer ( ) ;
for ( const name of [ 'touchstart' , 'touchmove' , 'touchend' ] ) assert . equal ( options [ name ] . capture , true ) ;
const point = { clientX : 20 , clientY : 30 } ;
h . touchstart ( { target : canvas , touches : [ point ] } ) ; h . touchend ( { touches : [ ] } ) ;
2026-09-07 09:05:14 +00:00
assert . equal ( keyboard . focused , false ) ; assert . equal ( rfb . focusOnClick , false ) ;
2026-09-06 14:09:17 +00:00
keyboard . blur ( ) ; h . touchstart ( { target : canvas , touches : [ point ] } ) ; h . touchmove ( { touches : [ { clientX : 50 , clientY : 30 } ] } ) ; h . touchend ( { touches : [ ] } ) ; assert . equal ( keyboard . focused , false ) ;
h . touchstart ( { target : canvas , touches : [ point , point ] } ) ; h . touchend ( { touches : [ ] } ) ; assert . equal ( keyboard . focused , false ) ;
rfb . viewOnly = true ; h . touchstart ( { target : canvas , touches : [ point ] } ) ; h . touchend ( { touches : [ ] } ) ; assert . equal ( keyboard . focused , false ) ;
} ) ;
test ( 'mobile IME sends committed Chinese once and supports input-only backspace and Enter' , ( ) => {
const { handlers : h , keyboard , sent } = mobileViewer ( ) ;
sent . length = 0 ;
h . compositionstart ( { target : keyboard } ) ; keyboard . value += '中文' ;
h . input ( { target : keyboard , isComposing : true } ) ; assert . equal ( sent . length , 0 ) ;
h . compositionend ( { target : keyboard } ) ; h . input ( { target : keyboard , isComposing : false } ) ;
assert . equal ( sent . length , 1 ) ; assert . equal ( sent [ 0 ] . text , '中文' ) ;
keyboard . value = '' ; h . input ( { target : keyboard , inputType : 'deleteContentBackward' } ) ;
let prevented = false ; h . beforeinput ( { target : keyboard , inputType : 'insertLineBreak' , preventDefault ( ) { prevented = true } } ) ;
assert . equal ( prevented , true ) ;
assert . deepEqual ( sent . map ( x => x . text || x . key ) , [ '中文' , 'BackSpace' , 'Return' ] ) ;
} ) ;
test ( 'mobile keyboard is cleared and blurred when control is released; foreign messages cannot change control' , ( ) => {
const { handlers : h , keyboard , sent , window } = mobileViewer ( ) ;
keyboard . focus ( ) ; keyboard . value += 'secret' ;
h . message ( { origin : window . location . origin , source : { } , data : { type : 'lazyboy-view-only' , viewOnly : true } } ) ;
assert . equal ( keyboard . focused , true ) ;
h . message ( { origin : window . location . origin , source : window . parent , data : { type : 'lazyboy-view-only' , viewOnly : true } } ) ;
assert . equal ( keyboard . focused , false ) ; assert . equal ( keyboard . value , '\u200b' ) ;
sent . length = 0 ; keyboard . value += 'blocked' ; h . input ( { target : keyboard } ) ; assert . equal ( sent . length , 0 ) ;
} ) ;
function pointerButton ( viewer , action ) {
viewer . handlers . click ( { target : { closest : ( ) => ( { dataset : { action } } ) } } ) ;
}
function pointerTouch ( viewer , name , points ) {
let prevented = false , stopped = false ;
2026-09-07 09:05:14 +00:00
viewer . handlers [ name ] ( { target : { closest : selector => selector === '#trackpad' ? { } : null } , touches : points . map ( ( [ clientX , clientY ] ) => ( { clientX , clientY } ) ) , preventDefault ( ) { prevented = true } , stopImmediatePropagation ( ) { stopped = true } } ) ;
2026-09-06 14:09:17 +00:00
return prevented && stopped ;
}
test ( 'trackpad moves relative to cursor, clamps edges and taps without opening keyboard' , ( ) => {
const v = mobileViewer ( ) ; pointerButton ( v , 'mode' ) ;
assert . equal ( pointerTouch ( v , 'touchstart' , [ [ 30 , 30 ] ] ) , true ) ;
pointerTouch ( v , 'touchmove' , [ [ 60 , 40 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . equal ( v . pointerEvents . length , 1 ) ; assert . equal ( v . pointerEvents [ 0 ] . x , 180 ) ; assert . ok ( Math . abs ( v . pointerEvents [ 0 ] . y - 110 ) < . 001 ) ;
assert . equal ( v . keyboard . focused , false ) ;
pointerTouch ( v , 'touchstart' , [ [ 10 , 10 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . deepEqual ( v . pointerEvents . slice ( - 2 ) . map ( e => e . mask ) , [ 1 , 0 ] ) ;
pointerTouch ( v , 'touchstart' , [ [ 10 , 10 ] ] ) ; pointerTouch ( v , 'touchmove' , [ [ 1000 , - 1000 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . equal ( v . pointerEvents . at ( - 1 ) . x , 299 ) ; assert . equal ( v . pointerEvents . at ( - 1 ) . y , 0 ) ;
pointerButton ( v , 'keyboard' ) ; assert . equal ( v . keyboard . focused , true ) ;
} ) ;
test ( 'two-finger trackpad scroll and right tap do not also left click' , ( ) => {
const v = mobileViewer ( ) ; pointerButton ( v , 'mode' ) ;
pointerTouch ( v , 'touchstart' , [ [ 20 , 20 ] , [ 50 , 20 ] ] ) ; pointerTouch ( v , 'touchmove' , [ [ 20 , 80 ] , [ 50 , 80 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . deepEqual ( v . pointerEvents , [ { type : 'wheel' , dx : - 0 , dy : - 60 } ] ) ;
pointerTouch ( v , 'touchstart' , [ [ 20 , 20 ] , [ 50 , 20 ] ] ) ; pointerTouch ( v , 'touchend' , [ [ 20 , 20 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . deepEqual ( v . pointerEvents . slice ( - 2 ) . map ( e => e . mask ) , [ 4 , 0 ] ) ;
} ) ;
test ( 'drag is released on cancellation, mode switch, blur and control handoff' , ( ) => {
for ( const release of [ 'cancel' , 'mode' , 'blur' , 'handoff' ] ) {
const v = mobileViewer ( ) ; pointerButton ( v , 'drag' ) ;
assert . equal ( v . pointerEvents . at ( - 1 ) . mask , 1 ) ;
if ( release === 'cancel' ) v . handlers . touchcancel ( ) ;
if ( release === 'mode' ) pointerButton ( v , 'mode' ) ;
if ( release === 'blur' ) v . handlers . blur ( ) ;
if ( release === 'handoff' ) v . handlers . message ( { origin : v . window . location . origin , source : v . window . parent , data : { type : 'lazyboy-view-only' , viewOnly : true } } ) ;
assert . equal ( v . pointerEvents . at ( - 1 ) . mask , 0 , release ) ;
assert . equal ( v . elements [ 'pointer-drag' ] . attributes [ 'aria-pressed' ] , 'false' ) ;
}
} ) ;
test ( 'view-only pointer controls never send mouse input and trackpad asks for control' , ( ) => {
const v = mobileViewer ( ) ; pointerButton ( v , 'mode' ) ; v . rfb . viewOnly = true ;
for ( const action of [ 'left' , 'right' , 'drag' , 'up' , 'down' ] ) pointerButton ( v , action ) ;
pointerTouch ( v , 'touchstart' , [ [ 20 , 20 ] ] ) ; pointerTouch ( v , 'touchmove' , [ [ 50 , 50 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . equal ( v . pointerEvents . length , 0 ) ;
assert . equal ( v . sent . at ( - 1 ) . type , 'lazyboy-request-control' ) ;
} ) ;
2026-09-07 09:05:14 +00:00
test ( 'trackpad mode swallows screen taps so fingers on the picture do not click' , ( ) => {
const v = mobileViewer ( ) ; pointerButton ( v , 'mode' ) ;
let prevented = false , stopped = false ;
v . handlers . touchstart ( { target : v . canvas , touches : [ { clientX : 20 , clientY : 30 } ] , preventDefault ( ) { prevented = true } , stopImmediatePropagation ( ) { stopped = true } } ) ;
assert . equal ( prevented && stopped , true ) ;
assert . equal ( v . pointerEvents . length , 0 ) ;
assert . equal ( v . keyboard . focused , false ) ;
} ) ;
test ( 'coarse pointers start in trackpad mode with a body class for independent controls' , ( ) => {
const source = fs . readFileSync ( 'apps/web/vnc.html' , 'utf8' ) . match ( /<script type="module">([\s\S]*?)<\/script>/ ) [ 1 ] . replace ( /import RFB[^;]+;/ , '' ) ;
const body = { className : '' , classList : { tokens : new Set ( ) , toggle ( name , force ) { if ( force ) this . tokens . add ( name ) ; else this . tokens . delete ( name ) ; body . className = [ ... this . tokens ] . join ( ' ' ) } } } ;
const window = { location : { pathname : '/vnc.html' , protocol : 'http:' , host : 'localhost' , origin : 'http://localhost' , hash : '' } , parent : { postMessage ( ) { } } , addEventListener ( ) { } , matchMedia : ( ) => ( { matches : true , addEventListener ( ) { } } ) } ;
const elements = { } ; const element = id => elements [ id ] || ( elements [ id ] = { style : { } , attributes : { } , hidden : true , setAttribute ( k , v ) { this . attributes [ k ] = v } , querySelectorAll ( ) { return [ ] } } ) ;
class RFB { constructor ( ) { } addEventListener ( ) { } focus ( ) { } }
vm . runInNewContext ( source , { window , document : { body , location : { href : 'http://localhost/vnc.html?view_only=false' } , getElementById : id => element ( id ) , querySelector : ( ) => null } , navigator : { } , RFB , setTimeout ( ) { } , clearTimeout ( ) { } } ) ;
assert . equal ( body . className , 'touch-ui' ) ;
assert . equal ( elements . trackpad . hidden , false ) ;
} ) ;
test ( 'mobile shortcut row sends modifiers and hides with the keyboard' , ( ) => {
const v = mobileViewer ( ) ;
pointerButton ( v , 'keyboard' ) ;
assert . equal ( v . keyboard . focused , true ) ;
assert . equal ( v . elements [ 'keyboard-shortcuts' ] . hidden , false ) ;
v . handlers . click ( { target : { closest : ( ) => ( { dataset : { action : 'modifier' , key : 'ctrl' } } ) } } ) ;
v . handlers . click ( { target : { closest : ( ) => ( { dataset : { action : 'key' , key : 'c' } } ) } } ) ;
assert . equal ( v . sent . at ( - 1 ) . type , 'lazyboy-mobile-key' ) ;
assert . equal ( v . sent . at ( - 1 ) . key , 'ctrl+c' ) ;
v . handlers . click ( { target : { closest : ( ) => ( { dataset : { action : 'hide-keyboard' } } ) } } ) ;
assert . equal ( v . keyboard . focused , false ) ;
assert . equal ( v . elements [ 'keyboard-shortcuts' ] . hidden , true ) ;
} ) ;
test ( 'status and HUD sit outside the remote pixels; screenshots stay opt-in' , ( ) => {
const app = fs . readFileSync ( 'apps/web/src/App.tsx' , 'utf8' ) ;
assert . match ( app , /\{!computerOpen && <RunStatus runId=\{liveRunId\}\/>\}/ ) ;
assert . match ( app , /<RunStatus runId=\{liveRunId\}\/>\{hud\}/ ) ;
assert . match ( app , /\{!computerOpen&&hud\}<div className="preview">/ ) ;
assert . match ( app , /--visible-height/ ) ;
const css = fs . readFileSync ( 'apps/web/src/computer.css' , 'utf8' ) ;
assert . match ( css , /\.computer-overlay>\.run-status/ ) ;
assert . match ( css , /\.computer-hud\{position:static/ ) ;
const tools = fs . readFileSync ( 'crates/api/src/tools.rs' , 'utf8' ) ;
assert . match ( tools , /screenshots are opt-in with observe:true/ ) ;
assert . match ( tools , /if args\.get\("observe"\)\.and_then\(Value::as_bool\) != Some\(true\)/ ) ;
const runs = fs . readFileSync ( 'crates/api/src/runs.rs' , 'utf8' ) ;
assert . match ( runs , /Never silently stop/ ) ;
assert . match ( runs , /let goal_mode = goal_mode \|\| !chat_only/ ) ;
} ) ;
2026-09-07 08:27:33 +00:00
const monitorJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/run-monitor.tsx' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS , jsx : ts . JsxEmit . ReactJSX } } ) . outputText ;
const monitorBox = { exports : { } , require : name => {
if ( name === 'react' ) return { useCallback : fn => fn , useEffect : ( ) => { } , useRef : ( ) => ( { current : null } ) , useState : ( ) => [ null , ( ) => { } ] } ;
if ( name === 'react/jsx-runtime' ) return { jsx : ( ) => null , jsxs : ( ) => null , Fragment : 'Fragment' } ;
if ( name === './api' ) return { api : async ( ) => ( { activity : [ ] } ) } ;
if ( name === './i18n' ) return { t : i18n . t , getLocale : ( ) => i18n . locale } ;
throw new Error ( 'unexpected import ' + name ) ;
} } ;
vm . runInNewContext ( monitorJs , monitorBox ) ;
const { formatElapsed , shortDuration , errorActions , errorTitle , trailText } = monitorBox . exports ;
const FAILURE _CODES = [ 'interrupted' , 'tool_timeout' , 'model_key' , 'model_quota' , 'model_unknown' , 'model_timeout' , 'network' , 'computer_gone' , 'lease_lost' , 'unknown' ] ;
test ( 'run timings stay on one glanceable line' , ( ) => {
assert . equal ( formatElapsed ( 0 ) , '0:00' ) ;
assert . equal ( formatElapsed ( 9_500 ) , '0:09' ) ;
assert . equal ( formatElapsed ( 65_000 ) , '1:05' ) ;
assert . equal ( formatElapsed ( 3_725_000 ) , '1:02:05' ) ;
assert . equal ( formatElapsed ( - 5 ) , '0:00' ) ;
assert . equal ( formatElapsed ( NaN ) , '0:00' ) ;
assert . equal ( shortDuration ( 340 ) , '340ms' ) ;
assert . equal ( shortDuration ( 6_400 ) , '6.4s' ) ;
assert . equal ( shortDuration ( 65_000 ) , '1:05' ) ;
assert . equal ( shortDuration ( null ) , '' ) ;
assert . equal ( shortDuration ( - 1 ) , '' ) ;
} ) ;
test ( 'every failure code offers at least one action, in the right order' , ( ) => {
const buttons = code => errorActions ( code ) . join ( ',' ) ;
assert . equal ( buttons ( 'model_key' ) , 'settings,retry' ) ;
assert . equal ( buttons ( 'model_unknown' ) , 'settings,retry' ) ;
assert . equal ( buttons ( 'model_quota' ) , 'retry,settings' ) ;
assert . equal ( buttons ( 'model_timeout' ) , 'retry,settings' ) ;
assert . equal ( buttons ( 'network' ) , 'retry,settings' ) ;
assert . equal ( buttons ( 'computer_gone' ) , 'screen,retry' ) ;
assert . equal ( buttons ( 'tool_timeout' ) , 'screen,retry' ) ;
assert . equal ( buttons ( 'interrupted' ) , 'screen,retry' ) ;
assert . equal ( buttons ( 'lease_lost' ) , 'retry' ) ;
for ( const code of [ ... FAILURE _CODES , 'made_up' , undefined , null ] ) {
const actions = errorActions ( code ) ;
assert . ok ( actions . length > 0 , String ( code ) ) ;
assert . ok ( actions . every ( action => [ 'retry' , 'screen' , 'settings' ] . includes ( action ) ) , code ) ;
assert . equal ( new Set ( actions ) . size , actions . length , 'no duplicate buttons for ' + code ) ;
}
} ) ;
test ( 'failure titles resolve in both catalogs and never repeat themselves' , ( ) => {
for ( const locale of Object . keys ( catalogs ) ) {
i18n . locale = locale ;
const titles = FAILURE _CODES . map ( code => errorTitle ( code ) ) ;
for ( const [ index , title ] of titles . entries ( ) ) {
assert . notEqual ( title , title . startsWith ( 'errorTitle' ) ? title : ` ${ title } -missing ` , FAILURE _CODES [ index ] ) ;
assert . ok ( ! title . startsWith ( 'errorTitle' ) , ` ${ locale } ${ FAILURE _CODES [ index ] } has no copy ` ) ;
assert . ok ( title . trim ( ) . length > 0 ) ;
}
assert . equal ( new Set ( titles ) . size , titles . length , locale ) ;
assert . ok ( errorTitle ( 'made_up' ) . length > 0 ) ;
}
i18n . locale = 'en' ;
} ) ;
test ( 'trail lines read as sentences with the detail a stuck run needs' , ( ) => {
assert . equal ( trailText ( { id : 1 , kind : 'model' , createdAt : '' , turn : 3 , elapsedMs : 6400 , text : '先打開網頁' } ) , 'turn 3, thought for 6.4s — 先打開網頁' ) ;
assert . match ( trailText ( { id : 2 , kind : 'tool' , createdAt : '' , step : 'browser: click #12' , status : 'ok' , elapsedMs : 400 } ) , /browser: click #12 · ok · 400ms/ ) ;
assert . match ( trailText ( { id : 3 , kind : 'tool' , createdAt : '' , step : 'shell: npm test' , status : 'timed_out' , snippet : 'killed after 150s' } ) , /timed out — killed after 150s/ ) ;
assert . match ( trailText ( { id : 4 , kind : 'run' , createdAt : '' , event : 'started' , task : '整理下載資料' } ) , /整理下載資料/ ) ;
assert . match ( trailText ( { id : 5 , kind : 'run' , createdAt : '' , event : 'completed' , turns : 9 } ) , /Done in 9 turns/ ) ;
assert . match ( trailText ( { id : 6 , kind : 'run' , createdAt : '' , event : 'waiting_input' , reason : '登入' } ) , /Waiting for you: 登入/ ) ;
2026-09-07 09:05:14 +00:00
assert . match ( trailText ( { id : 10 , kind : 'run' , createdAt : '' , event : 'paused' , reason : '已達本輪執行上限' } ) , /Paused: 已達本輪執行上限/ ) ;
2026-09-07 08:27:33 +00:00
assert . match ( trailText ( { id : 7 , kind : 'run' , createdAt : '' , event : 'retry' } ) , /Re-queued/ ) ;
assert . match ( trailText ( { id : 8 , kind : 'retry' , createdAt : '' , attempt : 2 , gaveUp : true , error : '429 rate limit' } ) , /attempt 2 failed, retrying · gave up — 429 rate limit/ ) ;
assert . match ( trailText ( { id : 9 , kind : 'notice' , createdAt : '' , text : '這輪不需要電腦' } ) , /這輪不需要電腦/ ) ;
} ) ;
test ( 'the bubble reads the run activity endpoint the API actually mounts' , ( ) => {
const monitor = fs . readFileSync ( 'crates/api/src/monitor.rs' , 'utf8' ) ;
assert . match ( monitor , /\.route\("\/api\/runs\/\{id\}\/activity", get\(activity\)\)/ ) ;
assert . match ( monitor , /\.route\("\/api\/runs\/\{id\}\/retry", post\(retry\)\)/ ) ;
const app = fs . readFileSync ( 'apps/web/src/App.tsx' , 'utf8' ) ;
assert . match ( app , /chip\.kind==="error"\?/ ) ;
assert . match ( app , /errorActions\(chip\.code\)/ ) ;
assert . match ( app , /`\/api\/runs\/\$\{runId\}\/retry`,\{method:"POST",body:"\{\}"\}/ ) ;
assert . match ( app , /<RunProbe runId=\{chip\.runId\|\|null\} align="end" label=\{t\("errorDetails"\)\}/ ) ;
assert . match ( app , /<RunProbe runId=\{member\.id===computer\.botId\?computer\.busyRunId:null\}><Avatar/ ) ;
const probe = fs . readFileSync ( 'apps/web/src/run-monitor.tsx' , 'utf8' ) ;
assert . match ( probe , /`\/api\/runs\/\$\{runId\}\/activity\$\{after\}`/ ) ;
2026-09-07 09:05:14 +00:00
assert . match ( probe , /export function RunStatus/ ) ;
assert . match ( probe , /`\/api\/runs\/\$\{runId\}\/activity\?limit=30`/ ) ;
2026-09-07 08:27:33 +00:00
assert . match ( fs . readFileSync ( 'apps/web/src/main.tsx' , 'utf8' ) , /import "\.\/monitor\.css";/ ) ;
} ) ;