For all 11 nodes with auto-gen markers: wiki/Home.md sections 5 (topic contract) and 9 (data model) regenerated via npm run wiki:all. New Unit column shows '<measure> (default <unit>)' for declared topics, '—' otherwise. Effect column now uses descriptor.description (P11.2 field) overriding the generic per-prefix fallback. For rotatingMachine + reactor: Phase 10 test rewrites — 3 + 8 files moved off private nodeClass internals (_attachInputHandler, _commands, _pendingExtras, _registerChild, _tick, etc.) to the public BaseNodeAdapter surface (node.handlers.input, node.source.*). +6 / +7 net new tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
112 lines
4.0 KiB
JavaScript
112 lines
4.0 KiB
JavaScript
'use strict';
|
|
|
|
// Phase 10 rewrite: drives only the public BaseNodeAdapter surface.
|
|
// The pre-refactor _attachInputHandler private switch is gone — input
|
|
// dispatch goes through the commands registry that BaseNodeAdapter builds
|
|
// at construction. Tests fire msgs through `node.handlers.input` and
|
|
// observe via `node.sends`, `inst.source.engine.*`, and per-fire calls
|
|
// captured on a child stub registered through `RED.nodes.getNode(id)`.
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const nodeClass = require('../../src/nodeClass');
|
|
const { makeUiConfig } = require('../helpers/factories');
|
|
|
|
function makeNode(id = 'reactor-1') {
|
|
const sends = [];
|
|
const statuses = [];
|
|
const handlers = {};
|
|
return {
|
|
id, sends, statuses, handlers,
|
|
send(arr) { sends.push(arr); },
|
|
status(b) { statuses.push(b); },
|
|
on(ev, fn) { handlers[ev] = fn; },
|
|
warn() {}, error() {},
|
|
};
|
|
}
|
|
|
|
function makeRED(nodeMap = {}) {
|
|
return { nodes: { getNode: (id) => nodeMap[id] || null } };
|
|
}
|
|
|
|
function closeNode(node) {
|
|
if (node.handlers.close) node.handlers.close(() => {});
|
|
}
|
|
|
|
test('legacy alias topics drive engine setters and updateState', async () => {
|
|
const childSource = {
|
|
id: 'child-source-A',
|
|
config: { general: { id: 'child-source-A' }, functionality: { softwareType: 'measurement', positionVsParent: 'upstream' }, asset: { type: 'temperature' } },
|
|
};
|
|
const node = makeNode();
|
|
const RED = makeRED({ childA: { source: childSource } });
|
|
const inst = new nodeClass(makeUiConfig(), RED, node, 'reactor');
|
|
|
|
try {
|
|
let doneCount = 0;
|
|
const done = () => { doneCount += 1; };
|
|
|
|
// data.clock alias → updateState(timestamp). Capture currentTime
|
|
// before/after to verify the engine advanced.
|
|
const t0 = inst.source.engine.currentTime;
|
|
await node.handlers.input({ topic: 'clock', timestamp: t0 + 1 }, () => {}, done);
|
|
|
|
// Fluent alias → engine setInfluent setter.
|
|
await node.handlers.input(
|
|
{ topic: 'Fluent', payload: { inlet: 0, F: 7, C: [1,2,3,4,5,6,7,8,9,10,11,12,13] } },
|
|
() => {}, done,
|
|
);
|
|
assert.equal(inst.source.engine.Fs[0], 7);
|
|
assert.deepEqual(inst.source.engine.Cs_in[0], [1,2,3,4,5,6,7,8,9,10,11,12,13]);
|
|
|
|
// OTR alias → engine setOTR setter.
|
|
await node.handlers.input({ topic: 'OTR', payload: 3.5 }, () => {}, done);
|
|
assert.equal(inst.source.engine.OTR, 3.5);
|
|
|
|
// Temperature alias → engine setTemperature setter.
|
|
await node.handlers.input({ topic: 'Temperature', payload: 18.2 }, () => {}, done);
|
|
assert.equal(inst.source.engine.temperature, 18.2);
|
|
|
|
// Dispersion alias — CSTR engine does not own a setDispersion setter
|
|
// (only PFR does); the Reactor wrapper guards on engine type and the
|
|
// dispatch should silently return without throwing.
|
|
await node.handlers.input({ topic: 'Dispersion', payload: 0.2 }, () => {}, done);
|
|
|
|
// registerChild alias → registers via childRegistrationUtils.
|
|
// The handler resolves the child via RED.nodes.getNode(payload).source.
|
|
await node.handlers.input(
|
|
{ topic: 'registerChild', payload: 'childA', positionVsParent: 'upstream' },
|
|
() => {}, done,
|
|
);
|
|
|
|
assert.equal(doneCount, 6);
|
|
} finally {
|
|
closeNode(node);
|
|
}
|
|
});
|
|
|
|
test('canonical topics are accepted (data.fluent, data.otr, data.temperature)', async () => {
|
|
const node = makeNode();
|
|
const inst = new nodeClass(makeUiConfig(), makeRED(), node, 'reactor');
|
|
|
|
try {
|
|
let done = 0;
|
|
await node.handlers.input(
|
|
{ topic: 'data.fluent', payload: { inlet: 0, F: 11, C: [0,0,0,0,0,0,0,0,0,0,0,0,0] } },
|
|
() => {}, () => { done += 1; },
|
|
);
|
|
assert.equal(inst.source.engine.Fs[0], 11);
|
|
|
|
await node.handlers.input({ topic: 'data.otr', payload: 4.2 }, () => {}, () => { done += 1; });
|
|
assert.equal(inst.source.engine.OTR, 4.2);
|
|
|
|
await node.handlers.input({ topic: 'data.temperature', payload: 19.7 }, () => {}, () => { done += 1; });
|
|
assert.equal(inst.source.engine.temperature, 19.7);
|
|
|
|
assert.equal(done, 3);
|
|
} finally {
|
|
closeNode(node);
|
|
}
|
|
});
|