Deploy Storybook / deploy (push) Successful in 59s
Remove the data, layout, interaction and employee-popup setting panels from the Expanded OrgChart Story. The chart now renders a fixed sample (7 companies x 20, TB, all expanded, collapse/zoom/pan on, Drag & Drop off, HM Dialog) right under the page title. Story ID, OrgChart packages and public API are unchanged. Align the Story browser checks (P7, P8, P9, P9.2-P9.5, geometry, popup parity, hierarchy, HM Story) with the preview: setting-only scenarios are removed, chart rendering, collapse, Zoom/Pan/Fit and HM Dialog checks run on the fixed state. Move/Drag & Drop coverage stays in the Vanilla Core checks (verify-p9-3-core, verify-p9-4-core). Verified: tsc, test:guard 94/94, node --check on 11 scripts, changed browser sections PASS. Pre-existing P7 story-menu, P8 Department long-name, P9 Expanded tb/runner failures remain separate issues. verify-p9-5-popup-parity was not run (needs the Vanilla example server).
94 lines
7.0 KiB
JavaScript
94 lines
7.0 KiB
JavaScript
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { launchChromium } from '../../scripts/orgchart-browser-runtime.mjs';
|
|
|
|
const root = fileURLToPath(new URL('../../', import.meta.url));
|
|
const output = path.join(root, 'test-results/org-chart-p9-5');
|
|
const phase = process.argv[2] ?? 'after';
|
|
const origin = process.env.ORGCHART_STORY_ORIGIN ?? 'http://127.0.0.1:6065';
|
|
await fs.mkdir(output, { recursive: true });
|
|
const browser = await launchChromium({ headless: true });
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 1100 } });
|
|
page.setDefaultTimeout(30000);
|
|
const errors = [], measurements = [], checks = [];
|
|
page.on('pageerror', error => errors.push(error.message));
|
|
page.on('console', message => { if (message.type() === 'error') errors.push(message.text()); });
|
|
const settle = () => page.evaluate(async () => { await document.fonts.ready; for (let i = 0; i < 12; i++) await new Promise(requestAnimationFrame); });
|
|
async function ready(count) {
|
|
await page.waitForFunction(count => {
|
|
const shadow = document.querySelector('.hm-orgchart-host')?.shadowRoot;
|
|
return shadow && !shadow.querySelector('[aria-busy="true"]') && [...shadow.querySelectorAll('link[rel="stylesheet"]')].every(link => link.sheet)
|
|
&& (count === undefined || shadow.querySelectorAll('.hm-orgchart-employee').length === count);
|
|
}, count, { timeout: 60000 });
|
|
await settle();
|
|
}
|
|
async function measure(name) {
|
|
const value = await page.evaluate(() => {
|
|
const shadow = document.querySelector('.hm-orgchart-host').shadowRoot;
|
|
const viewport = shadow.querySelector('.hm-orgchart-viewport'), forest = shadow.querySelector('.hm-orgchart-forest');
|
|
const rect = element => { const r = element.getBoundingClientRect(); return { left:r.left,top:r.top,width:r.width,height:r.height,right:r.right,bottom:r.bottom }; };
|
|
const union = elements => {
|
|
const boxes = [...elements].filter(element => element.getClientRects().length && getComputedStyle(element).visibility !== 'hidden').map(rect).filter(r => r.width > 0 && r.height > 0);
|
|
if (!boxes.length) return null;
|
|
const left = Math.min(...boxes.map(r => r.left)), right = Math.max(...boxes.map(r => r.right));
|
|
const top = Math.min(...boxes.map(r => r.top)), bottom = Math.max(...boxes.map(r => r.bottom));
|
|
return { left,right,top,bottom,width:right-left,height:bottom-top };
|
|
};
|
|
const v = rect(viewport), f = rect(forest);
|
|
const content = { left:v.left+viewport.clientLeft,top:v.top+viewport.clientTop,width:viewport.clientWidth,height:viewport.clientHeight };
|
|
const cx = content.left + content.width/2, cy = content.top + content.height/2;
|
|
const trees = union(shadow.querySelectorAll('.hm-orgchart-tree'));
|
|
const visible = union(shadow.querySelectorAll('.hm-orgchart-organization'));
|
|
const error = bounds => bounds ? { x:(bounds.left+bounds.right)/2-cx,y:(bounds.top+bounds.bottom)/2-cy } : null;
|
|
return { wrapper:rect(shadow.querySelector('.hm-orgchart-expanded')),viewport:v,content,contentCenter:{x:cx,y:cy},forest:f,trees,visible,
|
|
error:{forest:error(f),trees:error(trees),visible:error(visible)},scale:Number(viewport.dataset.scale),x:Number(viewport.dataset.panX),y:Number(viewport.dataset.panY),
|
|
unscaledForest:{width:forest.offsetWidth,height:forest.offsetHeight},employees:shadow.querySelectorAll('.hm-orgchart-employee').length,
|
|
companies:shadow.querySelectorAll('.node[data-parent-id=""]').length,layout:shadow.querySelector('.hm-orgchart-expanded').dataset.autoLayout };
|
|
});
|
|
measurements.push({ name, ...value });
|
|
await fs.writeFile(path.join(output, `${phase}-progress.json`), JSON.stringify({ measurements,checks,errors }, null, 2));
|
|
return value;
|
|
}
|
|
const check = (pass, name, detail) => { checks.push({name,status:pass?'PASS':'FAIL',detail}); if (!pass) console.log('FAIL',name,JSON.stringify(detail)); };
|
|
const centered = (s, name, vertical = false) => check(Math.abs(s.error.visible.x) < 0.75 && (!vertical || Math.abs(s.error.visible.y) < 0.75), name, s.error);
|
|
// Expanded is preview-only (2026-10-22): company/count/layout combinations are no longer
|
|
// reachable from the Story, so only the fixed 7x20 TB state, Fit, Pan and widths are measured.
|
|
async function open() {
|
|
await page.goto(`${origin}/iframe.html?id=orgchart--expanded-org-chart&viewMode=story`);
|
|
await ready(140);
|
|
}
|
|
async function fit() { await page.getByRole('button',{name:'Fit to View',exact:true}).click(); await settle(); }
|
|
try {
|
|
await open(); const initial = await measure('initial-7x20-tb');
|
|
await page.screenshot({path:path.join(output,`${phase}-initial.png`),fullPage:true});
|
|
if (phase === 'after') { centered(initial,'Initial 7-company forest horizontally centered'); check(initial.scale === 1,'Initial oversized forest keeps 100% scale',initial.scale); }
|
|
await fit(); const fitted = await measure('initial-7x20-tb-fit');
|
|
if (phase === 'after') {
|
|
centered(fitted,'7x20/tb: Fit center both axes',true);
|
|
check(fitted.visible.left >= fitted.content.left+15 && fitted.visible.right <= fitted.content.left+fitted.content.width-15
|
|
&& fitted.visible.top >= fitted.content.top+15 && fitted.visible.bottom <= fitted.content.top+fitted.content.height-15,'7x20/tb: Fit contains visible tree',fitted.visible);
|
|
}
|
|
if (phase === 'after') {
|
|
// User Pan preservation across resize.
|
|
const viewport = page.locator('.hm-orgchart-viewport'); await viewport.scrollIntoViewIfNeeded(); await viewport.focus();
|
|
await page.keyboard.press('ArrowLeft'); await page.keyboard.press('ArrowUp'); await settle();
|
|
const panned = await measure('user-pan');
|
|
await page.setViewportSize({width:1280,height:1100}); await ready(); const resized = await measure('pan-resize');
|
|
check(resized.x===panned.x && resized.y===panned.y && resized.scale===panned.scale,'Manual Pan retained through resize',{before:panned,after:resized});
|
|
await fit(); centered(await measure('fit-after-pan'),'Explicit Fit restores center after user Pan',true);
|
|
for (const width of [390,768,1280,1440]) {
|
|
await page.setViewportSize({width,height:1100}); await open();
|
|
centered(await measure(`width-${width}-initial`),`${width}px automatic horizontal center`);
|
|
await fit(); centered(await measure(`width-${width}-fit`),`${width}px Fit center`,true);
|
|
}
|
|
await page.setViewportSize({width:1440,height:1100}); await open();
|
|
await fit(); await page.screenshot({path:path.join(output,'after-fit.png'),fullPage:true});
|
|
}
|
|
} catch(error) { check(false,'Measurement completed',{message:error.message,stack:error.stack}); }
|
|
check(errors.length===0,'Browser console/page errors zero',errors);
|
|
const result = {phase,origin,browser:browser.version(),measurements,checks,errors,pass:checks.filter(c=>c.status==='PASS').length,fail:checks.filter(c=>c.status==='FAIL').length};
|
|
await fs.writeFile(path.join(output,`${phase}.json`),JSON.stringify(result,null,2));
|
|
console.log(JSON.stringify({phase,measurements:measurements.length,pass:result.pass,fail:result.fail,initial:measurements[0],output:path.join(output,`${phase}.json`)}));
|
|
await browser.close(); if(result.fail) process.exitCode=1;
|