const test = require('node:test'); const assert = require('node:assert/strict'); const { makeMeasurementInstance } = require('../helpers/factories'); /** * Tests for the calibration / stability / repeatability primitives. These * methods interact with the stored window from the smoothing pipeline, so * each test seeds storedValues explicitly. */ test("isStable returns false with fewer than 2 samples", () => { const m = makeMeasurementInstance(); m.storedValues = []; assert.equal(m.isStable(), false); // current implementation returns false (not object) at <2 samples }); test("isStable reports stability and stdDev for a flat window", () => { const m = makeMeasurementInstance(); m.storedValues = [10, 10, 10, 10, 10]; const { isStable, stdDev } = m.isStable(); assert.equal(isStable, true); assert.equal(stdDev, 0); }); test("evaluateRepeatability returns stdDev when conditions are met", () => { const m = makeMeasurementInstance({ smoothing: { smoothWindow: 5, smoothMethod: 'mean' }, }); m.storedValues = [10, 10, 10, 10, 10]; const rep = m.evaluateRepeatability(); assert.equal(rep, 0); }); test("evaluateRepeatability refuses when smoothing is disabled", () => { const m = makeMeasurementInstance({ smoothing: { smoothWindow: 5, smoothMethod: 'none' }, }); m.storedValues = [10, 10, 10, 10, 10]; assert.equal(m.evaluateRepeatability(), null); }); test("evaluateRepeatability refuses with insufficient samples", () => { const m = makeMeasurementInstance({ smoothing: { smoothWindow: 5, smoothMethod: 'mean' }, }); m.storedValues = [10]; assert.equal(m.evaluateRepeatability(), null); }); test("calibrate sets offset when input is stable and scaling enabled", () => { const m = makeMeasurementInstance({ scaling: { enabled: true, inputMin: 4, inputMax: 20, absMin: 0, absMax: 100, offset: 0 }, smoothing: { smoothWindow: 5, smoothMethod: 'mean' }, }); // Stable window fed through calculateInput so outputAbs reflects the // pipeline (important because calibrate uses outputAbs for its delta). [3, 3, 3, 3, 3].forEach((v) => m.calculateInput(v)); const outputBefore = m.outputAbs; m.calibrate(); // Offset should now be inputMin - outputAbs(before). assert.equal(m.config.scaling.offset, 4 - outputBefore); }); test("calibrate aborts when input is not stable", () => { const m = makeMeasurementInstance({ scaling: { enabled: true, inputMin: 0, inputMax: 100, absMin: 0, absMax: 10, offset: 0 }, smoothing: { smoothWindow: 5, smoothMethod: 'mean' }, }); // Cheat: populate storedValues with clearly non-stable data. calibrate // calls isStable() -> stdDev > threshold -> warn + no offset change. m.storedValues = [0, 100, 0, 100, 0]; const offsetBefore = m.config.scaling.offset; m.calibrate(); assert.equal(m.config.scaling.offset, offsetBefore); }); test("calibrate uses absMin when scaling is disabled", () => { const m = makeMeasurementInstance({ scaling: { enabled: false, inputMin: 0, inputMax: 1, absMin: 5, absMax: 10, offset: 0 }, smoothing: { smoothWindow: 5, smoothMethod: 'mean' }, }); [5, 5, 5, 5, 5].forEach((v) => m.calculateInput(v)); const out = m.outputAbs; m.calibrate(); assert.equal(m.config.scaling.offset, 5 - out); }); test("toggleSimulation flips the simulation flag", () => { const m = makeMeasurementInstance({ simulation: { enabled: false } }); m.toggleSimulation(); assert.equal(m.config.simulation.enabled, true); m.toggleSimulation(); assert.equal(m.config.simulation.enabled, false); }); test("tick runs simulateInput when simulation is enabled", async () => { const m = makeMeasurementInstance({ scaling: { enabled: false, inputMin: 0, inputMax: 1, absMin: 0, absMax: 100, offset: 0 }, smoothing: { smoothWindow: 1, smoothMethod: 'none' }, simulation: { enabled: true }, }); const before = m.inputValue; await m.tick(); await m.tick(); await m.tick(); // Simulated input must drift from its initial state. assert.notEqual(m.inputValue, before); }); test("tick is a no-op on inputValue when simulation is disabled", async () => { const m = makeMeasurementInstance({ scaling: { enabled: false, inputMin: 0, inputMax: 1, absMin: 0, absMax: 100, offset: 0 }, smoothing: { smoothWindow: 1, smoothMethod: 'none' }, simulation: { enabled: false }, }); m.inputValue = 42; await m.tick(); await m.tick(); assert.equal(m.inputValue, 42); });