'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const DashboardApi = require('../../src/specificClass.js'); // Build a source node with an optional registered-child Map. `children` is an // array of source nodes; each is wrapped in the { child, position, softwareType } // entry shape that childRegistrationUtils.registeredChildren uses at runtime. function makeNode(id, softwareType, children = [], positionVsParent = 'downstream') { const map = new Map(); for (const c of children) { map.set(c.config.general.id, { child: c, softwareType: c.config.functionality.softwareType, position: c.config.functionality.positionVsParent || 'downstream', }); } return { config: { general: { id, name: id }, functionality: { softwareType, positionVsParent }, }, childRegistrationUtils: { registeredChildren: map }, }; } test('recurses a 3-level tree from a single wired root', () => { const api = new DashboardApi({}); // dashboardapi(root) -> machineGroup(child) -> machine(grandchild) const grandchild = makeNode('rm-1', 'machine'); const child = makeNode('mgc-1', 'machineGroupControl', [grandchild]); const root = makeNode('ps-1', 'pumpingStation', [child], 'atequipment'); const dashboards = api.generateDashboardsForGraph(root); const ids = dashboards.map((d) => d.nodeId); assert.deepEqual(ids, ['ps-1', 'mgc-1', 'rm-1'], 'pre-order: root, child, grandchild'); assert.equal(dashboards[0].nodeId, 'ps-1', 'root composed first'); }); test('each parent links only to its own direct children (per-level links)', () => { const api = new DashboardApi({}); const grandchild = makeNode('rm-1', 'machine'); const child = makeNode('mgc-1', 'machineGroupControl', [grandchild]); const root = makeNode('ps-1', 'pumpingStation', [child], 'atequipment'); const dashboards = api.generateDashboardsForGraph(root); const byId = Object.fromEntries(dashboards.map((d) => [d.nodeId, d.dashboard])); assert.equal(byId['ps-1'].links.length, 1, 'root links to its one direct child'); assert.equal(byId['ps-1'].links[0].title, 'mgc-1'); assert.equal(byId['mgc-1'].links.length, 1, 'child links to its one grandchild'); assert.equal(byId['mgc-1'].links[0].title, 'rm-1'); assert.ok(!byId['rm-1'].links || byId['rm-1'].links.length === 0, 'leaf has no child links'); }); test('cycle protection: a node reachable twice is composed once', () => { const api = new DashboardApi({}); const a = makeNode('a', 'pumpingStation', [], 'atequipment'); const b = makeNode('b', 'machineGroupControl'); // wire a -> b and b -> a (cycle) a.childRegistrationUtils.registeredChildren.set('b', { child: b, softwareType: 'machineGroupControl', position: 'downstream' }); b.childRegistrationUtils.registeredChildren.set('a', { child: a, softwareType: 'pumpingStation', position: 'downstream' }); const dashboards = api.generateDashboardsForGraph(a); const ids = dashboards.map((d) => d.nodeId).sort(); assert.deepEqual(ids, ['a', 'b'], 'each node composed exactly once despite the cycle'); }); test('diamond topology: shared descendant composed once', () => { const api = new DashboardApi({}); const shared = makeNode('shared', 'machine'); const left = makeNode('left', 'machineGroupControl', [shared]); const right = makeNode('right', 'machineGroupControl', [shared]); const root = makeNode('root', 'pumpingStation', [left, right], 'atequipment'); const dashboards = api.generateDashboardsForGraph(root); const sharedCount = dashboards.filter((d) => d.nodeId === 'shared').length; assert.equal(sharedCount, 1, 'shared grandchild gets a single dashboard'); }); test('subtreeIdsFor recurses the full subtree (great-grandchildren included)', () => { const api = new DashboardApi({}); const ggc = makeNode('ggc-1', 'measurement'); const gc = makeNode('gc-1', 'machine', [ggc]); const child = makeNode('child-1', 'machineGroupControl', [gc]); const ids = api.subtreeIdsFor('dApi-1', child); assert.ok(ids.has('dApi-1') && ids.has('child-1') && ids.has('gc-1') && ids.has('ggc-1')); assert.equal(ids.size, 4, 'dashboardAPI + child + grandchild + great-grandchild'); }); test('includeChildren:false composes only the root (no recursion)', () => { const api = new DashboardApi({}); const grandchild = makeNode('rm-1', 'machine'); const child = makeNode('mgc-1', 'machineGroupControl', [grandchild]); const root = makeNode('ps-1', 'pumpingStation', [child], 'atequipment'); const dashboards = api.generateDashboardsForGraph(root, { includeChildren: false }); assert.equal(dashboards.length, 1); assert.equal(dashboards[0].nodeId, 'ps-1'); });