Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified archify.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion archify/explainer/reader.css
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ dialog::backdrop{background:rgb(10 10 12/.55);backdrop-filter:blur(2px)}
/* Narrow desktop windows */
@media(max-width:1255px){.diagram-panel,.diagram-panel+.panel,.panel:has(+.diagram-panel){grid-column:span 12}}
@media(max-width:1100px){.shell{width:calc(100% - 40px)}.masthead{align-items:flex-start;flex-wrap:wrap}.board{gap:12px}.panel-head{padding:12px 16px}.panel-body{padding:14px 16px 16px}.answer-scope{padding:10px 16px 12px}}
@media(max-width:760px){.shell{width:calc(100% - 24px)}.board{display:flex;flex-direction:column}.sources ol{grid-template-columns:1fr}h1{font-size:22px}.verdict{font-size:18px}}
@media(max-width:760px){.shell{width:calc(100% - 24px)}.board{display:flex;flex-direction:column}.sources ol{grid-template-columns:1fr}h1{font-size:22px}.verdict{font-size:18px}.js .tools{position:relative;min-width:0;max-width:100%;flex-wrap:wrap}.tools .tool-menu{position:static}.tools .menu-options{left:0;right:auto;max-width:100%;top:calc(100% + 6px)}}
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;animation:none!important;transition:none!important}}

/* Print */
Expand Down
36 changes: 35 additions & 1 deletion test/explainer-browser.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,42 @@ test('retry layout and referenced node explanations remain readable and keyboard
}finally{await browser.close();}
});

test('Explainer toolbar and menus remain inside narrow viewports',{skip:!chrome},async()=>{
const {renderExplainer}=await import('../archify/explainer/render.mjs');
const browser=new ChromeVisualBrowser(chrome);
try {
for(const locale of ['en','zh-CN']) {
const document=formalDocument();document.meta.locale=locale;
const file=artifact('toolbar-'+locale,renderExplainer(document));
for(const width of [320,390,1440])for(const style of ['clean','editorial','product','swiss']) {
await browser.resetPage();await inspectPublicHtml(browser,{artifactPath:file,width,height:900,keyboard:false});
await keyboardActivate(browser,'.style-menu [aria-haspopup]');
await keyboardActivate(browser,`[data-style-choice="${style}"]`);
const selected=await pageValue(browser,`({style:document.documentElement.dataset.style,label:document.querySelector('[data-style-label]').textContent,choice:document.querySelector('[data-style-choice="${style}"]').textContent})`);
assert.equal(selected.style,style);assert.equal(selected.label,selected.choice);
assert.equal(await pageValue(browser,"document.activeElement===document.querySelector('.style-menu [aria-haspopup]')"),true);
const containment=await pageValue(browser,`({width:innerWidth,scroll:document.documentElement.scrollWidth,controls:[...document.querySelectorAll('.tools button')].filter(e=>e.getBoundingClientRect().width).map(e=>{const r=e.getBoundingClientRect();return {left:r.left,right:r.right}})})`);
assert.equal(containment.scroll,width,JSON.stringify({locale,width,style,containment}));
assert.ok(containment.controls.every(r=>r.left>=0&&r.right<=width),JSON.stringify(containment));
for(const menu of ['.style-menu','.export-menu']) {
await keyboardActivate(browser,menu+' [aria-haspopup]');
const popup=await pageValue(browser,`(()=>{const e=document.querySelector(${JSON.stringify(menu+' .menu-options')});const r=e.getBoundingClientRect();return {left:r.left,right:r.right,visible:getComputedStyle(e).display!=='none',scroll:document.documentElement.scrollWidth}})()`);
assert.equal(popup.visible,true);assert.ok(popup.left>=0&&popup.right<=width,JSON.stringify({locale,width,style,menu,popup}));assert.equal(popup.scroll,width);
assert.equal(await pageValue(browser,`document.querySelector(${JSON.stringify(menu+' .menu-options')}).contains(document.activeElement)`),true,'Keyboard opening must focus a menu item');
await keyboardPress(browser,'Escape');
const closed=await pageValue(browser,`(()=>{const toggle=document.querySelector(${JSON.stringify(menu+' [aria-haspopup]')});return {hidden:document.querySelector(${JSON.stringify(menu+' .menu-options')}).hidden,expanded:toggle.getAttribute('aria-expanded'),focused:document.activeElement===toggle}})()`);
assert.deepEqual(closed,{hidden:true,expanded:'false',focused:true},JSON.stringify({locale,width,style,menu,closed}));
}
await browser.cdp.send('Emulation.setEmulatedMedia',{media:'print'},await browser.sessionPromise);
assert.equal(await pageValue(browser,"getComputedStyle(document.querySelector('.tools')).display"),'none');
}
}
} finally { await browser.close(); }
});

async function pageValue(browser,expression){const result=await browser.cdp.send('Runtime.evaluate',{expression,returnByValue:true,awaitPromise:true},await browser.sessionPromise);if(result.exceptionDetails)throw new Error(result.exceptionDetails.exception?.description||result.exceptionDetails.text);return result.result.value;}
async function keyboardActivate(browser,selector,key='Enter',shift=false) {assert.equal(await pageValue(browser,`(function(){const e=document.querySelector(${JSON.stringify(selector)});if(!e)return false;e.scrollIntoView({block:'center'});e.focus();return document.activeElement===e;})()`),true,selector);for(const type of ['keyDown','keyUp'])await browser.cdp.send('Input.dispatchKeyEvent',{type,key,code:key===' '?'Space':key,windowsVirtualKeyCode:key==='Escape'?27:key===' '?32:key==='Tab'?9:13,modifiers:shift?8:0,...(key==='Enter'&&type==='keyDown'?{text:'\r',unmodifiedText:'\r'}:{})},await browser.sessionPromise);await pageValue(browser,'new Promise(resolve=>setTimeout(resolve,0))');}
async function keyboardActivate(browser,selector,key='Enter',shift=false) {assert.equal(await pageValue(browser,`(function(){const e=document.querySelector(${JSON.stringify(selector)});if(!e)return false;e.scrollIntoView({block:'center'});e.focus();return document.activeElement===e;})()`),true,selector);await keyboardPress(browser,key,shift);}
async function keyboardPress(browser,key,shift=false) {for(const type of ['keyDown','keyUp'])await browser.cdp.send('Input.dispatchKeyEvent',{type,key,code:key===' '?'Space':key,windowsVirtualKeyCode:key==='Escape'?27:key===' '?32:key==='Tab'?9:13,modifiers:shift?8:0,...(key==='Enter'&&type==='keyDown'?{text:'\r',unmodifiedText:'\r'}:{})},await browser.sessionPromise);await pageValue(browser,'new Promise(resolve=>setTimeout(resolve,0))');}
test('native step opens the complete Viewer and focuses exact nodes and edges with readable graph text',{skip:!chrome},async()=>{
const {renderExplainer}=await import('../archify/explainer/render.mjs');const {compileExplainerDiagram,prepareExplainerDiagrams}=await import('../archify/explainer/diagrams.mjs');
const spec=compileExplainerDiagram('sequence',['actor api | API | backend','actor redis | Redis | database','api | redis | Read cache','redis | api | Return cache'],{title:'Cache lookup',id:'native',quality:'showcase'});
Expand Down
Loading