feat(org-chart): finalize chart features and AI generation rules
This commit is contained in:
1 parent
7614507583
commit
d0828a7ff4
80 files changed
+8152
-221
No files matched your search
@@ -0,0 +1,88 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { applyInitialVisibility, autoEngineOptions, resolveAutoLayout, resolveInteractions } from './auto-layout-policy.js';
|
||||
import { createOrgChart } from './index.js';
|
||||
import { engineTree, type RenderContext } from './render.js';
|
||||
import type { Organization, OrganizationTreeNode } from '../data/index.js';
|
||||
|
||||
const organization = (id: string, parentId: string | null): Organization => ({ id, parentId, name: id, type: parentId ? 'team' : 'root', leaderId: null, displayOrder: 0 });
|
||||
const organizations = [organization('root', null), organization('a', 'root'), organization('b', 'root'), organization('a1', 'a'), organization('a2', 'a1')];
|
||||
|
||||
test('each independent layout policy selects compatible installed engine options', () => {
|
||||
assert.deepEqual(autoEngineOptions(resolveAutoLayout()), { direction: 't2b', visibleLevel: 999 });
|
||||
assert.deepEqual(autoEngineOptions(resolveAutoLayout({ type: 'lr', initialVisibleDepth: 1 })), { direction: 'l2r', visibleLevel: 999 });
|
||||
for (const depth of [2, 3, 4] as const) assert.deepEqual(autoEngineOptions(resolveAutoLayout({ type: 'hybrid', hybridStartDepth: depth })), { direction: 't2b', visibleLevel: 999, verticalLevel: depth });
|
||||
});
|
||||
|
||||
test('collapse, Zoom and Pan have independent validated defaults', () => {
|
||||
assert.deepEqual(resolveInteractions(), { collapse: true, zoom: true, pan: true });
|
||||
assert.deepEqual(resolveInteractions({ collapse: false }), { collapse: false, zoom: true, pan: true });
|
||||
assert.deepEqual(resolveInteractions({ zoom: false }), { collapse: true, zoom: false, pan: true });
|
||||
assert.deepEqual(resolveInteractions({ pan: false }), { collapse: true, zoom: true, pan: false });
|
||||
assert.throws(() => resolveInteractions({ zoom: 0 as unknown as boolean }), /interactions.zoom must be boolean/);
|
||||
});
|
||||
|
||||
test('incompatible per-group/global/directional combinations fail explicitly', () => {
|
||||
assert.throws(() => resolveAutoLayout({ type: 'lr', hybridOrganizationIds: ['a'] }), /supported only with TB/);
|
||||
assert.throws(() => resolveAutoLayout({ type: 'hybrid', hybridOrganizationIds: ['a'] }), /supported only with TB/);
|
||||
assert.throws(() => resolveAutoLayout({ initialVisibleDepth: 0 as 1 }), /initialVisibleDepth/);
|
||||
assert.throws(() => resolveAutoLayout({ hybridStartDepth: 5 as 2 }), /hybridStartDepth/);
|
||||
assert.throws(() => resolveAutoLayout({ hybridOrganizationIds: [''] }), /nonempty/);
|
||||
const input = ['a', 'a'];
|
||||
const resolved = resolveAutoLayout({ hybridOrganizationIds: input });
|
||||
input.push('b');
|
||||
assert.deepEqual(resolved.hybridOrganizationIds, ['a']);
|
||||
assert.ok(Object.isFrozen(resolved.hybridOrganizationIds));
|
||||
});
|
||||
|
||||
test('initial depth is one-based and user expansion survives subsequent renders and new data', () => {
|
||||
const collapsed = new Set<string>(), initialized = new Set<string>();
|
||||
applyInitialVisibility(organizations, collapsed, initialized, 2);
|
||||
assert.deepEqual([...collapsed], ['a', 'b', 'a1', 'a2']);
|
||||
collapsed.delete('a');
|
||||
applyInitialVisibility([...organizations, organization('b1', 'b')], collapsed, initialized, 2);
|
||||
assert.equal(collapsed.has('a'), false);
|
||||
assert.equal(collapsed.has('b1'), true);
|
||||
applyInitialVisibility(organizations, collapsed, initialized, 'all', true);
|
||||
assert.equal(collapsed.size, 0);
|
||||
collapsed.add('a1');
|
||||
applyInitialVisibility(organizations.filter(item => item.id !== 'a2'), collapsed, initialized, 'all');
|
||||
assert.equal(collapsed.has('a1'), true);
|
||||
assert.equal(initialized.has('a2'), false);
|
||||
});
|
||||
|
||||
test('Depth 1 projection keeps independent company roots while collapsed descendants stay out of DOM', () => {
|
||||
const roots: OrganizationTreeNode[] = [{ organization: organizations[0]!, children: [{ organization: organizations[1]!, children: [{ organization: organizations[3]!, children: [] }] }] }];
|
||||
const collapsed = new Set<string>(), initialized = new Set<string>();
|
||||
applyInitialVisibility(organizations, collapsed, initialized, 1);
|
||||
const context = { orderedOrganizations: [], readOnly: true, collapsedOrganizations: collapsed, autoLayout: resolveAutoLayout() } as unknown as RenderContext;
|
||||
const tree = engineTree(roots, 'initial', context);
|
||||
assert.equal(tree.length, 1);
|
||||
assert.equal(tree[0]!.children, undefined);
|
||||
assert.deepEqual(context.orderedOrganizations.map(item => item.id), ['root']);
|
||||
});
|
||||
|
||||
test('per-group Hybrid projection marks only the selected parent and leaves source immutable', () => {
|
||||
const roots: OrganizationTreeNode[] = [{ organization: organizations[0]!, children: [{ organization: organizations[1]!, children: [{ organization: organizations[3]!, children: [] }] }, { organization: organizations[2]!, children: [] }] }];
|
||||
const before = structuredClone(roots);
|
||||
const context = { orderedOrganizations: [], readOnly: true, autoLayout: resolveAutoLayout({ hybridOrganizationIds: ['a'] }) } as unknown as RenderContext;
|
||||
const tree = engineTree(roots, 'groups', context);
|
||||
assert.equal(tree[0]!.hybrid, undefined);
|
||||
assert.equal(tree[0]!.level, 1);
|
||||
assert.equal(tree[0]!.children![0]!.hybrid, true);
|
||||
assert.equal(tree[0]!.children![0]!.level, 2);
|
||||
assert.equal(tree[0]!.children![0]!.children![0]!.level, 3);
|
||||
assert.equal(tree[0]!.children![1]!.hybrid, undefined);
|
||||
assert.deepEqual(roots, before);
|
||||
});
|
||||
|
||||
test('Fixed ignores Auto configuration and facade defaults keep existing TB fully expanded', () => {
|
||||
const options = { dataSource: { load: async () => ({}) }, stylesheetUrls: { engine: '/engine.css', core: '/style.css' } };
|
||||
const fixed = createOrgChart({ ...options, autoLayout: { type: 'invalid' as 'tb' } });
|
||||
assert.equal(fixed.getAutoLayout().type, 'tb');
|
||||
fixed.destroy();
|
||||
const expanded = createOrgChart({ ...options, layoutMode: 'auto' });
|
||||
assert.equal(expanded.getAutoLayout().initialVisibleDepth, 'all');
|
||||
assert.equal(expanded.getAutoLayout().type, 'tb');
|
||||
expanded.destroy();
|
||||
});
|
||||
Reference in new issue
Block a user