89 lines
5.8 KiB
TypeScript
89 lines
5.8 KiB
TypeScript
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();
|
|
});
|