Files
archy/tests/lifecycle/peer-files-scroll-browser.cjs
T

81 lines
6.7 KiB
JavaScript

// Exercises actual PeerFiles, dashboard wrapper, shared styles and fade directive.
// Synthetic catalog only; no live app, wallet, payment or node requests.
const { chromium, expect } = require('../../neode-ui/node_modules/@playwright/test');
const fs = require('node:fs');
const path = require('node:path');
const base = process.env.PEER_SCROLL_TEST_URL || 'http://127.0.0.1:5198';
const output = process.env.PEER_SCROLL_EVIDENCE || '/tmp/archy-peer-scroll';
fs.mkdirSync(output, {recursive:true,mode:0o700});
const peer = 'a'.repeat(56) + '.onion';
(async()=>{
// Vite resolves dependency URLs for this exact checkout.
const source = await fetch(base+'/src/stores/resources.ts').then(r=>r.text());
const vue = source.match(/from "([^"]*\/vue\.js[^" ]*)"/)[1];
const peerSource = await fetch(base+'/src/views/PeerFiles.vue').then(r=>r.text());
const routerUrl = peerSource.match(/from "([^"]*vue-router\.js[^" ]*)"/)[1];
const pinia = source.match(/from "([^"]*pinia[^" ]*\.js[^" ]*)"/)[1];
const browser = await chromium.launch({headless:true});
const results=[];
try {
for (const width of [390,844,1440]) {
const page = await browser.newPage({viewport:{width,height:900},hasTouch:width<1000});
const errors=[];page.on('pageerror',e=>{errors.push(e.message);console.error('PAGE',e.message)});
page.on('console',m=>{if(m.type()==='error')console.error('CONSOLE',m.text())});
await page.route('**/peer-scroll-fixture',route=>route.fulfill({contentType:'text/html',body:`<!doctype html><meta name="viewport" content="width=device-width,initial-scale=1"><div id="fixture"></div><script type="module">
import {createApp,h} from '${vue}';
import {createPinia} from '${pinia}';
import {createRouter,createMemoryHistory} from '${routerUrl}';
import PeerFiles from '/src/views/PeerFiles.vue';
import {wrapperFor} from '/src/views/dashboard/dashboardViewWrappers.ts';
import {rpcClient} from '/src/api/rpc-client.ts';
import '/src/style.css';import '/src/views/dashboard/dashboard-styles.css';
const peer=${JSON.stringify(peer)};
const items=Array.from({length:48},(_,i)=>({id:'fixture-'+i,filename:'Shared document '+String(i+1).padStart(2,'0')+'.txt',mime_type:'text/plain',size_bytes:1024,description:'Shared file fixture',access:'free'}));
rpcClient.federationListNodes=async()=>({nodes:[{did:'did:key:fixture-peer',pubkey:'fixture',onion:peer,name:'Peer file library',trust_level:'trusted'}]});
rpcClient.call=async({method})=>{if(method==='content.browse-peer')return {items,transport:'fips'};if(method==='content.list-owned')return {items:[]};throw Error('Unexpected fixture RPC: '+method)};
const router=createRouter({history:createMemoryHistory(),routes:[{path:'/dashboard/cloud',name:'cloud',component:{render:()=>null}},{path:'/dashboard/federation',name:'federation',component:{render:()=>null}}]});
await router.push('/dashboard/cloud');
const Wrapper=wrapperFor('/dashboard/cloud/peers/'+peer);
createApp({render:()=>h('div',{class:'dashboard-view',style:'height:100dvh;position:relative'},[h('div',{class:'perspective-container-wrapper'},[h('div',{class:'perspective-container'},[h(Wrapper,{mobileTabPaddingTop:width<768?64:null,needsMobileBackButtonSpace:true},{default:()=>h(PeerFiles,{peerId:peer,class:'view-container flex-none'})})])])])}).use(createPinia()).use(router).mount('#fixture');
window.fixtureReady=true;
</script>`.replace('width<768',`${width}<768`)}));
// Every HTTP request stays on the fixture Vite origin.
await page.route('**/*',route=>{
if(new URL(route.request().url()).origin!==base)return route.abort();
return route.fallback();
});
await page.goto(base+'/peer-scroll-fixture');
await page.waitForSelector('.peer-files-scroll .glass-card',{timeout:45000});
const scroll=page.locator('.peer-files-scroll');
const header=page.locator('.peer-files-identity');
const back=page.getByRole('button',{name:'Back to Cloud',exact:true}).filter({visible:true});
await expect(back).toBeVisible();
const before=await header.boundingBox();const beforeBack=await back.boundingBox();
const initial=await scroll.evaluate(el=>({height:el.clientHeight,content:el.scrollHeight,mask:el.style.maskImage,outer:el.closest('.dashboard-scroll-panel').scrollTop}));
if(initial.height<160||initial.content<=initial.height)throw Error('Missing constrained scrolling viewport '+JSON.stringify(initial));
if(!initial.mask.includes('black 0px')||!initial.mask.includes('18px'))throw Error('Initial bottom fade absent');
await scroll.evaluate(el=>{el.scrollTop=350;el.dispatchEvent(new Event('scroll'))});
await page.waitForTimeout(100);
const after=await header.boundingBox();const afterBack=await back.boundingBox();
for(const key of ['x','y','width','height']){
if(Math.abs(before[key]-after[key])>1)throw Error('Header moved: '+key);
if(Math.abs(beforeBack[key]-afterBack[key])>1)throw Error('Back button moved: '+key);
}
const mid=await scroll.evaluate(el=>({top:el.scrollTop,mask:el.style.maskImage,outer:el.closest('.dashboard-scroll-panel').scrollTop,rect:el.getBoundingClientRect().toJSON()}));
if(mid.top<300||mid.outer!==initial.outer||!mid.mask.includes('black 18px'))throw Error('Wrong scroll ownership or top fade');
await page.screenshot({path:path.join(output,`peer-scroll-${width}.png`)});
await scroll.evaluate(el=>{el.scrollTop=el.scrollHeight;el.dispatchEvent(new Event('scroll'))});
const last=await scroll.evaluate(el=>({mask:el.style.maskImage,remaining:el.scrollHeight-el.clientHeight-el.scrollTop}));
if(last.remaining>1||!/100% [+-] 0px/.test(last.mask))throw Error('Bottom fade not cleared at end '+JSON.stringify(last));
const isolation=await page.evaluate(()=>{const panel=document.createElement('div');panel.className='dashboard-scroll-panel';panel.style.overflowY='auto';panel.innerHTML='<div class=unrelated-page></div>';document.body.append(panel);const value=getComputedStyle(panel).overflowY;panel.remove();return value});
if(isolation!=='auto')throw Error('Unrelated page scroll changed');
const horizontal=await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth);
if(horizontal)throw Error('Horizontal overflow');
if(errors.length)throw Error(errors.join('\n'));
results.push({width,height:900,passed:true,initial,mid,last,header:after,back:afterBack});
await page.close();
}
} finally {await browser.close();fs.writeFileSync(path.join(output,'result.json'),JSON.stringify(results,null,2));}
console.log(JSON.stringify({passed:true,viewports:results.map(r=>r.width),evidence:output}));
})().catch(e=>{console.error(e);process.exitCode=1});