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).
110 lines
8.7 KiB
JavaScript
110 lines
8.7 KiB
JavaScript
// Focused P9.5 transform geometry probes on the preview-only Expanded Story; no P9.4 replay.
|
||
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 origin = (process.env.ORGCHART_STORY_ORIGIN ?? 'http://127.0.0.1:6064').replace(/\/$/, '');
|
||
const startedAt = new Date().toISOString(), checks = [], measurements = [], errors = [];
|
||
await fs.mkdir(output, { recursive: true });
|
||
const browser = await launchChromium({ headless: true });
|
||
const page = await browser.newPage({ viewport: { width: 1440, height: 1700 } });
|
||
page.setDefaultTimeout(30000);
|
||
page.on('pageerror', error => errors.push({ type: 'pageerror', message: error.message }));
|
||
page.on('console', message => { if (message.type() === 'error') errors.push({ type: 'console', message: message.text() }); });
|
||
const check = (pass, name, detail) => { checks.push({ name, status: pass ? 'PASS' : 'FAIL', detail }); fs.writeFile(path.join(output, 'geometry-progress.json'), JSON.stringify({ startedAt, checks, measurements, errors }, null, 2)).catch(() => {}); if (!pass) console.log('FAIL', name, JSON.stringify(detail)); };
|
||
const settle = () => page.evaluate(async () => { await document.fonts.ready; for (let i = 0; i < 10; 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();
|
||
}
|
||
// Expanded is preview-only (2026-10-22): the fixed 7 companies x 20 TB state is measured;
|
||
// company filtering, layout switching and Drag & Drop are no longer reachable from the Story.
|
||
async function setup() {
|
||
await page.goto(`${origin}/iframe.html?id=orgchart--expanded-org-chart&viewMode=story`);
|
||
await ready(140);
|
||
await page.locator('.hm-orgchart-viewport').scrollIntoViewIfNeeded(); await settle();
|
||
}
|
||
async function snapshot(name) {
|
||
const measurement = 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 scale = Number(viewport.dataset.scale), origin = forest.getBoundingClientRect();
|
||
const localPoint = p => ({ x: (p.x - origin.left) / scale, y: (p.y - origin.top) / scale });
|
||
const localRect = element => { const r = element.getBoundingClientRect(); return { x: (r.left - origin.left) / scale, y: (r.top - origin.top) / scale, width: r.width / scale, height: r.height / scale }; };
|
||
const endPoint = p => localPoint(p.getPointAtLength(p.getTotalLength()).matrixTransform(p.getScreenCTM()));
|
||
const employeePaths = [...shadow.querySelectorAll('.hm-orgchart-employee-connectors path')].map(p => {
|
||
const article = p.closest('.hm-orgchart-organization');
|
||
const card = [...article.querySelectorAll('.hm-orgchart-employee')].find(card => card.dataset.employeeId === p.dataset.employeeId);
|
||
const box = localRect(card), direction = p.dataset.direction;
|
||
const expected = direction === 'left' ? { x: box.x, y: box.y + box.height / 2 }
|
||
: direction === 'right' ? { x: box.x + box.width, y: box.y + box.height / 2 } : { x: box.x + box.width / 2, y: box.y };
|
||
const endpoint = endPoint(p);
|
||
return { id: p.dataset.employeeId, direction, d: p.getAttribute('d'), endpoint, expected, error: Math.hypot(endpoint.x - expected.x, endpoint.y - expected.y) };
|
||
});
|
||
const organizationPaths = [...shadow.querySelectorAll('.hm-orgchart-organization-connectors path')].map(p => {
|
||
const tree = p.closest('.hm-orgchart-tree');
|
||
const node = [...tree.querySelectorAll('.node')].find(node => node.dataset.organizationId === p.dataset.organizationId);
|
||
const heading = localRect(node.querySelector('.hm-orgchart-organization-name'));
|
||
const expected = p.dataset.direction === 'left' ? { x: heading.x, y: heading.y + heading.height / 2 } : { x: heading.x + heading.width / 2, y: heading.y };
|
||
const endpoint = endPoint(p);
|
||
return { id: p.dataset.organizationId, parentId: p.dataset.parentId, direction: p.dataset.direction, d: p.getAttribute('d'), endpoint, expected, error: Math.hypot(endpoint.x - expected.x, endpoint.y - expected.y) };
|
||
});
|
||
const cards = [...shadow.querySelectorAll('.hm-orgchart-employee')].map(card => ({ id: card.dataset.employeeId, organizationId: card.closest('.node').dataset.organizationId, ...localRect(card) }));
|
||
return { layout: shadow.querySelector('.hm-orgchart-expanded').dataset.autoLayout, scale, x: Number(viewport.dataset.panX), y: Number(viewport.dataset.panY), cards, employeePaths, organizationPaths, organizationConnectorPresentation: organizationPaths.length ? 'measured-svg' : 'existing-TB-pseudo-elements' };
|
||
});
|
||
measurements.push({ name, ...measurement }); return measurement;
|
||
}
|
||
function validateEndpoints(s, label) {
|
||
const paths = [...s.employeePaths, ...s.organizationPaths];
|
||
check(s.cards.length === 140 && s.employeePaths.length === 140, `${label}: all 140 cards retain employee connectors`, { cards: s.cards.length, employeePaths: s.employeePaths.length });
|
||
check(paths.every(p => p.error < 0.75), `${label}: connector endpoints reach intended targets`, { paths: paths.length, organizationPaths: s.organizationPaths.length, maxLocalError: Math.max(0, ...paths.map(p => p.error)), toleranceLocalPx: 0.75 });
|
||
if (s.layout !== 'tb') check(s.organizationPaths.length > 0, `${label}: organization routes are measured`, s.organizationPaths.length);
|
||
}
|
||
function compareGeometry(before, after, label) {
|
||
let maxDelta = 0;
|
||
const cardKeys = ['x', 'y', 'width', 'height'];
|
||
for (const a of before.cards) {
|
||
const b = after.cards.find(b => b.id === a.id);
|
||
if (!b) { maxDelta = Infinity; break; }
|
||
for (const key of cardKeys) maxDelta = Math.max(maxDelta, Math.abs(a[key] - b[key]));
|
||
}
|
||
for (const key of ['employeePaths', 'organizationPaths']) for (const a of before[key]) {
|
||
const b = after[key].find(b => b.id === a.id);
|
||
if (!b || a.d !== b.d) { maxDelta = Infinity; break; }
|
||
maxDelta = Math.max(maxDelta, Math.abs(a.endpoint.x - b.endpoint.x), Math.abs(a.endpoint.y - b.endpoint.y));
|
||
}
|
||
check(maxDelta < 0.1, `${label}: forest-local card/connector geometry is unchanged`, { maxLocalDelta: maxDelta, beforeScale: before.scale, afterScale: after.scale });
|
||
}
|
||
async function pan() {
|
||
await page.locator('.hm-orgchart-viewport').focus();
|
||
await page.keyboard.press('ArrowLeft'); await page.keyboard.press('ArrowUp'); await settle();
|
||
}
|
||
try {
|
||
const type = 'tb';
|
||
try {
|
||
await setup();
|
||
const centered = await snapshot(`${type}-centered`); validateEndpoints(centered, `${type} centered`);
|
||
await pan(); const panned = await snapshot(`${type}-panned`); compareGeometry(centered, panned, `${type} Pan`); validateEndpoints(panned, `${type} panned`);
|
||
await page.getByRole('button', { name: '확대', exact: true }).click(); await settle();
|
||
const zoomed = await snapshot(`${type}-zoomed`); compareGeometry(centered, zoomed, `${type} Zoom`); validateEndpoints(zoomed, `${type} zoomed`);
|
||
await page.getByRole('button', { name: 'Fit to View', exact: true }).click(); await settle();
|
||
const fitted = await snapshot(`${type}-fitted`); compareGeometry(centered, fitted, `${type} Fit`); validateEndpoints(fitted, `${type} fitted`);
|
||
await page.screenshot({ path: path.join(output, 'geometry-tb.png'), fullPage: true });
|
||
} catch (error) {
|
||
check(false, `${type}: focused geometry section completed`, { message: error.message, stack: error.stack });
|
||
}
|
||
} finally {
|
||
check(errors.length === 0, 'Focused geometry browser console/page errors zero', errors);
|
||
const result = { startedAt, completedAt: new Date().toISOString(), origin, browser: browser.version(), scope: 'P9.5 preview-only Expanded: 7 companies × 20 staff; TB Pan/Zoom/Fit transforms; existing TB organization pseudo-elements unchanged', 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, 'geometry.json'), JSON.stringify(result, null, 2));
|
||
console.log(JSON.stringify({ pass: result.pass, fail: result.fail, measurements: measurements.length, output: path.join(output, 'geometry.json') }));
|
||
await browser.close(); if (result.fail) process.exitCode = 1;
|
||
}
|