Files
generalFunctions/test/basic/statusUpdater.basic.test.js

190 lines
6.3 KiB
JavaScript
Raw Normal View History

'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const { StatusUpdater } = require('../../src/nodered/statusUpdater');
function makeNode() {
const calls = [];
return {
calls,
status(badge) { calls.push(badge); },
};
}
function makeSource(initial) {
return {
badge: initial,
throwOnNext: false,
getStatusBadge() {
if (this.throwOnNext) {
this.throwOnNext = false;
throw new Error('boom');
}
return this.badge;
},
};
}
function makeLogger() {
const errors = [];
return {
errors,
error(msg) { errors.push(msg); },
};
}
test('start() schedules a tick that applies the source badge', (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
const node = makeNode();
const source = makeSource({ fill: 'green', shape: 'dot', text: 'OK' });
const u = new StatusUpdater({ node, source, intervalMs: 1000 });
u.start();
assert.equal(node.calls.length, 0);
t.mock.timers.tick(1000);
assert.equal(node.calls.length, 1);
assert.deepEqual(node.calls[0], { fill: 'green', shape: 'dot', text: 'OK' });
u.stop();
});
test('multiple ticks reflect the latest badge from the source', (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
const node = makeNode();
const source = makeSource({ fill: 'green', shape: 'dot', text: 'A' });
const u = new StatusUpdater({ node, source, intervalMs: 500 });
u.start();
t.mock.timers.tick(500);
source.badge = { fill: 'yellow', shape: 'dot', text: 'B' };
t.mock.timers.tick(500);
source.badge = { fill: 'red', shape: 'ring', text: 'C' };
t.mock.timers.tick(500);
assert.equal(node.calls.length, 3);
assert.equal(node.calls[0].text, 'A');
assert.equal(node.calls[1].text, 'B');
assert.equal(node.calls[2].text, 'C');
u.stop();
});
test('source returns null → node.status({}) is called', (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
const node = makeNode();
const source = makeSource(null);
const u = new StatusUpdater({ node, source, intervalMs: 100 });
u.start();
t.mock.timers.tick(100);
assert.equal(node.calls.length, 1);
assert.deepEqual(node.calls[0], {});
u.stop();
});
test('source throw → error logged, error badge applied, next tick still runs', (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
const node = makeNode();
const logger = makeLogger();
const source = makeSource({ fill: 'green', shape: 'dot', text: 'OK' });
source.throwOnNext = true;
const u = new StatusUpdater({ node, source, intervalMs: 1000, logger });
u.start();
t.mock.timers.tick(1000);
assert.equal(logger.errors.length, 1, 'error logged once');
assert.match(logger.errors[0], /boom/);
assert.deepEqual(node.calls[0], { fill: 'red', shape: 'ring', text: '⚠ boom' });
// Subsequent tick: source recovers, normal badge resumes.
t.mock.timers.tick(1000);
assert.equal(node.calls.length, 2);
assert.deepEqual(node.calls[1], { fill: 'green', shape: 'dot', text: 'OK' });
u.stop();
});
test('stop() halts the interval AND clears the badge', (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
const node = makeNode();
const source = makeSource({ fill: 'green', shape: 'dot', text: 'OK' });
const u = new StatusUpdater({ node, source, intervalMs: 500 });
u.start();
t.mock.timers.tick(500);
assert.equal(node.calls.length, 1);
u.stop();
assert.equal(u.isRunning, false);
// stop() pushes a clear-badge call.
assert.equal(node.calls.length, 2);
assert.deepEqual(node.calls[1], {});
// No further ticks after stop.
t.mock.timers.tick(5000);
assert.equal(node.calls.length, 2);
});
test('start() called twice does not schedule two intervals', (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
const node = makeNode();
const source = makeSource({ fill: 'green', shape: 'dot', text: 'OK' });
const u = new StatusUpdater({ node, source, intervalMs: 1000 });
u.start();
u.start();
u.start();
t.mock.timers.tick(1000);
assert.equal(node.calls.length, 1, 'one tick per interval period');
t.mock.timers.tick(1000);
assert.equal(node.calls.length, 2);
u.stop();
});
test('intervalMs: 0 makes start() a no-op', (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
const node = makeNode();
const source = makeSource({ fill: 'green', shape: 'dot', text: 'OK' });
const u = new StatusUpdater({ node, source, intervalMs: 0 });
u.start();
assert.equal(u.isRunning, false);
t.mock.timers.tick(10000);
assert.equal(node.calls.length, 0);
});
test('intervalMs omitted is also treated as a no-op', (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
const node = makeNode();
const source = makeSource({ fill: 'green', shape: 'dot', text: 'OK' });
const u = new StatusUpdater({ node, source });
u.start();
assert.equal(u.isRunning, false);
t.mock.timers.tick(10000);
assert.equal(node.calls.length, 0);
});
test('constructor throws if node.status is missing', () => {
const source = makeSource(null);
assert.throws(
() => new StatusUpdater({ node: {}, source, intervalMs: 1000 }),
/node must expose a \.status/,
);
assert.throws(
() => new StatusUpdater({ node: null, source, intervalMs: 1000 }),
/node must expose a \.status/,
);
});
test('constructor throws if source.getStatusBadge is missing', () => {
const node = makeNode();
assert.throws(
() => new StatusUpdater({ node, source: {}, intervalMs: 1000 }),
/source must expose a \.getStatusBadge/,
);
assert.throws(
() => new StatusUpdater({ node, source: null, intervalMs: 1000 }),
/source must expose a \.getStatusBadge/,
);
});
test('isRunning getter reflects timer lifecycle', (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
const node = makeNode();
const source = makeSource(null);
const u = new StatusUpdater({ node, source, intervalMs: 1000 });
assert.equal(u.isRunning, false);
u.start();
assert.equal(u.isRunning, true);
u.stop();
assert.equal(u.isRunning, false);
});