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