import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import vm from 'node:vm'; import ts from 'typescript'; const html = await readFile(new URL('../index.html', import.meta.url), 'utf8'); // These requirements fail on the prior developer-oriented shell. assert.match(html, /id="tool-tabs"[^>]*role="tablist"/, 'consumer tools use an accessible tab list'); assert.match(html, /]*id="developer-tools"(?![^>]*\bopen)[^>]*>/, 'diagnostics are collapsed by default'); assert.ok(html.indexOf('id="reference-strip"') > html.indexOf('id="developer-tools"'), 'references live in developer tools'); for (const value of ['2','0','1']) assert.match(html, new RegExp(`data-pattern="${value}"`)); for (const id of ['image','test-photo','sample','ai-depth','depth-provenance','status','grid-size','curvature','photo-glass-distance','camera-offset','photo-zoom','photo-pan-x','photo-pan-y','reset','gyro','depth-image','video','effect','view-mode','depth-scale','light-x','light-y','sensitivity','depth-inspection']) { assert.equal([...html.matchAll(new RegExp(`\\bid="${id}"`,'g'))].length, 1, `${id} stays available exactly once`); } for (const [,attrs,body] of html.matchAll(/]*)>([\s\S]*?)<\/label>/g)) { const controlId=body.match(/<(?:input|select)\b[^>]*\bid="([^"]+)"/)?.[1]; if (controlId && body.includes('new Element({tool:name})); const panels=tabs.map(tab=>new Element({panel:tab.dataset.tool})); const cards=['2','0','1'].map(pattern=>new Element({pattern})); const select=new Element();select.value='2'; const effect=new Element();effect.value='mosaic'; const cardGroup=new Element(); const document={querySelectorAll:query=>query==='[data-tool]'?tabs:query==='[data-panel]'?panels:cards,querySelector:query=>({'#mosaic-style':select,'#effect':effect,'#pattern-cards':cardGroup}[query])}; const source=await readFile(new URL('../src/ui/PhotoBoothUI.ts',import.meta.url),'utf8'); const module={exports:{}}; vm.runInNewContext(ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS}}).outputText,{exports:module.exports,Event}); module.exports.initPhotoBoothUI(document,new AbortController().signal); const click=element=>element.dispatchEvent({type:'click'}); assert.equal(tabs[0].attributes['aria-selected'],'true');assert.deepEqual(panels.map(p=>p.hidden),[false,true,true]); click(tabs[2]);assert.deepEqual(panels.map(p=>p.hidden),[true,true,false]); assert.deepEqual(tabs.map(t=>t.tabIndex),[-1,-1,0]); let prevented=false;tabs[2].dispatchEvent({type:'keydown',key:'ArrowRight',preventDefault(){prevented=true;}}); assert.equal(tabs[0].focused,true);assert.equal(prevented,true); tabs[0].dispatchEvent({type:'keydown',key:'End',preventDefault(){}});assert.equal(tabs[2].attributes['aria-selected'],'true'); tabs[2].dispatchEvent({type:'keydown',key:'Home',preventDefault(){}});assert.equal(tabs[0].attributes['aria-selected'],'true'); let changes=0;select.addEventListener('change',()=>changes++); click(cards[0]);assert.equal(changes,0,'reselecting the current pattern must not reset its custom grid'); for (const card of [cards[1],cards[2],cards[0],cards[1]]) {click(card);assert.equal(select.value,card.dataset.pattern);assert.equal(card.attributes['aria-pressed'],'true');assert.equal(cards.filter(c=>c.attributes['aria-pressed']==='true').length,1);} assert.equal(changes,4); select.value='1';select.dispatchEvent(new Event('change'));assert.equal(cards[2].attributes['aria-pressed'],'true'); effect.value='frosted';effect.dispatchEvent(new Event('change'));assert.equal(cardGroup.hidden,true); effect.value='mosaic';effect.dispatchEvent(new Event('change'));assert.equal(cardGroup.hidden,false); const css=await readFile(new URL('../src/style.css',import.meta.url),'utf8'); assert.match(css,/prefers-reduced-motion:\s*reduce/); assert.match(css,/:focus-visible/); assert.match(css,/min-height:\s*44px/); // Source-level palette checks, not a claim about computed styles or rendered UI. const ruleColor=(selector,property)=>{ const start=css.indexOf(`${selector} {`);assert.ok(start>=0,selector); const rule=css.slice(start,css.indexOf('}',start)); const value=rule.match(new RegExp(`${property}:#([0-9a-f]{6})(?:;|\\b)`))?.[1];assert.ok(value,`${selector} ${property}`);return value; }; const luminance=hex=>[0,2,4].map(i=>parseInt(hex.slice(i,i+2),16)/255).map(c=>c<=.04045?c/12.92:((c+.055)/1.055)**2.4).reduce((sum,c,i)=>sum+c*[.2126,.7152,.0722][i],0); const contrast=(a,b)=>(Math.max(luminance(a),luminance(b))+.05)/(Math.min(luminance(a),luminance(b))+.05); assert.ok(contrast(ruleColor('.pattern-detail','color'),ruleColor('.pattern-card[aria-pressed=true]','background'))>=4.5,'selected card detail contrast'); assert.ok(contrast(ruleColor('.app-footer','color'),ruleColor(':root','background'))>=4.5,'footer contrast'); assert.match(css,/#canvas-wrap:focus-visible\s*\{\s*outline-offset:-4px/,'preview focus ring stays inside the clipping edge'); assert.match(css,/@media\(max-width:850px\)/,'tablet/phone stack breakpoint'); assert.match(css,/@media\(max-width:480px\)/,'compact phone styling'); console.log('Photo booth UI passed: preserved controls, collapsed diagnostics, card synchronization, repeat selection, effect visibility, tab keyboard navigation, reduced-motion/focus/touch rules, palette contrast and responsive source rules (no layout engine)');