106 lines
3.8 KiB
TypeScript
106 lines
3.8 KiB
TypeScript
/**
|
|
* Phase 1b continued — Step 1 tests for MixedModeScheduler.
|
|
*
|
|
* Exercises the subscriber routing and voltage cache in isolation from
|
|
* the SPICE engine. The engine is never booted in these tests; we
|
|
* drive `publishVoltage` directly so the routing logic can be locked
|
|
* down before the real `alter + tran + readVec` loop lands.
|
|
*
|
|
* Coverage:
|
|
* - publishVoltage fires every matching subscriber and only those
|
|
* - getCurrentVoltage returns the last published value per pin
|
|
* - unsubscribe removes the callback cleanly
|
|
* - reset (via __resetMixedModeScheduler) clears state between tests
|
|
*/
|
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import {
|
|
getMixedModeScheduler,
|
|
__resetMixedModeScheduler,
|
|
} from '../simulation/spice/MixedModeScheduler';
|
|
|
|
afterEach(() => {
|
|
__resetMixedModeScheduler();
|
|
});
|
|
|
|
describe('MixedModeScheduler — voltage cache', () => {
|
|
it('returns null until something is published', () => {
|
|
const sched = getMixedModeScheduler();
|
|
expect(sched.getCurrentVoltage('q1', 'C')).toBeNull();
|
|
});
|
|
|
|
it('returns the last published voltage per (component, pin)', () => {
|
|
const sched = getMixedModeScheduler();
|
|
sched.publishVoltage('q1', 'C', 4.5);
|
|
sched.publishVoltage('q1', 'B', 1.2);
|
|
sched.publishVoltage('q2', 'C', 0.3);
|
|
expect(sched.getCurrentVoltage('q1', 'C')).toBe(4.5);
|
|
expect(sched.getCurrentVoltage('q1', 'B')).toBe(1.2);
|
|
expect(sched.getCurrentVoltage('q2', 'C')).toBe(0.3);
|
|
sched.publishVoltage('q1', 'C', 2.7); // overwrite
|
|
expect(sched.getCurrentVoltage('q1', 'C')).toBe(2.7);
|
|
});
|
|
});
|
|
|
|
describe('MixedModeScheduler — subscribe / publish routing', () => {
|
|
it('fires the matching subscriber with the published voltage', () => {
|
|
const sched = getMixedModeScheduler();
|
|
const cb = vi.fn();
|
|
sched.subscribe('q1', 'C', cb);
|
|
sched.publishVoltage('q1', 'C', 4.7);
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenCalledWith('UNKNOWN', 4.7);
|
|
});
|
|
|
|
it('does NOT fire subscribers watching a different pin', () => {
|
|
const sched = getMixedModeScheduler();
|
|
const cbMatching = vi.fn();
|
|
const cbOtherPin = vi.fn();
|
|
const cbOtherComp = vi.fn();
|
|
sched.subscribe('q1', 'C', cbMatching);
|
|
sched.subscribe('q1', 'B', cbOtherPin);
|
|
sched.subscribe('q2', 'C', cbOtherComp);
|
|
sched.publishVoltage('q1', 'C', 4.7);
|
|
expect(cbMatching).toHaveBeenCalledTimes(1);
|
|
expect(cbOtherPin).not.toHaveBeenCalled();
|
|
expect(cbOtherComp).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('supports multiple subscribers on the same pin (fan-out)', () => {
|
|
const sched = getMixedModeScheduler();
|
|
const cbA = vi.fn();
|
|
const cbB = vi.fn();
|
|
sched.subscribe('q1', 'C', cbA);
|
|
sched.subscribe('q1', 'C', cbB);
|
|
sched.publishVoltage('q1', 'C', 4.7);
|
|
expect(cbA).toHaveBeenCalledWith('UNKNOWN', 4.7);
|
|
expect(cbB).toHaveBeenCalledWith('UNKNOWN', 4.7);
|
|
});
|
|
|
|
it('unsubscribe handle detaches the callback', () => {
|
|
const sched = getMixedModeScheduler();
|
|
const cb = vi.fn();
|
|
const cancel = sched.subscribe('q1', 'C', cb);
|
|
sched.publishVoltage('q1', 'C', 4.7);
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
cancel();
|
|
sched.publishVoltage('q1', 'C', 0.3);
|
|
expect(cb).toHaveBeenCalledTimes(1); // not called again
|
|
});
|
|
|
|
it('reset clears subscribers and voltage cache', () => {
|
|
const sched = getMixedModeScheduler();
|
|
const cb = vi.fn();
|
|
sched.subscribe('q1', 'C', cb);
|
|
sched.publishVoltage('q1', 'C', 4.7);
|
|
__resetMixedModeScheduler();
|
|
|
|
const sched2 = getMixedModeScheduler();
|
|
expect(sched2).not.toBe(sched);
|
|
expect(sched2.getCurrentVoltage('q1', 'C')).toBeNull();
|
|
sched2.publishVoltage('q1', 'C', 0.5);
|
|
// The old subscriber attached to the disposed scheduler must NOT
|
|
// fire from the new scheduler instance.
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|