diff --git a/package.json b/package.json index 29e164f..e9b2393 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hwacheon-network-viewer-validation", "private": true, - "scripts": { "test:ui": "node tests/viewer.cjs", "test:runner": "node tests/launch-failure.cjs", "test:audit": "node tests/audit-highlight.cjs" }, + "scripts": { "test:ui": "node tests/viewer.cjs", "test:runner": "node tests/launch-failure.cjs", "test:audit": "node tests/audit-highlight.cjs", "test:layout": "node tests/responsive-ui.cjs" }, "devDependencies": { "playwright": "1.62.1" } } diff --git a/tests/responsive-ui.cjs b/tests/responsive-ui.cjs new file mode 100644 index 0000000..1583ae2 --- /dev/null +++ b/tests/responsive-ui.cjs @@ -0,0 +1,52 @@ +const {chromium}=require('playwright'); +const assert=require('node:assert/strict'); +const fs=require('node:fs/promises'); +const path=require('node:path'); +const out=path.resolve(__dirname,'../test-results'); +(async()=>{ + const browser=await chromium.launch({headless:true,...(process.env.PLAYWRIGHT_CHANNEL?{channel:process.env.PLAYWRIGHT_CHANNEL}:{})}); + const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message)); + const results=[];let failure; + await fs.mkdir(out,{recursive:true}); + try{ + for(const [width,height] of [[1920,1080],[1366,768],[390,844]]){ + await page.setViewportSize({width,height});await page.goto(process.env.VIEWER_URL||'http://127.0.0.1:8081/',{waitUntil:'networkidle'}); + await page.waitForFunction(()=>document.querySelector('#loading').hidden); + const bounds=await page.evaluate(()=>({width:innerWidth,scroll:document.documentElement.scrollWidth, + mainBottom:document.querySelector('.app-grid').getBoundingClientRect().bottom, + footerTop:document.querySelector('.statusbar').getBoundingClientRect().top, + canvasWidth:document.querySelector('#scene').getBoundingClientRect().width})); + assert.ok(bounds.scroll<=width+1,JSON.stringify(bounds));assert.ok(bounds.mainBottom<=bounds.footerTop+1); + assert.ok(bounds.canvasWidth>width*(width<=1000?.9:.4)); + await page.screenshot({path:path.join(out,`ui9-${width}.png`),fullPage:true}); + if(width>1000){ + const original=bounds.canvasWidth;await page.locator('#toggle-controls').click(); + assert.equal(await page.locator('#controls-panel').isVisible(),false); + assert.ok(await page.locator('#scene').evaluate(e=>e.getBoundingClientRect().width)>original); + await page.locator('#toggle-controls').click(); + await page.locator('#toggle-inspector').click();assert.equal(await page.locator('#inspector-panel').isVisible(),false); + await page.locator('#toggle-inspector').click(); + }else{ + assert.equal(await page.locator('#controls-panel').isVisible(),false); + assert.equal(await page.locator('#inspector-panel').isVisible(),false); + await page.locator('#toggle-controls').click();assert.equal(await page.locator('#controls-panel').isVisible(),true); + await page.locator('#search').fill('UHR1-02901');await page.locator('#search-results button').first().click(); + assert.equal(await page.locator('#controls-panel').isVisible(),false); + assert.equal(await page.locator('#inspector-panel').isVisible(),true); + assert.equal(await page.locator('#selected-id').textContent(),'UHR1-02901'); + await page.screenshot({path:path.join(out,'ui9-mobile-details.png'),fullPage:true}); + await page.keyboard.press('Escape');assert.equal(await page.locator('#inspector-panel').isVisible(),false); + assert.equal(await page.locator('#toggle-inspector').evaluate(e=>e===document.activeElement),true); + await page.locator('#toggle-controls').click();await page.locator('#search').fill('__no_result__'); + assert.equal(await page.locator('#search-results button').count(),0); + await page.screenshot({path:path.join(out,'ui9-mobile-controls.png'),fullPage:true}); + await page.locator('#panel-backdrop').click({position:{x:375,y:100}}); + assert.equal(await page.locator('#controls-panel').isVisible(),false); + } + const overflow=await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth+1);assert.equal(overflow,false); + results.push({width,height,status:'passed'}); + } + assert.deepEqual(errors,[]); + }catch(e){failure=e.stack;process.exitCode=1;await page.screenshot({path:path.join(out,'ui9-failure.png'),fullPage:true}).catch(()=>{});} + finally{const report={status:failure?'failed':'passed',viewports:results,...(failure?{error:failure}:{})};await fs.writeFile(path.join(out,'ui9-validation.json'),JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2));await browser.close();} +})().catch(e=>{console.error(e);process.exitCode=1;}); diff --git a/validation/issue-9-result.json b/validation/issue-9-result.json new file mode 100644 index 0000000..e90c883 --- /dev/null +++ b/validation/issue-9-result.json @@ -0,0 +1,75 @@ +{ + "status": "passed", + "viewports": [ + { + "width": 1920, + "height": 1080, + "status": "passed" + }, + { + "width": 1366, + "height": 768, + "status": "passed" + }, + { + "width": 390, + "height": 844, + "status": "passed" + } + ], + "existing_facility_panel_validation": { + "status": "passed", + "facilities_checked": 1662, + "search_click_samples": [ + "MH3", + "UHR1-0058", + "UHR1-02901", + "OUT_PUMP", + "UHR1-01411", + "C_OUT_PUMP" + ], + "checks": [ + "all facility information panels", + "summary counts", + "real search/selection clicks", + "missing-facility explanations", + "unknown ID", + "scale reset", + "plan view", + "layer toggles", + "audit dialog", + "reset", + "no page errors" + ], + "browser": "156.0.4314.42", + "limitations": [ + "Information consistency; screenshot review is separate from dimensional geometry verification." + ] + }, + "audit_highlight_validation": { + "status": "passed", + "target_conduits": 207, + "red_pixels_on": 4127, + "red_pixels_off": 0, + "original_colors_restored": true, + "checks": [ + "target IDs", + "red pixels", + "1 second phases", + "exact original pixel restoration", + "timer cancellation", + "steady mode", + "layer visibility", + "selection reasons", + "3 unplaced facilities", + "reset", + "reduced motion", + "no page errors" + ] + }, + "visual_review": [ + "ui9-1366.png", + "ui9-390.png", + "ui9-mobile-details.png" + ] +} diff --git a/web/app.js b/web/app.js index b8174e3..b9aaf0e 100644 --- a/web/app.js +++ b/web/app.js @@ -33,6 +33,7 @@ function drawProfile(f){ c.fillStyle='#587455';c.textAlign='left';c.fillText(`관저 ${fmt(z1,3)}`,13,142);c.textAlign='right';c.fillText(`${fmt(z2,3)} m`,227,142); } export function selectFacility(f){ + if(f&&window.innerWidth<=1000)setPanel('inspector',true); if(!f)return;app.selected=f;app.renderer.set({selected:f.pickId});$('#view-name').textContent=f.id; let html=`${labels[f.type]}