Files
HMDesign/examples/org-chart/verify-p9-2.mjs
T
shkim4 f4ddd5ac4b
Deploy Storybook / deploy (push) Successful in 59s
docs(storybook): make Expanded OrgChart a preview-only page
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).
2026-10-22 17:51:21 +09:00

102 lines
9.6 KiB
JavaScript

// P9.2 Expanded integration only. Historical suites are preserved.
// Expanded is preview-only (2026-10-22): company selection, data scope/count and Apply/Reset
// sections were removed with the Story settings; chart-common checks run on the fixed 7x20 TB state.
import fs from 'node:fs/promises';
import { writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { launchChromium } from '../../scripts/orgchart-browser-runtime.mjs';
import { companyColorPresets } from '../../packages/org-chart/dist/index.js';
const root = fileURLToPath(new URL('../../', import.meta.url));
const output = path.join(root, 'test-results/org-chart-p9-2');
const origin = process.env.ORGCHART_STORY_ORIGIN ?? 'http://127.0.0.1:6062';
const only = process.argv.slice(2), run = name => !only.length || only.includes(name);
const suffix = only.length ? '-' + only.join('-') : '';
const ids = Object.values(companyColorPresets).map(company => company.companyId), total = ids.length * 20;
const results = [], measurements = [], errors = [], startedAt = new Date().toISOString();
await fs.mkdir(output, { recursive: true });
const persist = () => writeFileSync(path.join(output, `expanded-progress${suffix}.json`), JSON.stringify({ startedAt, results, measurements, errors }, null, 2));
const check = (pass, name, detail) => { results.push({ name, status: pass ? 'PASS' : 'FAIL', ...(detail === undefined ? {} : { detail }) }); persist(); if (!pass) console.log('FAIL', name, JSON.stringify(detail)); };
const browser = await launchChromium({ headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 1100 } });
page.setDefaultTimeout(15000);
page.on('pageerror', error => { errors.push({ type: 'pageerror', message: error.message }); persist(); });
page.on('console', message => { if (message.type() === 'error') { errors.push({ type: 'console', message: message.text(), location: message.location() }); persist(); } });
const settle = () => page.evaluate(async () => { await document.fonts.ready; for (let i = 0; i < 8; 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);
await settle();
}
async function goto(size = { width: 1440, height: 1100 }) {
await page.setViewportSize(size);
await page.goto(`${origin}/iframe.html?id=orgchart--expanded-org-chart&viewMode=story`);
await ready(total);
}
async function state() {
return page.evaluate(() => {
const shadow = document.querySelector('.hm-orgchart-host').shadowRoot, expanded = shadow.querySelector('.hm-orgchart-expanded');
const viewport = shadow.querySelector('.hm-orgchart-viewport'), forest = shadow.querySelector('.hm-orgchart-forest');
const rect = element => { if (!element) return null; const r = element.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height, right: r.right, bottom: r.bottom }; };
return { employees: shadow.querySelectorAll('.hm-orgchart-employee').length,
roots: [...shadow.querySelectorAll('.hm-orgchart-company-tree')].map(element => element.dataset.companyId), type: expanded?.dataset.autoLayout,
empty: forest?.children.length === 0, scale: Number(viewport?.dataset.scale), panX: Number(viewport?.dataset.panX), panY: Number(viewport?.dataset.panY), viewport: rect(viewport), forest: rect(forest),
documentWidth: document.documentElement.scrollWidth, width: innerWidth,
cards: [...shadow.querySelectorAll('.hm-orgchart-employee')].map(element => ({ id: element.dataset.employeeId, companyId: element.dataset.companyId, direction: element.dataset.connectionDirection })),
titles: [...shadow.querySelectorAll('.node[data-company-id] .hm-orgchart-organization-name')].map(element => ({ companyId: element.closest('.node').dataset.companyId, color: getComputedStyle(element).color })),
autoControlsInFixed: Boolean(shadow.querySelector('.hm-orgchart-stage .hm-orgchart-expanded-toggle')) };
});
}
async function snap(name, fullPage = false) { await page.screenshot({ path: path.join(output, `${name}.png`), fullPage }); }
async function section(name, action) { if (!run(name)) return; try { await action(); } catch (error) { check(false, `${name}: execution completed`, { message: error.message, stack: error.stack }); await snap(`failure-${name}`, true).catch(() => {}); } }
await section('modes', async () => {
await goto(); await snap('expanded-preview', true);
const s = await state(); measurements.push({ name: 'tb', state: s });
check(s.type === 'tb' && s.employees === total && s.cards.every(card => ids.includes(card.companyId)) && new Set(s.cards.map(card => card.companyId)).size === ids.length, 'tb: fixed seven-company 140 employee preview renders');
check(s.titles.length > 0 && s.titles.every(title => title.color === 'rgb(255, 255, 255)') && s.cards.every(card => ['top', 'left', 'right'].includes(card.direction)), 'tb: existing white titles and directional cards');
check(await page.locator('.hm-orgchart-story-expanded fieldset, .hm-orgchart-story-expanded [role="combobox"], .hm-orgchart-story-expanded [role="switch"]').count() === 0, 'Preview-only: no setting controls outside the chart');
// The 140 employee forest opens at 100% wider than the viewport; Fit brings the first group into view.
await page.getByRole('button', { name: 'Fit to View', exact: true }).click(); await settle();
const toggle = page.locator('.hm-orgchart-expanded-toggle:not(:disabled)').first();
const id = await toggle.getAttribute('data-organization-id'); await toggle.click(); await ready();
const current = () => page.locator(`.hm-orgchart-expanded-toggle[data-organization-id="${id}"]`);
check(await current().getAttribute('aria-expanded') === 'false', 'Group collapse'); await current().press('Space'); await ready(); check(await current().getAttribute('aria-expanded') === 'true', 'Group expands with Space');
const employee = page.locator('.hm-orgchart-employee').first(), employeeId = await employee.getAttribute('data-employee-id'); await employee.click();
const dialog = page.locator('[data-component="HMOrgChartEmployeeDialog"]'); await dialog.waitFor();
check(await dialog.getAttribute('data-employee-id') === employeeId && await page.getByRole('dialog').count() === 1, 'Employee HM Dialog identity');
await page.keyboard.press('Escape'); await dialog.waitFor({ state: 'hidden' }); await settle();
check(await page.evaluate(() => document.querySelector('.hm-orgchart-host').shadowRoot.activeElement?.dataset.employeeId) === employeeId, 'Employee Dialog ESC returns card focus');
await page.getByRole('button', { name: 'Fit to View', exact: true }).click(); await settle(); const before = await state();
await page.getByRole('button', { name: '확대', exact: true }).click(); await settle(); const zoom = await state(); check(zoom.scale > before.scale, 'Zoom remains functional');
const viewport = page.locator('.hm-orgchart-viewport'); await viewport.scrollIntoViewIfNeeded(); const box = await viewport.boundingBox(); const panBefore = await state();
await page.mouse.move(box.x + 8, box.y + box.height - 8); await page.mouse.down(); await page.mouse.move(box.x + 58, box.y + box.height - 38, { steps: 5 }); await page.mouse.up(); await settle(); const pan = await state();
check(Math.abs(pan.panX - panBefore.panX) >= 40 && Math.abs(pan.panY - panBefore.panY) >= 20, 'Pointer Pan remains functional', { before: [panBefore.panX, panBefore.panY], after: [pan.panX, pan.panY] });
await page.getByRole('button', { name: 'Fit to View', exact: true }).click(); await settle(); const fit = await state();
check(fit.forest.x >= fit.viewport.x - 1 && fit.forest.right <= fit.viewport.right + 1 && fit.forest.y >= fit.viewport.y - 1 && fit.forest.bottom <= fit.viewport.bottom + 1, 'Fit contains complete forest', fit);
});
await section('responsive', async () => {
for (const size of [{ width: 1920, height: 1080 }, { width: 768, height: 1024 }, { width: 390, height: 844 }]) {
await goto(size);
check((await state()).documentWidth <= size.width + 1, `${size.width}: no horizontal page overflow`);
await snap(`expanded-${size.width}`, true);
}
});
await section('menu', async () => {
const index = await (await page.request.get(`${origin}/index.json`)).json();
const entries = Object.values(index.entries).filter(entry => entry.type === 'story' && entry.title.startsWith('OrgChart/') && !(entry.tags ?? []).includes('internal'));
const expected = ['orgchart--logo-personnel-summary', 'orgchart--department-colors', 'orgchart--full-composition', 'orgchart--expanded-org-chart'];
check(entries.length === 4 && entries.every(entry => expected.includes(entry.id)), 'Existing four public Story IDs retained', entries.map(entry => ({ id: entry.id, title: entry.title })));
});
check(errors.length === 0, 'New console/page errors: zero', errors);
const result = { startedAt, completedAt: new Date().toISOString(), browser: browser.version(), origin, sections: only, results, measurements, errors, pass: results.filter(item => item.status === 'PASS').length, fail: results.filter(item => item.status === 'FAIL').length };
await fs.writeFile(path.join(output, `browser-expanded${suffix}.json`), JSON.stringify(result, null, 2));
console.log(JSON.stringify({ pass: result.pass, fail: result.fail, errors: errors.length, output: `test-results/org-chart-p9-2/browser-expanded${suffix}.json` }));
await browser.close(); if (result.fail) process.exitCode = 1;