From 570f055980d4cec23483d2fdb71cd724c21db692 Mon Sep 17 00:00:00 2001 From: Brett Ausmeier Date: Tue, 27 May 2025 19:47:19 +0200 Subject: [PATCH] Fix skipping of event handlers Event handlers registered after a `once` handler were being silently skipped, causing unpredictable behaviour in tour interactions. This occurred because modifying an array during iteration is unsafe. Making a copy of the bindings array before iteration prevents these handers from being skipped. --- shepherd.js/src/evented.ts | 2 +- test/unit/evented.spec.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/shepherd.js/src/evented.ts b/shepherd.js/src/evented.ts index 95f55500f..c539e1912 100644 --- a/shepherd.js/src/evented.ts +++ b/shepherd.js/src/evented.ts @@ -77,7 +77,7 @@ export class Evented { // eslint-disable-next-line @typescript-eslint/no-explicit-any trigger(event: string, ...args: any[]) { if (!isUndefined(this.bindings) && this.bindings[event]) { - this.bindings[event]?.forEach((binding, index) => { + this.bindings[event]?.slice().forEach((binding, index) => { const { ctx, handler, once } = binding; const context = ctx || this; diff --git a/test/unit/evented.spec.js b/test/unit/evented.spec.js index 45b5ce108..0949603b3 100644 --- a/test/unit/evented.spec.js +++ b/test/unit/evented.spec.js @@ -38,6 +38,14 @@ describe('Evented', () => { step: { id: 'test', text: 'A step' } }); }); + + it('does not skip event bindings after removing an event binding', () => { + testEvent.once('testOn', () => true); + const handlerSpy = jest.fn(); + testEvent.on('testOn', handlerSpy); + testEvent.trigger('testOn'); + expect(handlerSpy).toHaveBeenCalled(); + }); }); describe('off()', () => {