diff --git a/trackers/javascript-tracker/src/in_queue.ts b/trackers/javascript-tracker/src/in_queue.ts index e9b9fd44f..2130cf7fb 100644 --- a/trackers/javascript-tracker/src/in_queue.ts +++ b/trackers/javascript-tracker/src/in_queue.ts @@ -265,6 +265,11 @@ export function InQueueManager(functionName: string, asyncQueue: Array) // Strip GlobalSnowplowNamespace from ID fnTrackers[tracker.id.replace(`${functionName}_`, '')] = tracker; } + + // Append the `fnTrackers` object to the parameterArray, to allow it to be + // accessed as the final argument in the callback, useful in environments that don't support `this` (GTM) + Array.prototype.push.call(parameterArray, fnTrackers); + input.apply(fnTrackers, parameterArray); } catch (ex) { LOG.error('Tracker callback failed', ex); diff --git a/trackers/javascript-tracker/test/unit/in_queue.test.ts b/trackers/javascript-tracker/test/unit/in_queue.test.ts index 7df31124d..bd086bb17 100644 --- a/trackers/javascript-tracker/test/unit/in_queue.test.ts +++ b/trackers/javascript-tracker/test/unit/in_queue.test.ts @@ -163,6 +163,34 @@ describe('InQueueManager', () => { asyncQueue.push(['updatePageActivity:firstTracker;secondTracker']); expect(output).toEqual(29); }); +}); + +describe('Snowplow callback', () => { + const asyncQueue = InQueueManager('callback', []); + const mockTrackers: Record = {}; + + let userId: string | null | undefined; + + const newTracker = (trackerId: string): any => { + return { + id: trackerId, + setUserId: function (s?: string | null) { + userId = s; + }, + getUserId: function () { + return userId; + }, + }; + }; + + beforeEach(() => { + const tracker = newTracker('sp1'); + mockTrackers.sp1 = tracker; + }); + + afterEach(() => { + delete mockTrackers.sp1; + }); it('Execute a user-defined custom callback', () => { let callbackExecuted = false; @@ -183,4 +211,58 @@ describe('InQueueManager', () => { ]); }).not.toThrow(); }); + + it('A custom callback with arguments provided will pass those arguments into the callback parameters', () => { + asyncQueue.push([ + function (a: number, b: number) { + expect(a).toEqual(1); + expect(b).toEqual(2); + }, + 1, + 2, + ]); + }); + + it('The callback will be passed the tracker dictionary as the final argument', () => { + asyncQueue.push([ + function (a: number, b: number, trackers: Record) { + expect(a).toEqual(1); + expect(b).toEqual(2); + expect(trackers.sp1).toEqual(mockTrackers.callback_sp1); + }, + 1, + 2, + ]); + }); + + it('The callback will be passed the tracker dictionary as the argument if there is only one parameter', () => { + asyncQueue.push([ + function (trackers: Record) { + const tracker = trackers.sp1; + expect(tracker).toEqual(mockTrackers.callback_sp1); + }, + ]); + }); + + it('The callback can access the tracker dictionary using both `this` and the first argument', () => { + asyncQueue.push([ + function (this: any, trackers: Record) { + expect(this.sp1).toEqual(mockTrackers.callback_sp1); + expect(trackers.sp1).toEqual(mockTrackers.callback_sp1); + }, + ]); + }); + + it('The callback can access the tracker dictionary using both `this` and the last argument, along with arguments', () => { + asyncQueue.push([ + function (this: any, a: number, b: number, trackers: Record) { + expect(this.sp1).toEqual(mockTrackers.callback_sp1); + expect(a).toEqual(1); + expect(b).toEqual(2); + expect(trackers.sp1).toEqual(mockTrackers.callback_sp1); + }, + 1, + 2, + ]); + }); });