Files
generalFunctions/test/measurementContainer.test.js
Rene De Ren dec5f63b21 refactor: adopt POSITIONS constants, fix ESLint warnings, break menuUtils into modules
- Replace hardcoded position strings with POSITIONS.* constants
- Prefix unused variables with _ to resolve no-unused-vars warnings
- Fix no-prototype-builtins with Object.prototype.hasOwnProperty.call()
- Extract menuUtils.js (543 lines) into 6 focused modules under menu/
- menuUtils.js now 35 lines, delegates via prototype mixin pattern
- Add 158 unit tests for ConfigManager, MeasurementContainer, ValidationUtils

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:36:52 +01:00

337 lines
13 KiB
JavaScript

const MeasurementContainer = require('../src/measurements/MeasurementContainer');
describe('MeasurementContainer', () => {
let mc;
beforeEach(() => {
mc = new MeasurementContainer({ windowSize: 5, autoConvert: false });
});
// ── Construction ─────────────────────────────────────────────────────
describe('constructor', () => {
it('should initialise with default windowSize when none provided', () => {
const m = new MeasurementContainer();
expect(m.windowSize).toBe(10);
});
it('should accept a custom windowSize', () => {
expect(mc.windowSize).toBe(5);
});
it('should start with an empty measurements map', () => {
expect(mc.measurements).toEqual({});
});
it('should populate default units', () => {
expect(mc.defaultUnits.pressure).toBe('mbar');
expect(mc.defaultUnits.flow).toBe('m3/h');
});
it('should allow overriding default units', () => {
const m = new MeasurementContainer({ defaultUnits: { pressure: 'Pa' } });
expect(m.defaultUnits.pressure).toBe('Pa');
});
});
// ── Chainable setters ───────────────────────────────────────────────
describe('chaining API — type / variant / position', () => {
it('should set type and return this for chaining', () => {
const ret = mc.type('pressure');
expect(ret).toBe(mc);
expect(mc._currentType).toBe('pressure');
});
it('should reset variant and position when type is called', () => {
mc.type('pressure').variant('measured').position('upstream');
mc.type('flow');
expect(mc._currentVariant).toBeNull();
expect(mc._currentPosition).toBeNull();
});
it('should set variant and return this', () => {
mc.type('pressure');
const ret = mc.variant('measured');
expect(ret).toBe(mc);
expect(mc._currentVariant).toBe('measured');
});
it('should throw if variant is called without type', () => {
expect(() => mc.variant('measured')).toThrow(/Type must be specified/);
});
it('should set position (lowercased) and return this', () => {
mc.type('pressure').variant('measured');
const ret = mc.position('Upstream');
expect(ret).toBe(mc);
expect(mc._currentPosition).toBe('upstream');
});
it('should throw if position is called without variant', () => {
mc.type('pressure');
expect(() => mc.position('upstream')).toThrow(/Variant must be specified/);
});
});
// ── Storing and retrieving values ───────────────────────────────────
describe('value() and retrieval methods', () => {
beforeEach(() => {
mc.type('pressure').variant('measured').position('upstream');
});
it('should store a value and retrieve it with getCurrentValue()', () => {
mc.value(42, 1000);
expect(mc.getCurrentValue()).toBe(42);
});
it('should return this for chaining from value()', () => {
const ret = mc.value(1, 1000);
expect(ret).toBe(mc);
});
it('should store multiple values and keep the latest', () => {
mc.value(10, 1).value(20, 2).value(30, 3);
expect(mc.getCurrentValue()).toBe(30);
});
it('should respect the windowSize (rolling window)', () => {
for (let i = 1; i <= 8; i++) {
mc.value(i, i);
}
const all = mc.getAllValues();
// windowSize is 5, so only the last 5 values should remain
expect(all.values.length).toBe(5);
expect(all.values).toEqual([4, 5, 6, 7, 8]);
});
it('should compute getAverage() correctly', () => {
mc.value(10, 1).value(20, 2).value(30, 3);
expect(mc.getAverage()).toBe(20);
});
it('should compute getMin()', () => {
mc.value(10, 1).value(5, 2).value(20, 3);
expect(mc.getMin()).toBe(5);
});
it('should compute getMax()', () => {
mc.value(10, 1).value(5, 2).value(20, 3);
expect(mc.getMax()).toBe(20);
});
it('should return null for getCurrentValue() when no values exist', () => {
expect(mc.getCurrentValue()).toBeNull();
});
it('should return null for getAverage() when no values exist', () => {
expect(mc.getAverage()).toBeNull();
});
it('should return null for getMin() when no values exist', () => {
expect(mc.getMin()).toBeNull();
});
it('should return null for getMax() when no values exist', () => {
expect(mc.getMax()).toBeNull();
});
});
// ── getAllValues() ──────────────────────────────────────────────────
describe('getAllValues()', () => {
it('should return values, timestamps, and unit', () => {
mc.type('pressure').variant('measured').position('upstream');
mc.unit('bar');
mc.value(10, 100).value(20, 200);
const all = mc.getAllValues();
expect(all.values).toEqual([10, 20]);
expect(all.timestamps).toEqual([100, 200]);
expect(all.unit).toBe('bar');
});
it('should return null when chain is incomplete', () => {
mc.type('pressure');
expect(mc.getAllValues()).toBeNull();
});
});
// ── unit() ──────────────────────────────────────────────────────────
describe('unit()', () => {
it('should set unit on the underlying measurement', () => {
mc.type('pressure').variant('measured').position('upstream');
mc.unit('bar');
const measurement = mc.get();
expect(measurement.unit).toBe('bar');
});
});
// ── get() ───────────────────────────────────────────────────────────
describe('get()', () => {
it('should return the Measurement instance for a complete chain', () => {
mc.type('pressure').variant('measured').position('upstream');
mc.value(1, 1);
const m = mc.get();
expect(m).toBeDefined();
expect(m.type).toBe('pressure');
expect(m.variant).toBe('measured');
expect(m.position).toBe('upstream');
});
it('should return null when chain is incomplete', () => {
mc.type('pressure');
expect(mc.get()).toBeNull();
});
});
// ── exists() ────────────────────────────────────────────────────────
describe('exists()', () => {
it('should return false for a non-existent measurement', () => {
mc.type('pressure').variant('measured').position('upstream');
expect(mc.exists()).toBe(false);
});
it('should return true after a value has been stored', () => {
mc.type('pressure').variant('measured').position('upstream').value(1, 1);
expect(mc.exists()).toBe(true);
});
it('should support requireValues option', () => {
mc.type('pressure').variant('measured').position('upstream');
// Force creation of measurement without values
mc.get();
expect(mc.exists({ requireValues: false })).toBe(true);
expect(mc.exists({ requireValues: true })).toBe(false);
});
it('should support explicit type/variant/position overrides', () => {
mc.type('pressure').variant('measured').position('upstream').value(1, 1);
// Reset chain, then query by explicit keys
mc.type('flow');
expect(mc.exists({ type: 'pressure', variant: 'measured', position: 'upstream' })).toBe(true);
expect(mc.exists({ type: 'flow', variant: 'measured', position: 'upstream' })).toBe(false);
});
it('should return false when type is not set and not provided', () => {
const fresh = new MeasurementContainer({ autoConvert: false });
expect(fresh.exists()).toBe(false);
});
});
// ── getLaggedValue() / getLaggedSample() ─────────────────────────────
describe('getLaggedValue() and getLaggedSample()', () => {
beforeEach(() => {
mc.type('pressure').variant('measured').position('upstream');
mc.value(10, 100).value(20, 200).value(30, 300);
});
it('should return the value at lag=1 (previous value)', () => {
expect(mc.getLaggedValue(1)).toBe(20);
});
it('should return null when lag exceeds stored values', () => {
expect(mc.getLaggedValue(10)).toBeNull();
});
it('should return a sample object from getLaggedSample()', () => {
const sample = mc.getLaggedSample(0);
expect(sample).toHaveProperty('value', 30);
expect(sample).toHaveProperty('timestamp', 300);
});
it('should return null from getLaggedSample when not enough values', () => {
expect(mc.getLaggedSample(10)).toBeNull();
});
});
// ── Listing helpers ─────────────────────────────────────────────────
describe('getTypes() / getVariants() / getPositions()', () => {
beforeEach(() => {
mc.type('pressure').variant('measured').position('upstream').value(1, 1);
mc.type('flow').variant('predicted').position('downstream').value(2, 2);
});
it('should list all stored types', () => {
const types = mc.getTypes();
expect(types).toContain('pressure');
expect(types).toContain('flow');
});
it('should list variants for a given type', () => {
mc.type('pressure');
expect(mc.getVariants()).toContain('measured');
});
it('should return empty array for type with no variants', () => {
mc.type('temperature');
expect(mc.getVariants()).toEqual([]);
});
it('should throw if getVariants() called without type', () => {
const fresh = new MeasurementContainer({ autoConvert: false });
expect(() => fresh.getVariants()).toThrow(/Type must be specified/);
});
it('should list positions for type+variant', () => {
mc.type('pressure').variant('measured');
expect(mc.getPositions()).toContain('upstream');
});
it('should throw if getPositions() called without type and variant', () => {
const fresh = new MeasurementContainer({ autoConvert: false });
expect(() => fresh.getPositions()).toThrow(/Type and variant must be specified/);
});
});
// ── clear() ─────────────────────────────────────────────────────────
describe('clear()', () => {
it('should reset all measurements and chain state', () => {
mc.type('pressure').variant('measured').position('upstream').value(1, 1);
mc.clear();
expect(mc.measurements).toEqual({});
expect(mc._currentType).toBeNull();
expect(mc._currentVariant).toBeNull();
expect(mc._currentPosition).toBeNull();
});
});
// ── Child context setters ───────────────────────────────────────────
describe('child context', () => {
it('should set childId and return this', () => {
expect(mc.setChildId('c1')).toBe(mc);
expect(mc.childId).toBe('c1');
});
it('should set childName and return this', () => {
expect(mc.setChildName('pump1')).toBe(mc);
expect(mc.childName).toBe('pump1');
});
it('should set parentRef and return this', () => {
const parent = { id: 'p1' };
expect(mc.setParentRef(parent)).toBe(mc);
expect(mc.parentRef).toBe(parent);
});
});
// ── Event emission ──────────────────────────────────────────────────
describe('event emission', () => {
it('should emit an event when a value is set', (done) => {
mc.emitter.on('pressure.measured.upstream', (data) => {
expect(data.value).toBe(42);
expect(data.type).toBe('pressure');
expect(data.variant).toBe('measured');
expect(data.position).toBe('upstream');
done();
});
mc.type('pressure').variant('measured').position('upstream').value(42, 1);
});
});
// ── setPreferredUnit ────────────────────────────────────────────────
describe('setPreferredUnit()', () => {
it('should store preferred unit and return this', () => {
const ret = mc.setPreferredUnit('pressure', 'Pa');
expect(ret).toBe(mc);
expect(mc.preferredUnits.pressure).toBe('Pa');
});
});
});