feat(org-chart): implement expandable layout and multi-company navigation
Integrate P5-1B through P5-4 shared Core layout, collapse, zoom/pan/fit, company roots and keyboard accessibility. Include React/HTML examples, virtual 20/100/300 employee fixtures, focused checks and the additive data contract.
This commit is contained in:
1 parent
a92913d6bc
commit
064517ec46
30 files changed
+7532
-183
No files matched your search
@@ -0,0 +1,45 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { validateOrgChartDataset, normalizeOrgChartDataset } from '../data/index.js';
|
||||
import { resolveExpandedCompanies, validateTopNodes } from './expanded-company.js';
|
||||
import { createOrgChart } from './index.js';
|
||||
|
||||
test('fixed JSON fixtures have exact unique counts, valid ancestry and company memberships', () => {
|
||||
for (const [size, count, companies, depth] of [['small', 20, 3, 2], ['medium', 100, 5, 3], ['large', 300, 7, 4]] as const) {
|
||||
const input = JSON.parse(readFileSync(`examples/org-chart/data-expanded-${size}.json`, 'utf8'));
|
||||
const validation = validateOrgChartDataset(input);
|
||||
assert.equal(validation.valid, true, JSON.stringify(validation));
|
||||
if (!validation.valid) continue;
|
||||
const data = validation.data, normalized = normalizeOrgChartDataset(data);
|
||||
const resolved = resolveExpandedCompanies(data, normalized.roots, 'hanmac');
|
||||
assert.equal(data.employees.length, count);
|
||||
assert.equal(new Set(data.employees.map(employee => employee.id)).size, count);
|
||||
assert.equal(resolved.companyIds.length, companies);
|
||||
assert.equal(resolved.issues.length, 0);
|
||||
for (const employee of data.employees) assert.equal(resolved.companies.get(employee.organizationId), employee.affiliationId);
|
||||
let maxDepth = 0;
|
||||
const visit = (nodes: typeof normalized.roots, level: number) => { maxDepth = Math.max(maxDepth, level); nodes.forEach(node => { if (node.children.length) visit(node.children, level + 1); }); };
|
||||
visit(normalized.roots, 0); assert.equal(maxDepth, depth);
|
||||
assert.equal(validateTopNodes(data, normalized.roots, resolved.companies, { hanmac: { title: '회장', display: 'card' } }).length, 0);
|
||||
assert.ok(validateTopNodes(data, normalized.roots, resolved.companies, { hanmac: { employeeId: 'missing' } }).length);
|
||||
assert.ok(validateTopNodes(data, normalized.roots, resolved.companies, { hanmac: { profileImage: 'javascript:alert(1)' } }).length);
|
||||
}
|
||||
});
|
||||
|
||||
test('inherited identity stays stable; cross-company ancestry fails instead of mixing companies', () => {
|
||||
const data = JSON.parse(readFileSync('examples/org-chart/data-expanded-small.json', 'utf8'));
|
||||
delete data.organizations[1].companyId;
|
||||
let roots = normalizeOrgChartDataset(data).roots;
|
||||
assert.equal(resolveExpandedCompanies(data, roots, 'saman').companies.get(data.organizations[1].id), 'hanmac');
|
||||
data.organizations[1].companyId = 'saman'; roots = normalizeOrgChartDataset(data).roots;
|
||||
assert.equal(resolveExpandedCompanies(data, roots, 'hanmac').issues[0]?.code, 'COMPANY_HIERARCHY');
|
||||
});
|
||||
|
||||
test('invalid Auto view configuration fails early, leaving Fixed untouched', () => {
|
||||
const options = { dataSource: { load: async () => ({}) }, stylesheetUrls: { engine: '/engine.css', core: '/style.css' } };
|
||||
assert.throws(() => createOrgChart({ ...options, layoutMode: 'auto', selectedCompanyIds: ['unknown'] }), /selectedCompanyIds/);
|
||||
assert.throws(() => createOrgChart({ ...options, layoutMode: 'auto', zoom: { min: 2 } }), /zoom/);
|
||||
const fixed = createOrgChart({ ...options, layoutMode: 'fixed', selectedCompanyIds: ['unknown'], companyTopNodes: { unknown: { title: 'legacy' } } });
|
||||
assert.equal(fixed.getViewportState(), undefined); fixed.destroy();
|
||||
});
|
||||
Reference in new issue
Block a user