/* Mock-only: known image metadata renders real slots; no synthetic skeletons. */ const assert=require('assert'),fs=require('fs'),{chromium}=require('playwright'); (async()=>{ const base=process.env.FRAME_FLOW_URL;assert(base); const token=fs.readFileSync(process.env.FRAME_FLOW_ENV_FILE,'utf8').split('\n').find(x=>x.startsWith('FRAME_FLOW_TOKEN=')).slice(17); assert((await(await fetch(base+'/api/v1/settings',{headers:{Authorization:'Bearer '+token}})).json()).mock===true); const browser=await chromium.launch({headless:true,executablePath:process.env.CHROME_EXECUTABLE||'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'}); try{ const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message)); let releaseAccount;await page.route('**/api/v1/auth/me',async r=>{await new Promise(resolve=>releaseAccount=resolve);await r.continue();}); await page.goto(base);await page.locator('#nav button').first().waitFor();assert.equal(await page.locator('.image-pending,.placeholder-line,.page-placeholder').count(),0);assert.equal(await page.locator('.page-loading').count(),0);releaseAccount(); await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor(); let checks=1; for(const width of [1440,390]){ await page.setViewportSize({width,height:844});await page.evaluate(()=>state.galleryCache?.clear());let releaseMetadata,metadataRequested,releaseImage,imageRequested; await page.route('**/api/v1/devices/*/images',async r=>{metadataRequested();await new Promise(resolve=>releaseMetadata=resolve);await r.fulfill({json:[{image_id:1,name:'Progressive fixture',url:'/progressive-fixture.svg',current:false}]});}); await page.route('**/progressive-fixture.svg*',async r=>{assert.equal(await page.locator('#gallery-search').count(),1,'Search must precede image requests');assert(await page.locator('#gallery-search').evaluate(e=>typeof e.oninput==='function'));imageRequested();await new Promise(resolve=>releaseImage=resolve);await r.fulfill({contentType:'image/svg+xml',body:''});}); const metadata=new Promise(resolve=>metadataRequested=resolve);const image=new Promise(resolve=>imageRequested=resolve); await page.evaluate(()=>{window.navigation=navigate('gallery');});await metadata; assert.equal(await page.locator('.image-pending,.placeholder-line,.page-placeholder').count(),0);assert.equal(await page.locator('.image-card').count(),0);assert.equal(await page.locator('.page-loading').count(),0);checks++; releaseMetadata();await image;await page.evaluate(()=>window.navigation); assert(await page.locator('#gallery-upload').isEnabled());assert.equal(await page.locator('.image-loading .spinner').count(),1);assert.equal(await page.locator('.image-pending').count(),0);checks++; assert(await page.locator('.image-loading').evaluate(box=>{const a=box.getBoundingClientRect(),b=box.querySelector('.spinner').getBoundingClientRect();return Math.abs(a.x+a.width/2-b.x-b.width/2)<1&&Math.abs(a.y+a.height/2-b.y-b.height/2)<1;}));checks++; releaseImage();await page.waitForFunction(()=>document.querySelector('.thumb')?.naturalWidth===300&&!document.querySelector('.image-loading'));checks++; await page.evaluate(()=>navigate('upload')); const cachedMetadata=new Promise(resolve=>metadataRequested=resolve);const cachedImage=new Promise(resolve=>imageRequested=resolve); await page.evaluate(()=>{window.navigation=navigate('gallery');});await cachedMetadata;await cachedImage; assert.equal(await page.locator('#gallery-search').count(),1,'Search must exist while cached metadata refresh is pending');assert(await page.locator('#gallery-search').evaluate(e=>typeof e.oninput==='function'),'Search must be interactive');assert.equal(await page.locator('.image-card').count(),1);assert.equal(await page.locator('.image-loading .spinner').count(),1);assert.equal(await page.locator('.image-pending,.placeholder-line,.page-placeholder').count(),0);checks++; releaseMetadata();await page.evaluate(()=>window.navigation);releaseImage();await page.waitForFunction(()=>document.querySelector('.thumb')?.naturalWidth===300&&!document.querySelector('.image-loading')); await page.unroute('**/api/v1/devices/*/images');await page.unroute('**/progressive-fixture.svg*'); let releaseDevice,deviceRequested;await page.route('**/api/v1/devices/living-room',async r=>{deviceRequested();await new Promise(resolve=>releaseDevice=resolve);await r.continue();}); const device=new Promise(resolve=>deviceRequested=resolve);await page.evaluate(()=>{window.navigation=navigate('dashboard');});await device; assert.equal(await page.locator('.image-pending,.placeholder-line,.page-placeholder').count(),0);assert.equal(await page.locator('.page-loading').count(),0);checks++; releaseDevice();await page.evaluate(()=>window.navigation);await page.unroute('**/api/v1/devices/living-room'); } let releaseJobs,jobsRequested;await page.route('**/api/v1/jobs',async r=>{jobsRequested();await new Promise(resolve=>releaseJobs=resolve);await r.fulfill({json:[]});}); await page.route('**/api/v1/assets?*',r=>r.fulfill({json:[{asset_id:'local-fixture',name:'Local fixture',url:'/local-fixture.svg',origin:'manual',starred:false}]})); await page.route('**/local-fixture.svg',r=>r.fulfill({contentType:'image/svg+xml',body:''})); const jobs=new Promise(resolve=>jobsRequested=resolve);await page.evaluate(()=>{state.tab='local';return navigate('gallery');});await jobs; assert.equal(await page.locator('.asset-select').count(),1);assert(await page.locator('#gallery-search').evaluate(e=>typeof e.oninput==='function'));await page.locator('#gallery-search').fill('no-match');assert(await page.locator('.image-card').evaluate(e=>e.hidden));releaseJobs();checks++; assert.deepEqual(errors,[]);console.log('Known image positions without skeletons:',checks,'checks PASS; delayed bootstrap, metadata and images; no page errors'); }finally{await browser.close();} })().catch(e=>{console.error(e.message);process.exit(1)});