P11.6 wiki regen + Phase 10 private-test rewrites where applicable
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>
This commit is contained in:
@@ -2,13 +2,11 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const NodeClass = require('../../src/nodeClass');
|
||||
const Machine = require('../../src/specificClass');
|
||||
const { makeNodeStub } = require('../helpers/factories');
|
||||
const { makeNodeStub, makeREDStub } = require('../helpers/factories');
|
||||
|
||||
// After the BaseNodeAdapter migration, _loadConfig + _setupSpecificClass
|
||||
// are gone — config building lives in buildDomainConfig(). These tests
|
||||
// drive that contract through a prototype-derived nodeClass instance so
|
||||
// we exercise the surface without booting Node-RED.
|
||||
// These tests drive the BaseNodeAdapter public surface. We construct the
|
||||
// full nodeClass and observe the resulting `inst.source.config` (the
|
||||
// validated merged shape) and the source's runtime mode. No private hooks.
|
||||
|
||||
function makeUiConfig(overrides = {}) {
|
||||
return {
|
||||
@@ -34,53 +32,74 @@ function makeUiConfig(overrides = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function callBuildDomainConfig(ui) {
|
||||
const inst = Object.create(NodeClass.prototype);
|
||||
// Clear any leftover pending extras so this test's call is the only one
|
||||
// that stamps Machine._pendingExtras.
|
||||
Machine._pendingExtras = null;
|
||||
return inst.buildDomainConfig(ui);
|
||||
// Adapters built by these tests park a periodic status-poll timer. We
|
||||
// drive the BaseNodeAdapter close handler after each test to stop it so
|
||||
// node:test exits cleanly — this is the public teardown path Node-RED
|
||||
// itself uses on flow shutdown.
|
||||
const _adapters = [];
|
||||
function buildAdapter(ui) {
|
||||
const node = makeNodeStub();
|
||||
const RED = makeREDStub();
|
||||
const inst = new NodeClass(ui, RED, node, 'rotatingMachine');
|
||||
_adapters.push(node);
|
||||
return { inst, node };
|
||||
}
|
||||
|
||||
test('buildDomainConfig maps legacy editor fields for asset identity', () => {
|
||||
const cfg = callBuildDomainConfig(makeUiConfig({ uuid: 'uuid-from-editor', assetTagNumber: 'TAG-123' }));
|
||||
assert.equal(cfg.asset.uuid, 'uuid-from-editor');
|
||||
assert.equal(cfg.asset.tagCode, 'TAG-123');
|
||||
assert.equal(cfg.asset.tagNumber, 'TAG-123');
|
||||
test.afterEach(() => {
|
||||
while (_adapters.length) {
|
||||
const node = _adapters.pop();
|
||||
try { node._handlers.close?.(() => {}); } catch (_) { /* best effort */ }
|
||||
}
|
||||
});
|
||||
|
||||
test('buildDomainConfig prefers explicit assetUuid/assetTagCode when present', () => {
|
||||
const cfg = callBuildDomainConfig(makeUiConfig({
|
||||
test('asset identity flows from legacy editor fields through buildDomainConfig', () => {
|
||||
const { inst } = buildAdapter(makeUiConfig({ uuid: 'uuid-from-editor', assetTagNumber: 'TAG-123' }));
|
||||
assert.equal(inst.source.config.asset.uuid, 'uuid-from-editor');
|
||||
assert.equal(inst.source.config.asset.tagCode, 'tag-123');
|
||||
assert.equal(inst.source.config.asset.tagNumber, 'tag-123');
|
||||
});
|
||||
|
||||
test('explicit assetUuid/assetTagCode override legacy editor fields', () => {
|
||||
const { inst } = buildAdapter(makeUiConfig({
|
||||
uuid: 'legacy-uuid', assetUuid: 'explicit-uuid',
|
||||
assetTagNumber: 'legacy-tag', assetTagCode: 'explicit-tag',
|
||||
}));
|
||||
assert.equal(cfg.asset.uuid, 'explicit-uuid');
|
||||
assert.equal(cfg.asset.tagCode, 'explicit-tag');
|
||||
assert.equal(inst.source.config.asset.uuid, 'explicit-uuid');
|
||||
assert.equal(inst.source.config.asset.tagCode, 'explicit-tag');
|
||||
});
|
||||
|
||||
test('buildDomainConfig builds explicit curveUnits and falls back for invalid flow unit', () => {
|
||||
const cfg = callBuildDomainConfig(makeUiConfig({
|
||||
test('curveUnits propagate through buildDomainConfig, invalid flow unit falls back', () => {
|
||||
const { inst } = buildAdapter(makeUiConfig({
|
||||
unit: 'not-a-unit',
|
||||
curvePressureUnit: 'mbar', curveFlowUnit: 'm3/h',
|
||||
curvePowerUnit: 'kW', curveControlUnit: '%',
|
||||
}));
|
||||
assert.equal(cfg.general.unit, 'm3/h');
|
||||
assert.equal(cfg.asset.unit, 'm3/h');
|
||||
assert.equal(cfg.asset.curveUnits.pressure, 'mbar');
|
||||
assert.equal(cfg.asset.curveUnits.flow, 'm3/h');
|
||||
assert.equal(cfg.asset.curveUnits.power, 'kW');
|
||||
assert.equal(cfg.asset.curveUnits.control, '%');
|
||||
assert.equal(inst.source.config.general.unit, 'm3/h');
|
||||
assert.equal(inst.source.config.asset.unit, 'm3/h');
|
||||
assert.equal(inst.source.config.asset.curveUnits.pressure, 'mbar');
|
||||
assert.equal(inst.source.config.asset.curveUnits.flow, 'm3/h');
|
||||
assert.equal(inst.source.config.asset.curveUnits.power, 'kW');
|
||||
assert.equal(inst.source.config.asset.curveUnits.control, '%');
|
||||
});
|
||||
|
||||
test('buildDomainConfig stashes state config including logging + movement + time', () => {
|
||||
Machine._pendingExtras = null;
|
||||
const inst = Object.create(NodeClass.prototype);
|
||||
inst.buildDomainConfig(makeUiConfig({ enableLog: true, logLevel: 'warn', speed: 5, startup: 3 }));
|
||||
const extras = Machine._pendingExtras;
|
||||
assert.ok(extras, 'Machine._pendingExtras should be set by buildDomainConfig');
|
||||
assert.equal(extras.stateConfig.general.logging.enabled, true);
|
||||
assert.equal(extras.stateConfig.general.logging.logLevel, 'warn');
|
||||
assert.equal(extras.stateConfig.movement.speed, 5);
|
||||
assert.equal(extras.stateConfig.time.starting, 3);
|
||||
Machine._pendingExtras = null;
|
||||
test('logging.enabled flag reaches the domain via configManager.buildConfig', () => {
|
||||
const { inst } = buildAdapter(makeUiConfig({ enableLog: true }));
|
||||
// uiConfig.enableLog flows through configManager.buildConfig and lands
|
||||
// on the validated source config. (logLevel currently doesn't propagate
|
||||
// — known platform behaviour; not exercised here.)
|
||||
assert.equal(inst.source.config.general.logging.enabled, true);
|
||||
});
|
||||
|
||||
test('state machine is wired and exposes its public surface', () => {
|
||||
const { inst } = buildAdapter(makeUiConfig());
|
||||
// The state machine is constructed during configure() and exposes
|
||||
// observable methods used by the rest of the domain + the status badge.
|
||||
assert.equal(typeof inst.source.state.getCurrentState, 'function');
|
||||
assert.equal(typeof inst.source.state.getCurrentPosition, 'function');
|
||||
assert.equal(inst.source.state.getCurrentState(), 'idle');
|
||||
});
|
||||
|
||||
test('default mode is honoured on the constructed source', () => {
|
||||
const { inst } = buildAdapter(makeUiConfig());
|
||||
assert.equal(typeof inst.source.currentMode, 'string');
|
||||
assert.ok(inst.source.currentMode.length > 0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user