import assert from 'node:assert/strict'; import {readFileSync} from 'node:fs'; import test from 'node:test'; import vm from 'node:vm'; const script = readFileSync(new URL('../static/app.js', import.meta.url), 'utf8'); const presets = Object.fromEntries(['english', 'chinese'].map(name => [name, JSON.parse(readFileSync(new URL(`../examples/${name}.json`, import.meta.url), 'utf8'))])); // Run the real editor against a small DOM/fetch adapter; no models or browser packages needed. function editor() { const elements = new Map(), requests = [], decoded = []; let nextId = 0, failExample = null; const element = id => { if(!elements.has(id)) elements.set(id, {value:'', dataset:{}, style:{setProperty(){}}, classList:{toggle(){},add(){},remove(){}}, setAttribute(name,value){this.attributes??={};this.attributes[name]=value;}, removeAttribute(name){delete this.attributes?.[name];}, addEventListener(){}, getContext(){return {drawImage(){}};}, children:[], focus(){}}); return elements.get(id); }; const examples = ['english','chinese'].map(example => ({...element(example),dataset:{example}})); const models = ['meanflow','teacher'].map(model => ({...element(model),dataset:{model}})); const context = vm.createContext({structuredClone, URLSearchParams, console, setTimeout:(fn,ms)=>setTimeout(fn,ms).unref(), clearTimeout, document:{getElementById:element,addEventListener(){},querySelectorAll(selector){ if(selector==='[data-example]') return examples; if(selector==='[data-model]') return models; return []; }}, Image:class {async decode(){decoded.push(this.src);this.complete=true;}}, location:{pathname:'/',hash:''}, history:{replaceState(){}}, fetch:async(url,options) => { if(url==='/api/config') return {ok:true,json:async()=>({transport:'http'})}; assert.equal(url,'/api/session'); const {example}=JSON.parse(options.body);requests.push(example); if(example===failExample){failExample=null;throw new Error('Network unavailable');} return {ok:true,json:async()=>({id:`session-${++nextId}`,example,width:1024,height:1024, image:`/examples/${example}-preview.webp?v=release`, regions:presets[example].map((r,i)=>({...r,id:`example-${i}`,enabled:true,origin:'example'})), settings:{variant:'meanflow',steps:4,seed:42,guidance:2.6,text_threshold:.5}})}; }}); vm.runInContext(script,context); const run=code=>vm.runInContext(code,context); return {run,requests,decoded,element,failNext(example){failExample=example;}}; } async function ready(app) { await app.run("Promise.all([prepareExample('english'),prepareExample('chinese')])"); await new Promise(resolve=>setImmediate(resolve)); } test('switching and restoring example edits reuse prepared sessions and decoded images', async()=>{ const app=editor();await ready(app); assert.equal(app.requests.length,2); assert.equal(app.decoded.length,2); for(const example of ['chinese','english','chinese','english']) { await app.run(`loadExample('${example}')`); assert.equal(app.run('state.regions.length'),presets[example].length); assert.equal(app.element('guidance').value,2.6); assert.equal(app.element('refineButton').disabled,false); app.run("state.regions[0].text='edited'; state.regions[0].bbox[0]=0; state.regions[0].enabled=false;"); } await app.run("loadExample('english')"); assert.equal(app.run('state.regions[0].text'),presets.english[0].text); assert.equal(app.run('state.regions[0].bbox[0]'),presets.english[0].bbox[0]); assert.equal(app.run('state.regions[0].enabled'),true); assert.equal(app.requests.length,2); assert.equal(app.decoded.length,2); }); test('concurrent preparation shares one request and an expired session is renewed',async()=>{ const app=editor();await ready(app); app.run("exampleCache.get('chinese').expires=0"); await app.run("Promise.all([prepareExample('chinese'),prepareExample('chinese')])"); assert.equal(app.requests.filter(x=>x==='chinese').length,2); }); test('failed preparation is retryable without replacing the current editor',async()=>{ const app=editor();await ready(app); await app.run("loadExample('english')"); const current=app.run('state.session.id'); app.run("exampleCache.get('chinese').expires=0");app.failNext('chinese'); await app.run("loadExample('chinese')"); assert.equal(app.run('state.session.id'),current); assert.equal(app.run('state.busy'),false); await app.run("loadExample('chinese')"); assert.equal(app.run('state.regions.length'),13); }); test('detection immediately shows central activity and keeps unknown progress indeterminate',async()=>{ const app=editor();await ready(app); app.run("setBusy(true,'Preparing detection','detect'); updateActivity('Detecting characters and words',.3,true)"); assert.equal(app.element('jobProgress').hidden,false); assert.equal(app.element('jobTitle').textContent,'Detecting text'); assert.equal(app.element('jobPercent').hidden,true); assert.equal(app.element('progressTrack').attributes['aria-valuenow'],undefined); assert.equal(app.element('detectButton').attributes['aria-busy'],'true'); }); test('refinement displays reported progress, then leaves a nonblocking completion state',async()=>{ const app=editor();await ready(app); app.run("setBusy(true,'Waiting for GPU','refine');updateActivity('Refining · step 2 of 4',.55,false)"); assert.equal(app.element('jobTitle').textContent,'Refining text'); assert.equal(app.element('jobPercent').textContent,'55%'); assert.equal(app.element('jobPercent').hidden,false); assert.equal(app.element('progressTrack').attributes['aria-valuenow'],'55'); app.run("finishActivity('done','4 regions · 2.7s');setBusy(false)"); assert.equal(app.element('jobProgress').dataset.state,'done'); assert.equal(app.element('jobProgress').hidden,false); assert.equal(app.element('refineButton').attributes['aria-busy'],'false'); app.run("setBusy(true,'Preparing detection','detect')"); assert.equal(app.element('jobProgress').dataset.state,'running'); assert.equal(app.element('jobTitle').textContent,'Detecting text'); }); test('a job error remains visible after controls are released and can be dismissed',async()=>{ const app=editor();await ready(app); app.run("setBusy(true,'Waiting for GPU','refine');finishActivity('error','GPU quota exceeded');setBusy(false)"); assert.equal(app.element('jobProgress').hidden,false); assert.equal(app.element('jobProgress').dataset.state,'error'); assert.equal(app.element('jobMessage').textContent,'GPU quota exceeded'); assert.equal(app.element('closeProgress').hidden,false); app.run('dismissActivity()');assert.equal(app.element('jobProgress').hidden,true); });