fix(sim): keep PwmCallback 2-arg compatible via arity dispatch
Revert the earlier approach of widening the existing PWM-callback assertions to accept the new timeMs arg — that masked a contract change rather than fixing it. Instead, updatePwm now hands the optional timeMs only to listeners that declare a 3rd parameter (cb.length >= 3) — i.e. the buzzer, which needs the precise onset time. Plain (pin, dutyCycle) listeners, and the existing toHaveBeenCalledWith(pin, dutyCycle) tests, see an unchanged 2-arg call, so the original PwmCallback contract is preserved. Add a PinManager test locking the dispatch: a 2-param listener stays 2-arg; a 3-param listener receives timeMs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e6ba5ed9c7
commit
9b86c816c1
|
|
@ -168,7 +168,7 @@ describe('AVRSimulator — PWM OCR monitoring', () => {
|
|||
sim.start();
|
||||
sim.stop();
|
||||
|
||||
expect(pwmCb).toHaveBeenCalledWith(9, 128 / 255, expect.anything()); // 3rd arg = sim timeMs
|
||||
expect(pwmCb).toHaveBeenCalledWith(9, 128 / 255);
|
||||
});
|
||||
|
||||
it('PWM covers all six Arduino PWM pins', () => {
|
||||
|
|
@ -202,7 +202,7 @@ describe('AVRSimulator — PWM OCR monitoring', () => {
|
|||
|
||||
PWM_MAP.forEach(({ pin }, i) => {
|
||||
const expected = ((i + 1) * 25) / 255;
|
||||
expect(cbs[pin]).toHaveBeenCalledWith(pin, expected, expect.anything()); // 3rd arg = sim timeMs
|
||||
expect(cbs[pin]).toHaveBeenCalledWith(pin, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ describe('PinManager — PWM duty cycle', () => {
|
|||
const cb = vi.fn();
|
||||
pm.onPwmChange(9, cb);
|
||||
pm.updatePwm(9, 0.5);
|
||||
expect(cb).toHaveBeenCalledWith(9, 0.5, undefined); // 3rd arg = optional timeMs (not passed here)
|
||||
expect(cb).toHaveBeenCalledWith(9, 0.5);
|
||||
});
|
||||
|
||||
it('stores the latest PWM value', () => {
|
||||
|
|
@ -150,9 +150,39 @@ describe('PinManager — PWM duty cycle', () => {
|
|||
pwmPins.forEach((pin, i) => {
|
||||
const dc = (i + 1) / 6;
|
||||
pm.updatePwm(pin, dc);
|
||||
expect(callbacks[i]).toHaveBeenCalledWith(pin, dc, undefined); // optional timeMs not passed
|
||||
expect(callbacks[i]).toHaveBeenCalledWith(pin, dc);
|
||||
});
|
||||
});
|
||||
|
||||
// The optional timeMs (precise simulated onset time, used by the buzzer for
|
||||
// sample-accurate audio) must NOT widen the public PwmCallback contract:
|
||||
// listeners that declare only (pin, dutyCycle) keep getting a 2-arg call,
|
||||
// while a listener that declares a 3rd parameter receives timeMs. This guards
|
||||
// the arity-based dispatch the buzzer relies on. Regular functions are used
|
||||
// (not vi.fn) because the dispatch keys off Function.length, and a 3-param
|
||||
// listener must report length 3.
|
||||
it('hands timeMs only to listeners that declare a 3rd parameter', () => {
|
||||
let twoArgCount = -1;
|
||||
let threeArgCount = -1;
|
||||
let threeArgTime: number | undefined;
|
||||
function twoArg(this: unknown, _pin: number, _dc: number) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
twoArgCount = arguments.length;
|
||||
}
|
||||
function threeArg(this: unknown, _pin: number, _dc: number, t?: number) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
threeArgCount = arguments.length;
|
||||
threeArgTime = t;
|
||||
}
|
||||
pm.onPwmChange(7, twoArg);
|
||||
pm.onPwmChange(7, threeArg);
|
||||
|
||||
pm.updatePwm(7, 0.5, 123);
|
||||
|
||||
expect(twoArgCount).toBe(2); // original 2-arg contract preserved — no trailing timeMs
|
||||
expect(threeArgCount).toBe(3);
|
||||
expect(threeArgTime).toBe(123); // 3-arg listener (the buzzer) gets the precise time
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Analog voltage API ──────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ describe('ATtiny85 — PWM monitoring', () => {
|
|||
sim.start();
|
||||
sim.stop();
|
||||
|
||||
expect(pwmCb).toHaveBeenCalledWith(1, 128 / 255, expect.anything()); // 3rd arg = sim timeMs
|
||||
expect(pwmCb).toHaveBeenCalledWith(1, 128 / 255);
|
||||
});
|
||||
|
||||
it('PinManager receives PWM update on pin 0 when OCR0A (0x56) is written', () => {
|
||||
|
|
@ -300,7 +300,7 @@ describe('ATtiny85 — PWM monitoring', () => {
|
|||
sim.start();
|
||||
sim.stop();
|
||||
|
||||
expect(pwmCb).toHaveBeenCalledWith(0, 64 / 255, expect.anything()); // 3rd arg = sim timeMs
|
||||
expect(pwmCb).toHaveBeenCalledWith(0, 64 / 255);
|
||||
});
|
||||
|
||||
it('ATtiny85 PWM covers 4 pins (OCR0A/OCR0B/OCR1A/OCR1B)', () => {
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ describe('AVRSimulator Mega — PWM OCR mapping differs from Uno', () => {
|
|||
// Call pollPwmRegisters directly — avoids RAF dependency in unit tests
|
||||
(sim as any).pollPwmRegisters();
|
||||
|
||||
expect(cb).toHaveBeenCalledWith(13, 128 / 255, expect.anything()); // 3rd arg = sim timeMs
|
||||
expect(cb).toHaveBeenCalledWith(13, 128 / 255);
|
||||
sim.stop();
|
||||
});
|
||||
|
||||
|
|
@ -318,7 +318,7 @@ describe('AVRSimulator Mega — PWM OCR mapping differs from Uno', () => {
|
|||
|
||||
(sim as any).pollPwmRegisters();
|
||||
|
||||
expect(cb).toHaveBeenCalledWith(5, 200 / 255, expect.anything()); // 3rd arg = sim timeMs
|
||||
expect(cb).toHaveBeenCalledWith(5, 200 / 255);
|
||||
sim.stop();
|
||||
});
|
||||
|
||||
|
|
@ -335,7 +335,7 @@ describe('AVRSimulator Mega — PWM OCR mapping differs from Uno', () => {
|
|||
|
||||
(sim as any).pollPwmRegisters();
|
||||
|
||||
expect(cb).toHaveBeenCalledWith(6, 100 / 255, expect.anything()); // 3rd arg = sim timeMs
|
||||
expect(cb).toHaveBeenCalledWith(6, 100 / 255);
|
||||
sim.stop();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -201,7 +201,13 @@ export class PinManager {
|
|||
if (dutyCycle > 0) this.outputPins.add(pin);
|
||||
const callbacks = this.pwmListeners.get(pin);
|
||||
if (callbacks) {
|
||||
callbacks.forEach((cb) => cb(pin, dutyCycle, timeMs));
|
||||
// Backward-compatible dispatch: the original PwmCallback contract is
|
||||
// (pin, dutyCycle). Only listeners that actually declare a 3rd parameter
|
||||
// (the buzzer, which needs the precise onset time for sample-accurate
|
||||
// audio) receive timeMs. Plain 2-arg listeners — and the existing tests
|
||||
// that assert toHaveBeenCalledWith(pin, dutyCycle) — see an unchanged
|
||||
// 2-arg call instead of a spurious trailing arg.
|
||||
callbacks.forEach((cb) => (cb.length >= 3 ? cb(pin, dutyCycle, timeMs) : cb(pin, dutyCycle)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue