Files
rotatingMachine/test/basic/stateBindings.basic.test.js

92 lines
2.9 KiB
JavaScript
Raw Normal View History

const test = require('node:test');
const assert = require('node:assert/strict');
const EventEmitter = require('events');
const { bindStateEvents, isOperationalState, OPERATIONAL_STATES } =
require('../../src/state/stateBindings');
function makeFakeState() {
const emitter = new EventEmitter();
let current = 'idle';
return {
emitter,
setState(s) { current = s; },
getCurrentState() { return current; },
};
}
test('bindStateEvents attaches both listeners and they fire on emit', () => {
const state = makeFakeState();
let posCalls = 0;
let stateCalls = 0;
let lastStateArg = null;
bindStateEvents({
state,
onPositionChange: () => { posCalls++; },
onStateChange: (newState) => { stateCalls++; lastStateArg = newState; },
});
assert.equal(state.emitter.listenerCount('positionChange'), 1);
assert.equal(state.emitter.listenerCount('stateChange'), 1);
state.emitter.emit('positionChange', 42);
state.emitter.emit('stateChange', 'operational');
assert.equal(posCalls, 1);
assert.equal(stateCalls, 1);
assert.equal(lastStateArg, 'operational');
});
test('bindStateEvents teardown removes both listeners and is idempotent', () => {
const state = makeFakeState();
const teardown = bindStateEvents({
state,
onPositionChange: () => {},
onStateChange: () => {},
});
assert.equal(state.emitter.listenerCount('positionChange'), 1);
assert.equal(state.emitter.listenerCount('stateChange'), 1);
teardown();
assert.equal(state.emitter.listenerCount('positionChange'), 0);
assert.equal(state.emitter.listenerCount('stateChange'), 0);
teardown();
assert.equal(state.emitter.listenerCount('positionChange'), 0);
});
test('bindStateEvents validates context shape', () => {
assert.throws(() => bindStateEvents(null), /ctx\.state\.emitter is required/);
assert.throws(
() => bindStateEvents({ state: makeFakeState() }),
/handlers are required/,
);
});
test('isOperationalState returns true for operational/accelerating/decelerating/warmingup', () => {
const state = makeFakeState();
for (const s of ['operational', 'accelerating', 'decelerating', 'warmingup']) {
state.setState(s);
assert.equal(isOperationalState(state), true, `expected ${s} to be operational`);
}
});
test('isOperationalState returns false for non-operational states and bad input', () => {
const state = makeFakeState();
for (const s of ['idle', 'starting', 'stopping', 'coolingdown', 'emergencystopped']) {
state.setState(s);
assert.equal(isOperationalState(state), false, `expected ${s} not to be operational`);
}
assert.equal(isOperationalState(null), false);
assert.equal(isOperationalState({}), false);
});
test('OPERATIONAL_STATES list is exported and frozen-ish (no extras beyond contract)', () => {
assert.deepEqual(
[...OPERATIONAL_STATES].sort(),
['accelerating', 'decelerating', 'operational', 'warmingup'],
);
});