docs(storybook): make Expanded OrgChart a preview-only page
Deploy Storybook / deploy (push) Successful in 59s
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).
This commit is contained in:
1 parent
4a54cfbeb2
commit
f4ddd5ac4b
14 files changed
+163
-1134
No files matched your search
@@ -1,4 +1,4 @@
|
||||
// Actual Expanded Storybook UI: seven companies x three sizes x three layouts.
|
||||
// Actual Expanded Storybook UI: the preview-only fixed state (7 companies x 20, TB).
|
||||
// Build Storybook first; uses the existing optional Playwright/Chrome runtime.
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
@@ -23,10 +23,7 @@ const server = http.createServer(async (request, response) => {
|
||||
});
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
||||
let browser, failure;
|
||||
// Resume only complete company groups after a test harness interruption.
|
||||
const savedChecks = process.argv.includes('--resume')
|
||||
? JSON.parse(await fs.readFile(path.join(output, 'browser-progress.json'), 'utf8')).checks : [];
|
||||
const checks = savedChecks.filter(check => savedChecks.filter(row => row.company === check.company).length === 9);
|
||||
const checks = [];
|
||||
const errors = [];
|
||||
try {
|
||||
browser = await launchChromium({ headless: true });
|
||||
@@ -46,10 +43,6 @@ try {
|
||||
}, count);
|
||||
await settle();
|
||||
};
|
||||
const select = async (label, option) => {
|
||||
await page.getByRole('combobox', { name: label, exact: true }).click();
|
||||
await page.getByRole('option', { name: option, exact: true }).click();
|
||||
};
|
||||
const snapshot = () => page.locator('.hm-orgchart-host').evaluate(host => {
|
||||
const shadow = host.shadowRoot;
|
||||
const box = element => { const r = element.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height }; };
|
||||
@@ -77,64 +70,44 @@ try {
|
||||
const { viewport: v, contentBounds: f } = await snapshot();
|
||||
assert.ok(f.x >= v.x - 1 && f.y >= v.y - 1 && f.x + f.width <= v.x + v.width + 1 && f.y + f.height <= v.y + v.height + 1, `complete forest fits viewport: ${JSON.stringify({ viewport: v, forest: f })}`);
|
||||
};
|
||||
// Preview-only Expanded Story (2026-10-22): one fixed state, 7 companies x 20 employees in TB.
|
||||
const count = companies.length * 20;
|
||||
await page.goto(`http://127.0.0.1:${server.address().port}/iframe.html?id=orgchart--expanded-org-chart&viewMode=story`);
|
||||
await ready(140);
|
||||
let previousCompany;
|
||||
await ready(count);
|
||||
const companyOf = new Map(template.organizations.map(org => [org.id, org.companyId]));
|
||||
const rendered = await snapshot();
|
||||
assert.equal(rendered.layout, 'tb');
|
||||
assert.deepEqual(rendered.nodes.sort((a, b) => a.id.localeCompare(b.id)),
|
||||
template.organizations.map(org => ({ id: org.id, parentId: org.parentId, type: org.type })).sort((a, b) => a.id.localeCompare(b.id)));
|
||||
const byId = new Map(rendered.nodes.map(org => [org.id, org]));
|
||||
for (const team of rendered.nodes.filter(org => org.type === 'team')) {
|
||||
let ancestor = byId.get(team.parentId);
|
||||
while (ancestor) { assert.notEqual(ancestor.type, 'cell'); ancestor = byId.get(ancestor.parentId); }
|
||||
}
|
||||
const employees = rendered.employees.sort((a, b) => a.id.localeCompare(b.id));
|
||||
assert.equal(new Set(employees.map(employee => employee.id)).size, count);
|
||||
assert.ok(employees.every(employee => byId.has(employee.organizationId)));
|
||||
assert.ok(rendered.upstreamConnectors > 0, 'TB engine CSS connectors are present');
|
||||
await fit();
|
||||
await page.screenshot({ path: path.join(output, 'storybook-tb-140.png'), fullPage: true });
|
||||
for (const company of companies) {
|
||||
if (checks.filter(check => check.company === company.id).length === 9) continue;
|
||||
await page.getByRole('button', { name: /회사 선택/ }).click();
|
||||
await page.getByRole('checkbox', { name: previousCompany?.name ?? '전체 선택', exact: true }).uncheck();
|
||||
await page.getByRole('checkbox', { name: company.name, exact: true }).check();
|
||||
await page.keyboard.press('Escape');
|
||||
previousCompany = company;
|
||||
for (const count of [20, 100, 300]) {
|
||||
await select('회사당 직원 수', `${count}명`);
|
||||
await page.getByRole('button', { name: '데이터 적용', exact: true }).click();
|
||||
await ready(count);
|
||||
const expected = template.organizations.filter(org => org.companyId === company.id).map(org => ({ id: org.id, parentId: org.parentId, type: org.type })).sort((a, b) => a.id.localeCompare(b.id));
|
||||
let employees;
|
||||
for (const [type, label] of [['tb', '세로형 TB'], ['lr', '가로형 LR'], ['hybrid', '혼합형 Hybrid']]) {
|
||||
await select('조직도 표시 유형', label);
|
||||
await ready(count);
|
||||
const rendered = await snapshot();
|
||||
assert.equal(rendered.layout, type);
|
||||
assert.deepEqual(rendered.nodes.sort((a, b) => a.id.localeCompare(b.id)), expected);
|
||||
const byId = new Map(rendered.nodes.map(org => [org.id, org]));
|
||||
for (const team of rendered.nodes.filter(org => org.type === 'team')) {
|
||||
let ancestor = byId.get(team.parentId);
|
||||
while (ancestor) { assert.notEqual(ancestor.type, 'cell'); ancestor = byId.get(ancestor.parentId); }
|
||||
}
|
||||
const roster = rendered.employees.sort((a, b) => a.id.localeCompare(b.id));
|
||||
assert.equal(new Set(roster.map(employee => employee.id)).size, count);
|
||||
assert.ok(roster.every(employee => byId.has(employee.organizationId)));
|
||||
if (employees) assert.deepEqual(roster, employees); else employees = roster;
|
||||
if (type === 'tb') assert.ok(rendered.upstreamConnectors > 0, 'TB engine CSS connectors are present');
|
||||
else {
|
||||
assert.equal(rendered.paths.length, expected.filter(org => org.parentId).length);
|
||||
assert.ok(rendered.paths.every(p => p.d && !/NaN|Infinity/.test(p.d) && byId.get(p.id)?.parentId === p.parentId), 'measured connectors follow parent IDs and have finite geometry');
|
||||
}
|
||||
await fit();
|
||||
const teamId = `${company.id}-team-0`, cellId = `${company.id}-cell-0`;
|
||||
const toggle = page.locator(`.hm-orgchart-expanded-toggle[data-organization-id="${teamId}"]`);
|
||||
await toggle.focus(); await toggle.press('Enter'); await ready();
|
||||
assert.equal(await page.locator(`.node[data-organization-id="${cellId}"]`).count(), 0);
|
||||
assert.ok((await snapshot()).employees.length < count);
|
||||
await fit();
|
||||
await toggle.focus(); await toggle.press('Enter'); await ready(count);
|
||||
assert.equal(await page.locator(`.node[data-organization-id="${cellId}"]`).count(), 1);
|
||||
assert.deepEqual((await snapshot()).employees.sort((a, b) => a.id.localeCompare(b.id)), employees);
|
||||
await fit();
|
||||
checks.push({ company: company.id, employeeCount: count, layout: type, hierarchy: 'PASS', employeeIdentity: 'PASS', connectors: 'PASS', collapseExpand: 'PASS', fit: 'PASS' });
|
||||
await fs.writeFile(path.join(output, 'browser-progress.json'), JSON.stringify({ checks, errors }, null, 2));
|
||||
if (company.id === 'hanmac' && count === 300) await page.screenshot({ path: path.join(output, `storybook-${type}-300.png`), fullPage: true });
|
||||
}
|
||||
}
|
||||
console.log(`${company.name}: 9 combinations PASS`);
|
||||
assert.equal(employees.filter(employee => companyOf.get(employee.organizationId) === company.id).length, 20, `${company.id}: 20 employees`);
|
||||
const teamId = `${company.id}-team-0`, cellId = `${company.id}-cell-0`;
|
||||
const toggle = page.locator(`.hm-orgchart-expanded-toggle[data-organization-id="${teamId}"]`);
|
||||
await toggle.focus(); await toggle.press('Enter'); await ready();
|
||||
assert.equal(await page.locator(`.node[data-organization-id="${cellId}"]`).count(), 0);
|
||||
assert.ok((await snapshot()).employees.length < count);
|
||||
await fit();
|
||||
await toggle.focus(); await toggle.press('Enter'); await ready(count);
|
||||
assert.equal(await page.locator(`.node[data-organization-id="${cellId}"]`).count(), 1);
|
||||
assert.deepEqual((await snapshot()).employees.sort((a, b) => a.id.localeCompare(b.id)), employees);
|
||||
await fit();
|
||||
checks.push({ company: company.id, employeeCount: 20, layout: 'tb', hierarchy: 'PASS', employeeIdentity: 'PASS', connectors: 'PASS', collapseExpand: 'PASS', fit: 'PASS' });
|
||||
await fs.writeFile(path.join(output, 'browser-progress.json'), JSON.stringify({ checks, errors }, null, 2));
|
||||
}
|
||||
assert.deepEqual(errors, []);
|
||||
assert.equal(await page.getByRole('alert').count(), 0);
|
||||
assert.equal(checks.length, companies.length * 9);
|
||||
assert.equal(new Set(checks.map(check => `${check.company}/${check.employeeCount}/${check.layout}`)).size, companies.length * 9);
|
||||
assert.equal(checks.length, companies.length);
|
||||
} catch (error) {
|
||||
failure = error.stack;
|
||||
throw error;
|
||||
@@ -143,4 +116,4 @@ try {
|
||||
await browser?.close();
|
||||
await new Promise(resolve => server.close(resolve));
|
||||
}
|
||||
console.log(`Expanded Storybook: ${checks.length}/63 combinations PASS; ${errors.length} browser errors`);
|
||||
console.log(`Expanded Storybook: ${checks.length}/${companies.length} companies PASS; ${errors.length} browser errors`);
|
||||
Reference in new issue
Block a user