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'], ); });