Splits pumpingStation/src/ into focused concern modules. specificClass.js
will be slimmed to an orchestrator in P2.9 (integration); for now both
the inlined logic AND the new modules coexist so tests stay green
throughout.
src/basin/ BasinGeometry + thresholdValidator (pure)
src/measurement/ flowAggregator + measurementRouter + calibration
src/control/ levelBased + flowBased(stub) + manual + index dispatcher
src/safety/ safetyController split into dryRun + overfill rules
src/commands/ registry array + handlers (canonical names from start)
src/editor.js 260 lines of SVG basin-diagram redraw, was inline in .html
examples/standalone-demo.js was if(require.main===module) at bottom of specificClass.js
CONTRACT.md canonical inputs + outputs + emitted events
Modified:
src/specificClass.js removed the 170-line standalone demo block
pumpingStation.html oneditprepare/oneditsave delegate to editor.{init,save}
pumpingStation.js added admin endpoint serving src/editor.js
102 basic tests pass (60 new + 42 existing).
specificClass.js itself is unchanged in behaviour — integration is P2.9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
2.1 KiB
JavaScript
65 lines
2.1 KiB
JavaScript
// Unit tests for the manual control strategy.
|
|
// Run with: node --test test/basic/control-manual.basic.test.js
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const manual = require('../../src/control/manual');
|
|
|
|
function makeGroup(name) {
|
|
const calls = { handleInput: [] };
|
|
return {
|
|
config: { general: { name } },
|
|
handleInput: async (...args) => { calls.handleInput.push(args); },
|
|
_calls: calls,
|
|
};
|
|
}
|
|
|
|
function makeMachine(name) {
|
|
const calls = { handleInput: [] };
|
|
return {
|
|
config: { general: { name } },
|
|
handleInput: async (...args) => { calls.handleInput.push(args); },
|
|
_calls: calls,
|
|
};
|
|
}
|
|
|
|
function makeLogger() {
|
|
return { info: () => {}, debug: () => {}, warn: () => {}, error: () => {} };
|
|
}
|
|
|
|
test('forwardDemand calls handleInput("parent", demand) on every machine group', async () => {
|
|
const groups = { a: makeGroup('A'), b: makeGroup('B'), c: makeGroup('C') };
|
|
const ctx = { machineGroups: groups, machines: {}, logger: makeLogger() };
|
|
|
|
await manual.forwardDemand(ctx, 50);
|
|
|
|
for (const g of Object.values(groups)) {
|
|
assert.equal(g._calls.handleInput.length, 1);
|
|
assert.deepEqual(g._calls.handleInput[0], ['parent', 50]);
|
|
}
|
|
});
|
|
|
|
test('forwardDemand with no machineGroups but direct machines splits demand evenly', async () => {
|
|
const machines = { m1: makeMachine('M1'), m2: makeMachine('M2'), m3: makeMachine('M3'), m4: makeMachine('M4') };
|
|
const ctx = { machineGroups: {}, machines, logger: makeLogger() };
|
|
|
|
await manual.forwardDemand(ctx, 80);
|
|
|
|
for (const m of Object.values(machines)) {
|
|
assert.equal(m._calls.handleInput.length, 1);
|
|
assert.deepEqual(m._calls.handleInput[0], ['parent', 'execMovement', 20]);
|
|
}
|
|
});
|
|
|
|
test('run() is a no-op (manual mode is event-driven)', async () => {
|
|
const groups = { a: makeGroup('A') };
|
|
const ctx = { machineGroups: groups, machines: {}, logger: makeLogger() };
|
|
await manual.run(ctx, { percControl: 0 });
|
|
assert.equal(groups.a._calls.handleInput.length, 0);
|
|
});
|
|
|
|
test('manual exports name === "manual"', () => {
|
|
assert.equal(manual.name, 'manual');
|
|
});
|