42 lines
5.2 KiB
JavaScript
42 lines
5.2 KiB
JavaScript
// Isolated synthetic Results headers; no backend or running app mutation.
|
||
import assert from 'node:assert/strict';
|
||
import {readFile} from 'node:fs/promises';
|
||
import {createServer} from 'node:http';
|
||
import {pathToFileURL} from 'node:url';
|
||
import {build} from 'esbuild';
|
||
const {chromium}=await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE).href);
|
||
const main=await readFile('src/main.tsx','utf8');
|
||
const styles=[...main.matchAll(/^import '([^']+\.css)';/gm)].map(([,path])=>`import ${JSON.stringify(path.startsWith('.')?'./src/'+path.slice(2):path)};`).join('\n');
|
||
const bundle=await build({stdin:{resolveDir:process.cwd(),loader:'tsx',contents:`${styles}
|
||
import React,{useState} from 'react';import {createRoot} from 'react-dom/client';
|
||
import {FloatingResultsWindow} from './src/features/linked-results/FloatingResultsWindow';
|
||
function Example(){const [minimized,setMinimized]=useState(true);return <>{['SMBv1 is enabled on Domain Controllers','Unexpected principal with metadata edit permission on a GPO linked to a critical AD container '.repeat(8)].map((title,index)=><FloatingResultsWindow key={index} id={'w'+index} title={'Results ('+(index+1)+') – '+title} sheets={[]} color="" zIndex={1} offset={0} clusterPosition={{left:30,top:50+index*40}} clusterWidth={Number(new URLSearchParams(location.search).get('width'))} clusterIndex={index} minimized={minimized} onToggleMinimize={()=>setMinimized(value=>!value)} search="" onActivate={()=>{}} onClose={()=>{}} onToggleBookmark={()=>{}} onOpenRecord={()=>{}}/>)}</>};createRoot(document.getElementById('root')).render(<Example/>);`},jsx:'automatic',bundle:true,write:false,outdir:'synthetic',format:'iife',loader:{'.woff2':'dataurl','.woff':'dataurl','.png':'dataurl','.svg':'dataurl'}});
|
||
const server=createServer((req,res)=>{const file=bundle.outputFiles.find(file=>req.url==='/'+file.path.split(/[\\/]/).at(-1));res.setHeader('Content-Type',file?(req.url.endsWith('.css')?'text/css':'text/javascript'):'text/html');res.end(file?.contents??'<!doctype html><html><head><meta charset="utf-8"><link rel="stylesheet" href="/stdin.css"></head><body><div id="root"></div><script src="/stdin.js"></script></body></html>');});
|
||
await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));let browser;
|
||
try{
|
||
browser=await chromium.launch({headless:true,channel:'chrome'});const page=await browser.newPage();
|
||
for(const width of [760,440])for(const theme of ['dark','light']){
|
||
await page.setViewportSize({width:1000,height:900});await page.goto('http://127.0.0.1:'+server.address().port+'/?width='+width);await page.waitForSelector('.results-window');
|
||
await page.evaluate(theme=>{document.documentElement.dataset.theme=theme;
|
||
// A synthetic marker exercises the existing ring's CSS geometry; production group logic is covered separately.
|
||
const slot=document.querySelector('.results-window-related-slot');slot.innerHTML='<button class="related-finding-ring is-inline" aria-label="Synthetic group"><span></span></button>';
|
||
},theme);
|
||
const boxes=await page.locator('.results-window').evaluateAll(nodes=>nodes.map(node=>{
|
||
const head=node.querySelector('.results-window-head'),title=node.querySelector('.results-window-name'),actions=node.querySelector('.results-window-actions');
|
||
const rect=element=>{const r=element.getBoundingClientRect();return{left:r.left,right:r.right,top:r.top,bottom:r.bottom}};
|
||
return{head:rect(head),title:rect(title),actions:rect(actions),controls:[...actions.querySelectorAll('button:not(:disabled)')].map(button=>({name:button.getAttribute('aria-label')??button.textContent,rect:rect(button),hit:document.elementFromPoint(button.getBoundingClientRect().x+button.clientWidth/2,button.getBoundingClientRect().y+button.clientHeight/2)?.closest('button')===button})),clipped:title.scrollWidth>title.clientWidth,ellipsis:getComputedStyle(title).textOverflow,tooltip:title.title};
|
||
}));
|
||
assert.ok(Math.abs(boxes[0].actions.left-boxes[1].actions.left)<1);
|
||
assert.equal(await page.locator('.results-window-actions').evaluateAll(nodes=>nodes.every(node=>node.lastElementChild.classList.contains('results-window-close'))),true);
|
||
assert.equal(await page.locator('.results-window').evaluateAll(nodes=>nodes.every(node=>{
|
||
const close=node.querySelector('.results-window-close').getBoundingClientRect();
|
||
return [...node.querySelectorAll('.results-window-actions button')].every(button=>button.getBoundingClientRect().right<=close.right);
|
||
})),true);
|
||
for(const box of boxes){assert.ok(box.title.right<=box.actions.left);for(const control of box.controls){assert.ok(control.rect.right<=box.head.right);assert.equal(control.hit,true,JSON.stringify({width,theme,control,boxes}));}}
|
||
assert.equal(boxes[1].clipped,true);assert.equal(boxes[1].ellipsis,'ellipsis');assert.ok(boxes[1].tooltip.length>600);
|
||
await page.locator('.results-window-toggle').nth(1).click();assert.equal(await page.locator('.results-window.is-minimized').count(),0);
|
||
await page.locator('.results-window-toggle').nth(1).click();assert.equal(await page.locator('.results-window.is-minimized').count(),2);
|
||
}
|
||
console.log('Both themes at 760/440px Results widths: matching action positions, title ellipsis, full tooltip, all controls hit-testable, minimize/restore preserved.');
|
||
}finally{await browser?.close();await new Promise(resolve=>server.close(resolve));}
|