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..8967b537d 100644 --- a/trackers/javascript-tracker/test/unit/in_queue.test.ts +++ b/trackers/javascript-tracker/test/unit/in_queue.test.ts @@ -67,6 +67,9 @@ describe('InQueueManager', () => { setUserId: function (s?: string | null) { userId = s; }, + getUserId: function () { + return userId; + }, trackPageView: function () { output = attribute; }, @@ -183,4 +186,100 @@ describe('InQueueManager', () => { ]); }).not.toThrow(); }); + + it('Executing a custom callback with arguments should pass the arguments to the callback parameters', () => { + asyncQueue.push([ + function (a: number, b: number) { + expect(a).toEqual(1); + expect(b).toEqual(2); + }, + 1, + 2, + ]); + }); + + it('A custom callback with arguments provided will pass those arguments into the callback parameters', () => { + const tracker = newTracker('sp1'); + mockTrackers.sp1 = tracker; + + asyncQueue.push([ + function (a: number, b: number) { + expect(a).toEqual(1); + expect(b).toEqual(2); + }, + 1, + 2, + ]); + + delete mockTrackers.sp1; + }); + + it('The callback will be passed the tracker dictionary as the final argument', () => { + const mockTracker = newTracker('sp1'); + mockTracker.setUserId('foo'); + mockTrackers.sp1 = mockTracker; + + asyncQueue.push([ + function (a: number, b: number, trackers: Record) { + expect(a).toEqual(1); + expect(b).toEqual(2); + + const tracker = trackers.sp1; + + expect(tracker).toEqual(mockTracker); + expect(tracker.getUserId()).toEqual('foo'); + }, + 1, + 2, + ]); + delete mockTrackers.sp1; + }); + + it('The callback will be passed the tracker dictionary as the argument if there is only one parameter', () => { + const mockTracker = newTracker('sp1'); + mockTracker.setUserId('foo'); + mockTrackers.sp1 = mockTracker; + + asyncQueue.push([ + function (trackers: Record) { + const tracker = trackers.sp1; + + expect(tracker).toEqual(mockTracker); + expect(tracker.getUserId()).toEqual('foo'); + }, + ]); + delete mockTrackers.sp1; + }); + + it('The callback can access the tracker dictionary using both `this` and the first argument', () => { + const mockTracker = newTracker('sp1'); + mockTracker.setUserId('foo'); + mockTrackers.sp1 = mockTracker; + + asyncQueue.push([ + function (this: any, trackers: Record) { + expect(this.sp1).toEqual(mockTrackers.sp1); + expect(trackers.sp1).toEqual(mockTrackers.sp1); + }, + ]); + delete mockTrackers.sp1; + }); + + it('The callback can access the tracker dictionary using both `this` and the last argument, along with arguments', () => { + const mockTracker = newTracker('sp1'); + mockTracker.setUserId('foo'); + mockTrackers.sp1 = mockTracker; + + asyncQueue.push([ + function (this: any, a: number, b: number, trackers: Record) { + expect(this.sp1).toEqual(mockTrackers.sp1); + expect(a).toEqual(1); + expect(b).toEqual(2); + expect(trackers.sp1).toEqual(mockTrackers.sp1); + }, + 1, + 2, + ]); + delete mockTrackers.sp1; + }); });