From 4a1f8426a9a6847572fee09e7dfc13fabcfb3693 Mon Sep 17 00:00:00 2001 From: Jens Meichler Date: Thu, 9 Oct 2025 12:11:44 +0200 Subject: [PATCH 1/5] feat: make sendTrackingEvents updatable This may be necessary in case a cookie consent changes and no tracking event should be dispatched anymore. This would allow it to keep the logic therefore in the client and not having to do this check before each call. --- src/constructorio.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/constructorio.js b/src/constructorio.js index 958f94cd..31911f2f 100644 --- a/src/constructorio.js +++ b/src/constructorio.js @@ -158,10 +158,11 @@ class ConstructorIO { * @param {object} [options.testCells] - User test cells * @param {number} [options.sessionId] - Session ID - Will only be set in DOM-less environments * @param {string} [options.userId] - User ID + * @param {boolean} [options.sendTrackingEvents] - Indicates if tracking events should be dispatched */ setClientOptions(options) { if (Object.keys(options).length) { - const { apiKey, segments, testCells, sessionId, userId } = options; + const { apiKey, segments, testCells, sessionId, userId, sendTrackingEvents } = options; if (apiKey) { this.options.apiKey = apiKey; @@ -175,6 +176,10 @@ class ConstructorIO { this.options.testCells = testCells; } + if (sendTrackingEvents !== undefined) { + this.options.sendTrackingEvents = sendTrackingEvents; + } + // Set Session ID in dom-less environments only if (sessionId && !helpers.canUseDOM()) { this.options.sessionId = sessionId; From 27d4d6c1a41cd95f74bb5248ec0215ef46726afa Mon Sep 17 00:00:00 2001 From: Jens Meichler Date: Thu, 9 Oct 2025 13:33:19 +0200 Subject: [PATCH 2/5] test: add tests for setting the sendTrackingEvents property --- spec/src/constructorio.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/src/constructorio.js b/spec/src/constructorio.js index f7ad7f9d..d8fecabd 100644 --- a/spec/src/constructorio.js +++ b/spec/src/constructorio.js @@ -279,6 +279,34 @@ describe(`ConstructorIO${bundledDescriptionSuffix}`, () => { expect(instance.options).to.have.property('testCells').to.deep.equal(newTestCells); }); + it('Should update the client options with new sendTrackingEvents value', () => { + const instance = new ConstructorIO({ + apiKey: validApiKey, + sendTrackingEvents: true, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + + instance.setClientOptions({ + sendTrackingEvents: false, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(false); + }); + + it('Should not update the client options with undefined sendTrackingEvents value', () => { + const instance = new ConstructorIO({ + apiKey: validApiKey, + sendTrackingEvents: true, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + + instance.setClientOptions({}); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + }); + it('Should update the options for modules with new test cells', () => { const oldTestCells = { 'old-cell-name-1': 'old-cell-value-1', From 8eba6e1a69e8162745f646f1b8c4218139e0793d Mon Sep 17 00:00:00 2001 From: Enes Kutay SEZEN Date: Fri, 10 Oct 2025 15:26:48 -0400 Subject: [PATCH 3/5] Update tests and adjust implementation --- spec/src/constructorio.js | 140 ++++++++++++++++++++++++++++++++++++++ src/constructorio.js | 3 +- 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/spec/src/constructorio.js b/spec/src/constructorio.js index d8fecabd..309d1f2f 100644 --- a/spec/src/constructorio.js +++ b/spec/src/constructorio.js @@ -307,6 +307,146 @@ describe(`ConstructorIO${bundledDescriptionSuffix}`, () => { expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); }); + it('Should not update the client options with null sendTrackingEvents value', () => { + const instance = new ConstructorIO({ + apiKey: validApiKey, + sendTrackingEvents: true, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + + instance.setClientOptions({ + sendTrackingEvents: null, + }); + + // Should remain true because null is not a valid boolean + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + }); + + it('Should not update the client options with numeric sendTrackingEvents value', () => { + const instance = new ConstructorIO({ + apiKey: validApiKey, + sendTrackingEvents: true, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + + instance.setClientOptions({ + sendTrackingEvents: 0, + }); + + // Should remain true because 0 is not a valid boolean + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + + instance.setClientOptions({ + sendTrackingEvents: 1, + }); + + // Should remain true because 1 is not a valid boolean + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + }); + + it('Should not update the client options with string sendTrackingEvents value', () => { + const instance = new ConstructorIO({ + apiKey: validApiKey, + sendTrackingEvents: true, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + + instance.setClientOptions({ + sendTrackingEvents: 'false', + }); + + // Should remain true because string is not a valid boolean + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + + instance.setClientOptions({ + sendTrackingEvents: '', + }); + + // Should remain true because empty string is not a valid boolean + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + }); + + it('Should not update the client options with object sendTrackingEvents value', () => { + const instance = new ConstructorIO({ + apiKey: validApiKey, + sendTrackingEvents: true, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + + instance.setClientOptions({ + sendTrackingEvents: {}, + }); + + // Should remain true because object is not a valid boolean + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + + instance.setClientOptions({ + sendTrackingEvents: [], + }); + + // Should remain true because array is not a valid boolean + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + }); + + it('Should actually suppress tracking events when sendTrackingEvents is set to false', (done) => { + const fetchSpy = sinon.spy(fetch); + const instance = new ConstructorIO({ + apiKey: validApiKey, + sendTrackingEvents: true, + trackingSendDelay: 10, + fetch: fetchSpy, + }); + + instance.tracker.trackSessionStart(); + + // Wait for the first event to be queued + setTimeout(() => { + instance.setClientOptions({ + sendTrackingEvents: false, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(false); + + fetchSpy.resetHistory(); + instance.tracker.trackSessionStart(); + + // Wait to verify no tracking event was sent + setTimeout(() => { + expect(fetchSpy).not.to.have.been.called; + expect(instance.tracker.requests.sendTrackingEvents).to.equal(false); + done(); + }, 100); + }, 50); + }); + + it('Should propagate sendTrackingEvents update to tracker module', () => { + const instance = new ConstructorIO({ + apiKey: validApiKey, + sendTrackingEvents: true, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + expect(instance.tracker.requests.sendTrackingEvents).to.equal(true); + + instance.setClientOptions({ + sendTrackingEvents: false, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(false); + expect(instance.tracker.requests.sendTrackingEvents).to.equal(false); + + instance.setClientOptions({ + sendTrackingEvents: true, + }); + + expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + expect(instance.tracker.requests.sendTrackingEvents).to.equal(true); + }); + it('Should update the options for modules with new test cells', () => { const oldTestCells = { 'old-cell-name-1': 'old-cell-value-1', diff --git a/src/constructorio.js b/src/constructorio.js index 31911f2f..a4156d30 100644 --- a/src/constructorio.js +++ b/src/constructorio.js @@ -176,8 +176,9 @@ class ConstructorIO { this.options.testCells = testCells; } - if (sendTrackingEvents !== undefined) { + if (typeof sendTrackingEvents === 'boolean') { this.options.sendTrackingEvents = sendTrackingEvents; + this.tracker.requests.sendTrackingEvents = sendTrackingEvents; } // Set Session ID in dom-less environments only From 6f80fe216f59284862b0753cfa5e3d06bbe1ca75 Mon Sep 17 00:00:00 2001 From: Enes Kutay SEZEN Date: Fri, 10 Oct 2025 15:36:08 -0400 Subject: [PATCH 4/5] Add one more test --- spec/src/constructorio.js | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/spec/src/constructorio.js b/spec/src/constructorio.js index 309d1f2f..f40e0bb9 100644 --- a/spec/src/constructorio.js +++ b/spec/src/constructorio.js @@ -5,6 +5,7 @@ const chaiAsPromised = require('chai-as-promised'); const sinon = require('sinon'); const sinonChai = require('sinon-chai'); const helpers = require('../mocha.helpers'); +const store = require('../../test/utils/store'); const jsdom = require('./utils/jsdom-global'); const { default: packageVersion } = require('../../test/version'); let ConstructorIO = require('../../test/constructorio'); @@ -447,6 +448,48 @@ describe(`ConstructorIO${bundledDescriptionSuffix}`, () => { expect(instance.tracker.requests.sendTrackingEvents).to.equal(true); }); + it('Should send event, disable and block event, re-enable and allow event', (done) => { + helpers.clearStorage(); + store.session.set('_constructorio_is_human', true); + + const fetchSpy = sinon.spy(fetch); + const instance = new ConstructorIO({ + apiKey: validApiKey, + sendTrackingEvents: true, + trackingSendDelay: 10, + fetch: fetchSpy, + }); + + instance.tracker.trackSessionStart(); + + setTimeout(() => { + expect(fetchSpy.callCount).to.be.at.least(1); + + instance.setClientOptions({ + sendTrackingEvents: false, + }); + + expect(instance.options.sendTrackingEvents).to.equal(false); + expect(instance.tracker.requests.sendTrackingEvents).to.equal(false); + + fetchSpy.resetHistory(); + instance.tracker.trackSessionStart(); + + setTimeout(() => { + expect(fetchSpy).not.to.have.been.called; + + instance.setClientOptions({ + sendTrackingEvents: true, + }); + + expect(instance.options.sendTrackingEvents).to.equal(true); + expect(instance.tracker.requests.sendTrackingEvents).to.equal(true); + + done(); + }, 100); + }, 100); + }); + it('Should update the options for modules with new test cells', () => { const oldTestCells = { 'old-cell-name-1': 'old-cell-value-1', From 2fc383520bc4614890948564f28eb11ea1dba279 Mon Sep 17 00:00:00 2001 From: Enes Kutay SEZEN Date: Mon, 13 Oct 2025 10:17:44 -0400 Subject: [PATCH 5/5] Update test --- spec/src/constructorio.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/src/constructorio.js b/spec/src/constructorio.js index f40e0bb9..13802aba 100644 --- a/spec/src/constructorio.js +++ b/spec/src/constructorio.js @@ -431,6 +431,7 @@ describe(`ConstructorIO${bundledDescriptionSuffix}`, () => { }); expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + expect(instance.tracker.options).to.have.property('sendTrackingEvents').to.equal(true); expect(instance.tracker.requests.sendTrackingEvents).to.equal(true); instance.setClientOptions({ @@ -438,6 +439,7 @@ describe(`ConstructorIO${bundledDescriptionSuffix}`, () => { }); expect(instance.options).to.have.property('sendTrackingEvents').to.equal(false); + expect(instance.tracker.options).to.have.property('sendTrackingEvents').to.equal(false); expect(instance.tracker.requests.sendTrackingEvents).to.equal(false); instance.setClientOptions({ @@ -445,6 +447,7 @@ describe(`ConstructorIO${bundledDescriptionSuffix}`, () => { }); expect(instance.options).to.have.property('sendTrackingEvents').to.equal(true); + expect(instance.tracker.options).to.have.property('sendTrackingEvents').to.equal(true); expect(instance.tracker.requests.sendTrackingEvents).to.equal(true); });