Adds platform infrastructure used by the upcoming refactor of
nodeClass / specificClass across all 12 nodes:
- src/domain/UnitPolicy.js — extracted from rotatingMachine/MGC
- src/domain/ChildRouter.js — declarative event routing on top of childRegistrationUtils
- src/domain/LatestWinsGate.js — extracted from MGC dispatch gate
- src/domain/HealthStatus.js — standardised {level, flags, message, source}
- src/nodered/statusBadge.js — compose / error / idle / byState / text helpers
- src/stats/index.js — mean / stdDev / median / mad / lerp
All additive — no existing exports change shape.
56 unit tests pass under node:test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
71 lines
2.6 KiB
JavaScript
71 lines
2.6 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const { statusBadge, MAX_TEXT } = require('../../src/nodered/statusBadge');
|
|
|
|
test('compose joins parts with " | " and uses default green/dot', () => {
|
|
const badge = statusBadge.compose(['A', 'B']);
|
|
assert.deepEqual(badge, { fill: 'green', shape: 'dot', text: 'A | B' });
|
|
});
|
|
|
|
test('compose drops null/undefined/empty parts', () => {
|
|
const badge = statusBadge.compose(['A', null, 'B', undefined, '']);
|
|
assert.equal(badge.text, 'A | B');
|
|
assert.equal(badge.fill, 'green');
|
|
assert.equal(badge.shape, 'dot');
|
|
});
|
|
|
|
test('compose with empty parts and override fill returns empty text', () => {
|
|
const badge = statusBadge.compose([], { fill: 'yellow' });
|
|
assert.equal(badge.text, '');
|
|
assert.equal(badge.fill, 'yellow');
|
|
assert.equal(badge.shape, 'dot');
|
|
});
|
|
|
|
test('error returns red ring with ⚠ prefix', () => {
|
|
const badge = statusBadge.error('boom');
|
|
assert.deepEqual(badge, { fill: 'red', shape: 'ring', text: '⚠ boom' });
|
|
});
|
|
|
|
test('idle returns blue dot with ⏸ prefix', () => {
|
|
const badge = statusBadge.idle('waiting');
|
|
assert.deepEqual(badge, { fill: 'blue', shape: 'dot', text: '⏸️ waiting' });
|
|
});
|
|
|
|
test('byState returns the matching template', () => {
|
|
const map = { off: { fill: 'red', shape: 'dot', text: 'OFF' } };
|
|
const badge = statusBadge.byState(map, 'off');
|
|
assert.deepEqual(badge, { fill: 'red', shape: 'dot', text: 'OFF' });
|
|
});
|
|
|
|
test('byState returns grey "unknown state" badge when key is missing', () => {
|
|
const badge = statusBadge.byState({}, 'unknown');
|
|
assert.equal(badge.fill, 'grey');
|
|
assert.equal(badge.shape, 'ring');
|
|
assert.match(badge.text, /unknown state/);
|
|
assert.match(badge.text, /unknown/);
|
|
});
|
|
|
|
test('byState composes extra parts into the template text', () => {
|
|
const map = { run: { fill: 'green', shape: 'dot', text: 'RUN' } };
|
|
const badge = statusBadge.byState(map, 'run', { compose: ['flow=12.0', 'P=3kW'] });
|
|
assert.equal(badge.text, 'RUN | flow=12.0 | P=3kW');
|
|
});
|
|
|
|
test('text length is truncated to MAX_TEXT chars ending with …', () => {
|
|
const longInput = 'x'.repeat(200);
|
|
const badge = statusBadge.text(longInput);
|
|
assert.equal(badge.text.length, MAX_TEXT);
|
|
assert.equal(badge.text.endsWith('…'), true);
|
|
});
|
|
|
|
test('text helper defaults to green/dot and never returns null text', () => {
|
|
assert.equal(statusBadge.text(null).text, '');
|
|
assert.equal(statusBadge.text(undefined).text, '');
|
|
const badge = statusBadge.text('hi');
|
|
assert.equal(badge.fill, 'green');
|
|
assert.equal(badge.shape, 'dot');
|
|
});
|