diff --git a/.eslintrc b/.eslintrc index 8c3137f..256a3c4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,6 +16,7 @@ "before": true, "beforeEach": true, "after": true, + "afterEach": true, "uetq": true, "UET": true }, diff --git a/package.json b/package.json index 4a18560..9e5758c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "lint:fix": "eslint src/ test/src/ --fix", "test": "npm run build && npm run build:test && DEBUG=false karma start test/karma.config.js", "test:debug": "npm run build && npm run build:test && DEBUG=true karma start test/karma.config.js", - "watch": "ENVIRONMENT=production rollup --config rollup.config.js -w" + "watch": "ENVIRONMENT=production rollup --config rollup.config.js -w", + "watch:tests": "ENVIRONMENT=production rollup --config rollup.test.config.js -w" + }, "devDependencies": { "@semantic-release/changelog": "^5.0.1", diff --git a/src/BingAdsEventForwarder.js b/src/BingAdsEventForwarder.js index 5f9c4ec..ae9f4e2 100644 --- a/src/BingAdsEventForwarder.js +++ b/src/BingAdsEventForwarder.js @@ -27,45 +27,83 @@ var MessageType = { Commerce: 16, }; -var constructor = function() { +var bingConsentValues = { Denied: 'denied', Granted: 'granted' }; +var bingConsentProperties = ['ad_storage']; +var bingToMpConsentSettingsMapping = { + ad_storage: 'defaultAdStorageConsentSDK', +}; + +var constructor = function () { var self = this; var isInitialized = false; var forwarderSettings = null; var reportingService = null; + self.consentMappings = []; + self.consentPayloadAsString = ''; + self.consentPayloadDefaults = {}; + self.name = name; function initForwarder(settings, service, testMode) { + console.warn('BING Local DEV'); + forwarderSettings = settings; reportingService = service; + if (forwarderSettings.consentMapingWeb) { + self.consentMappings = parseSettingsString( + forwarderSettings.consentMapingWeb + ); + } + self.consentPayloadDefaults = getConsentSettings(forwarderSettings); + + var initialConsentPayload = cloneObject(self.consentPayloadDefaults); + var userConsentState = getUserConsentState(); + + var updatedConsentPayload = generateConsentPayload( + userConsentState, + self.consentMappings + ); + + var consentPayload; + if (!isEmpty(initialConsentPayload)) { + consentPayload = initialConsentPayload; + } else if (!isEmpty(updatedConsentPayload)) { + consentPayload = updatedConsentPayload; + } + try { if (!testMode) { - (function(window, document, tag, url, queue) { + (function (window, document, tag, url, queue) { var f; var n; var i; (window[queue] = window[queue] || []), (window.uetq = window.uetq || []), - (f = function() { + sendConsentDefaultToBing(consentPayload), + (f = function () { var obj = { ti: forwarderSettings.tagId, q: window.uetq, }; (obj.q = window[queue]), (window[queue] = new UET(obj)), - window[queue].push('pageLoad'); + maybeSendConsentUpdateToBing(consentPayload); + window[queue].push('pageLoad'); }), (n = document.createElement(tag)), (n.src = url), (n.async = 1), - (n.onload = n.onreadystatechange = function() { - var state = this.readyState; - (state && - state !== 'loaded' && - state !== 'complete') || - (f(), (n.onload = n.onreadystatechange = null)); - }), + (n.onload = n.onreadystatechange = + function () { + var state = this.readyState; + (state && + state !== 'loaded' && + state !== 'complete') || + (f(), + (n.onload = n.onreadystatechange = null)); + }), (i = document.getElementsByTagName(tag)[0]), i.parentNode.insertBefore(n, i); })(window, document, 'script', '//bat.bing.com/bat.js', 'uetq'); @@ -86,6 +124,7 @@ var constructor = function() { isInitialized = true; return 'Successfully initialized: ' + name; } catch (e) { + console.log('error?'); return "Can't initialize forwarder: " + name + ': ' + e; } } @@ -128,10 +167,13 @@ var constructor = function() { "Can't log event on forwarder: " + name + ', not initialized' ); } - try { var obj = createUetObject(event, 'pageLoad'); + var eventConsentState = getEventConsentState(event.ConsentState); + + maybeSendConsentUpdateToBing(eventConsentState); + window.uetq.push(obj); } catch (e) { return "Can't log event on forwarder: " + name + ': ' + e; @@ -180,10 +222,126 @@ var constructor = function() { return obj; } + function getEventConsentState(eventConsentState) { + return eventConsentState && eventConsentState.getGDPRConsentState + ? eventConsentState.getGDPRConsentState() + : {}; + } + + function generateConsentPayload(consentState, mappings) { + if (!mappings) { + return {}; + } + + var payload = cloneObject(self.consentPayloadDefaults); + if (mappings && mappings.length > 0) { + for (var i = 0; i < mappings.length; i++) { + var mappingEntry = mappings[i]; + var mpMappedConsentName = mappingEntry.map.toLowerCase(); + var bingMappedConsentName = mappingEntry.value; + + if ( + consentState[mpMappedConsentName] && + bingConsentProperties.indexOf(bingMappedConsentName) !== -1 + ) { + payload[bingMappedConsentName] = consentState[ + mpMappedConsentName + ].Consented + ? bingConsentValues.Granted + : bingConsentValues.Denied; + } + } + } + + return payload; + } + + function maybeSendConsentUpdateToBing(consentState) { + // If consent payload is empty, + // we never sent an initial default consent state + // so we shouldn't send an update. + if ( + self.consentPayloadAsString && + self.consentMappings && + !isEmpty(consentState) + ) { + var updatedConsentPayload = generateConsentPayload( + consentState, + self.consentMappings + ); + + var eventConsentAsString = JSON.stringify(updatedConsentPayload); + + if (eventConsentAsString !== self.consentPayloadAsString) { + window.uetq.push('consent', 'update', updatedConsentPayload); + self.consentPayloadAsString = JSON.stringify( + updatedConsentPayload + ); + } + } + } + + function sendConsentDefaultToBing(consentPayload) { + self.consentPayloadAsString = JSON.stringify(consentPayload); + + window.uetq.push('consent', 'default', consentPayload); + } + this.init = initForwarder; this.process = processEvent; }; +function getUserConsentState() { + var userConsentState = {}; + + if (mParticle.Identity && mParticle.Identity.getCurrentUser) { + var currentUser = mParticle.Identity.getCurrentUser(); + + if (!currentUser) { + return {}; + } + + var consentState = + mParticle.Identity.getCurrentUser().getConsentState(); + + if (consentState && consentState.getGDPRConsentState) { + userConsentState = consentState.getGDPRConsentState(); + } + } + + return userConsentState; +} + +function getConsentSettings(settings) { + var consentSettings = {}; + + Object.keys(bingToMpConsentSettingsMapping).forEach(function ( + bingConsentKey + ) { + var mpConsentSettingKey = + bingToMpConsentSettingsMapping[bingConsentKey]; + var bingConsentValuesKey = settings[mpConsentSettingKey]; + + // Microsoft recommends that for most countries, we should default to 'Granted' + // if a default value is not provided + if (bingConsentValuesKey && mpConsentSettingKey) { + consentSettings[bingConsentKey] = bingConsentValues[ + bingConsentValuesKey + ] + ? bingConsentValues[bingConsentValuesKey] + : bingConsentValues.Granted; + } else { + consentSettings[bingConsentKey] = bingConsentValues.Granted; + } + }); + + return consentSettings; +} + +function parseSettingsString(settingsString) { + return JSON.parse(settingsString.replace(/"/g, '"')); +} + function getId() { return moduleId; } @@ -228,6 +386,14 @@ if (typeof window !== 'undefined') { } } +function isEmpty(value) { + return value == null || !(Object.keys(value) || value).length; +} + +function cloneObject(obj) { + return JSON.parse(JSON.stringify(obj)); +} + module.exports = { register: register, }; diff --git a/test/src/tests.js b/test/src/tests.js index 931fd89..c0daaf7 100644 --- a/test/src/tests.js +++ b/test/src/tests.js @@ -1,15 +1,15 @@ -describe('Bing Ads Event Forwarder', function() { - var ReportingService = function() { +describe('Bing Ads Event Forwarder', function () { + var ReportingService = function () { var self = this; this.id = null; this.event = null; - this.cb = function(forwarder, event) { + this.cb = function (forwarder, event) { self.id = forwarder.id; self.event = event; }; - this.reset = function() { + this.reset = function () { self.id = null; self.event = null; }; @@ -32,7 +32,7 @@ describe('Bing Ads Event Forwarder', function() { Social: 7, Other: 8, Media: 9, - getName: function() { + getName: function () { return 'This is my name!'; }, }; @@ -48,40 +48,76 @@ describe('Bing Ads Event Forwarder', function() { Refund: 8, AddToWishlist: 9, RemoveFromWishlist: 10, - getName: function() { + getName: function () { return 'Action'; }, }; var reportService = new ReportingService(); - before(function() { + before(function () { mParticle.EventType = EventType; mParticle.MessageType = MessageType; mParticle.ProductActionType = ProductActionType; }); - beforeEach(function() { + beforeEach(function () { reportService.reset(); window.uetq = []; - mParticle.forwarder.init( - { - tagId: 'tagId', - }, - reportService.cb, - true - ); + }); - describe('Init the BingAds SDK', function() { - it('should init', function(done) { + describe('Init the BingAds SDK', function () { + beforeEach(function () { + mParticle.forwarder.init( + { + tagId: 'tagId', + }, + reportService.cb, + true + ); + }); + + it('should init', function (done) { window.uetq.length.should.equal(0); done(); }); + + it('should init with a consent payload', function (done) { + mParticle.forwarder.init( + { + tagId: 'tagId', + }, + reportService.cb, + false, // Disable testMode so we can test init + ); + + // UETQ queues up events as array elements and then parses them internally. + // The first 3 elements of this array will be the consent payload + // The 4th element will be the event payload + window.uetq.length.should.eql(3); + window.uetq[0].should.equal('consent'); + window.uetq[1].should.equal('default'); + window.uetq[2].should.eql({ + ad_storage: 'granted', + }); + + done(); + }); }); - describe('Track Events', function() { - it('should log events', function(done) { + describe('Track Events', function () { + beforeEach(function () { + mParticle.forwarder.init( + { + tagId: 'tagId', + }, + reportService.cb, + true + ); + }); + + it('should log events', function (done) { var obj = { EventDataType: MessageType.PageEvent, EventName: 'Test Page Event', @@ -99,7 +135,7 @@ describe('Bing Ads Event Forwarder', function() { done(); }); - it('should log commerce events', function(done) { + it('should log commerce events', function (done) { var obj = { EventDataType: MessageType.Commerce, EventName: 'Test Commerce Event', @@ -119,7 +155,7 @@ describe('Bing Ads Event Forwarder', function() { done(); }); - it('should not log event without an event name', function(done) { + it('should not log event without an event name', function (done) { mParticle.forwarder.process({ EventDataType: '', }); @@ -129,7 +165,7 @@ describe('Bing Ads Event Forwarder', function() { done(); }); - it('should not log incorrect events', function(done) { + it('should not log incorrect events', function (done) { mParticle.forwarder.process({ EventDataType: MessageType.Commerce, }); @@ -139,4 +175,584 @@ describe('Bing Ads Event Forwarder', function() { done(); }); }); + + describe('Consent', function () { + var consentMap = [ + { + jsmap: null, + map: 'marketing_consent', + maptype: 'ConsentPurposes', + value: 'ad_storage', + }, + ]; + + beforeEach(function () { + mParticle.forwarders = []; + }); + + afterEach(function () { + window.uetq = []; + }); + + it('should consolidate consent information', function (done) { + mParticle.forwarder.init( + { + tagId: 'tagId', + }, + reportService.cb, + false, // Disable testMode so we can test init + ); + + var obj = { + EventDataType: MessageType.PageEvent, + EventName: 'Test Page Event', + CustomFlags: { + 'Bing.EventValue': 10, + }, + ConsentState: { + getGDPRConsentState: function () { + return { + some_consent: { + Consented: true, + Timestamp: 1557935884509, + ConsentDocument: 'fake_consent_document', + Location: 'This is fake', + HardwareId: '123456', + }, + }; + }, + }, + }; + + var expectedConsentPayload = [ + 'consent', + 'update', + { ad_storage: 'granted' }, + ]; + + var expectedEventPayload = { + ea: 'pageLoad', + ec: 'This is my name!', + el: 'Test Page Event', + ev: 10, + }; + + mParticle.forwarder.process(obj); + + // UETQ queues up events as array elements and then parses them internally. + // The first 3 elements of this array will be the consent payload + // The 4th element will be the event payload + window.uetq.length.should.eql(4); + window.uetq[0].should.equal('consent'); + window.uetq[1].should.equal('default'); + window.uetq[2].should.eql(expectedConsentPayload[2]); + window.uetq[3].should.eql(expectedEventPayload); + + done(); + }); + + it('should construct a Default Consent State Payload from Mappings on init', function (done) { + mParticle.forwarder.init( + { + tagId: 'tagId', + consentMapingWeb: + '[{"jsmap":null,"map":"Marketing","maptype":"ConsentPurposes","value":"ad_storage"}]', + }, + reportService.cb, + false // Disable testMode so we can test init + ); + + var expectedConsentPayload = [ + 'consent', + 'default', + { ad_storage: 'granted' }, // Microsoft recommends defaulting to granted + ]; + + // UETQ queues up events as array elements and then parses them internally. + // The first 3 elements of this array will be the consent payload + window.uetq.length.should.eql(3); + window.uetq.should.eql(['consent', 'default', expectedConsentPayload[2]]); + + done(); + }); + + it('should construct a Default Consent State Payload from Default Settings and construct an Update Consent State Payload from Mappings', (done) => { + mParticle.forwarder.init( + { + tagId: 'tagId', + consentMapingWeb: JSON.stringify(consentMap), + defaultAdStorageConsentSDK: 'Denied', // Should be overridden by user consent state + }, + reportService.cb, + false // Disable testMode so we can test init + ); + + var expectedInitialConsentPayload = [ + 'consent', + 'default', + { ad_storage: 'denied' }, + ]; + + + var expectedUpdatedConsentPayload = [ + 'consent', + 'update', + { ad_storage: 'granted' }, + ]; + + window.uetq.length.should.eql(3); + window.uetq[0].should.equal('consent'); + window.uetq[1].should.equal('default'); + window.uetq[2].should.eql(expectedInitialConsentPayload[2]); + + + var obj = { + EventDataType: MessageType.PageEvent, + EventName: 'Test Page Event', + CustomFlags: { + 'Bing.EventValue': 10, + }, + ConsentState: { + getGDPRConsentState: function () { + return { + marketing_consent: { + Consented: true, + Timestamp: 1557935884509, + ConsentDocument: 'Marketing_Consent', + Location: 'This is fake', + HardwareId: '123456', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'some_consent', + }, + }; + }, + }, + }; + + mParticle.forwarder.process(obj); + + // UETQ should now have 7 elements + // The first 3 elements of this array will be the consent payload + // The next 3 elments will be the consent update payload + // The 7th element will be the event payload + + window.uetq.length.should.eql(7); + window.uetq[3].should.equal('consent'); + window.uetq[4].should.equal('update'); + window.uetq[5].should.eql(expectedUpdatedConsentPayload[2]); + + done(); + }); + + it('should ignore Unspecified Consent Settings if NOT explicitly defined in Consent State', (done) => { + mParticle.forwarder.init( + { + tagId: 'tagId', + consentMapingWeb: JSON.stringify(consentMap), + defaultAdStorageConsentSDK: 'Unspecified', // Should be overridden by user consent state + }, + reportService.cb, + false // Disable testMode so we can test init + ); + + var expectedConsentPayload = [ + 'consent', + 'default', + { ad_storage: 'granted' }, + ]; + + console.log('uetq', window.uetq); + + window.uetq.length.should.eql(3); + window.uetq[0].should.equal('consent'); + window.uetq[1].should.equal('default'); + window.uetq[2].should.eql(expectedConsentPayload[2]); + done(); + }); + + it('should construct a Consent State Update Payload when consent changes', (done) => { + mParticle.forwarder.init( + { + tagId: 'tagId', + consentMapingWeb: JSON.stringify(consentMap), + }, + reportService.cb, + false // Disable testMode so we can test init + ); + + var expectedInitialConsentPayload = [ + 'consent', + 'default', + { ad_storage: 'granted' }, + ]; + + var expectedUpdatedConsentPayload = [ + 'consent', + 'update', + { ad_storage: 'denied' }, + ]; + + // UETQ queues up events as array elements and then parses them internally. + // The first 3 elements of this array will be the consent payload + + window.uetq.length.should.eql(3); + window.uetq[0].should.equal('consent'); + window.uetq[1].should.equal('default'); + window.uetq[2].should.eql(expectedInitialConsentPayload[2]); + + var obj = { + EventDataType: MessageType.PageEvent, + EventName: 'Test Page Event', + CustomFlags: { + 'Bing.EventValue': 10, + }, + ConsentState: { + getGDPRConsentState: function () { + return { + marketing_consent: { + Consented: false, + Timestamp: 1557935884509, + ConsentDocument: 'Marketing_Consent', + Location: 'This is fake', + HardwareId: '123456', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'some_consent', + }, + }; + }, + }, + }; + + mParticle.forwarder.process(obj); + + // UETQ should now have 7 elements + // The first 3 elements of this array will be the initial consent payload + // The next 3 elments will be the consent update payload + // The 7th element will be the event payload + + window.uetq.length.should.eql(7); + window.uetq[3].should.equal('consent'); + window.uetq[4].should.equal('update'); + window.uetq[5].should.eql(expectedUpdatedConsentPayload[2]); + + done(); + }); + + it('should construct a Consent State Update Payload with Consent Setting Defaults when consent changes', (done) => { + mParticle.forwarder.init( + { + tagId: 'tagId', + consentMapingWeb: JSON.stringify(consentMap), + defaultAdStorageConsentSDK: 'Denied', + }, + reportService.cb, + false // Disable testMode so we can test init + ); + + var expectedInitialConsentPayload = [ + 'consent', + 'default', + { ad_storage: 'denied' }, + ]; + + var expectedUpdatedConsentPayload = [ + 'consent', + 'update', + { ad_storage: 'granted' }, + ]; + + // UETQ queues up events as array elements and then parses them internally. + // The first 3 elements of this array will be the consent payload + + window.uetq.length.should.eql(3); + window.uetq[0].should.equal('consent'); + window.uetq[1].should.equal('default'); + window.uetq[2].should.eql(expectedInitialConsentPayload[2]); + + var obj = { + EventDataType: MessageType.PageEvent, + EventName: 'Test Page Event', + CustomFlags: { + 'Bing.EventValue': 10, + }, + ConsentState: { + getGDPRConsentState: function () { + return { + marketing_consent: { + Consented: true, + Timestamp: 1557935884509, + ConsentDocument: 'Marketing_Consent', + Location: 'This is fake', + HardwareId: '123456', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'some_consent', + }, + }; + }, + }, + }; + + mParticle.forwarder.process(obj); + + // UETQ should now have 7 elements + // The first 3 elements of this array will be the initial consent payload + // The next 3 elments will be the consent update payload + // The 7th element will be the event payload + + window.uetq.length.should.eql(7); + window.uetq[3].should.equal('consent'); + window.uetq[4].should.equal('update'); + window.uetq[5].should.eql(expectedUpdatedConsentPayload[2]); + + done(); + }); + + it('should NOT construct a Consent State Update Payload if consent DOES NOT change', (done) => { + + mParticle.forwarder.init( + { + tagId: 'tagId', + consentMapingWeb: JSON.stringify(consentMap), + }, + reportService.cb, + false // Disable testMode so we can test init + ); + + var expectedInitialConsentPayload = [ + 'consent', + 'default', + { ad_storage: 'granted' }, + ]; + + var expectedUpdatedConsentPayload = [ + 'consent', + 'update', + { ad_storage: 'denied' }, + ]; + + // UETQ queues up events as array elements and then parses them internally. + // The first 3 elements of this array will be the consent payload + + window.uetq.length.should.eql(3); + window.uetq[0].should.equal('consent'); + window.uetq[1].should.equal('default'); + window.uetq[2].should.eql(expectedInitialConsentPayload[2]); + + var obj = { + EventDataType: MessageType.PageEvent, + EventName: 'Test Page Event', + CustomFlags: { + 'Bing.EventValue': 10, + }, + ConsentState: { + getGDPRConsentState: function () { + return { + marketing_consent: { + Consented: true, + Timestamp: 1557935884509, + ConsentDocument: 'Marketing_Consent', + Location: 'This is fake', + HardwareId: '123456', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'some_consent', + }, + }; + }, + }, + }; + + mParticle.forwarder.process(obj); + + // UETQ should now have 7 elements + // The first 3 elements of this array will be the initial consent payload + // The 4th element will be the event payload + + window.uetq.length.should.eql(4); + window.uetq[3].should.eql({ + ea: 'pageLoad', + ec: 'This is my name!', + el: 'Test Page Event', + ev: 10, + }); + + done(); + }); + + it('should create a Consent State Default of Granted if consent mappings and settings are undefined', (done) => { + + mParticle.forwarder.init( + { + tagId: 'tagId', + }, + reportService.cb, + false // Disable testMode so we can test init + ); + + var expectedInitialConsentPayload = [ + 'consent', + 'default', + { ad_storage: 'granted' }, + ]; + + // UETQ queues up events as array elements and then parses them internally. + // The first 3 elements of this array will be the consent payload + + window.uetq.length.should.eql(3); + window.uetq[0].should.equal('consent'); + window.uetq[1].should.equal('default'); + window.uetq[2].should.eql(expectedInitialConsentPayload[2]); + + var obj = { + EventDataType: MessageType.PageEvent, + EventName: 'Test Page Event', + CustomFlags: { + 'Bing.EventValue': 10, + }, + ConsentState: { + getGDPRConsentState: function () { + return { + marketing_consent: { + Consented: true, + Timestamp: 1557935884509, + ConsentDocument: 'Marketing_Consent', + Location: 'This is fake', + HardwareId: '123456', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'some_consent', + }, + }; + }, + }, + }; + + mParticle.forwarder.process(obj); + + // UETQ should now have 7 elements + // The first 3 elements of this array will be the initial consent payload + // The 4th element will be the event payload + + window.uetq.length.should.eql(4); + window.uetq[3].should.eql({ + ea: 'pageLoad', + ec: 'This is my name!', + el: 'Test Page Event', + ev: 10, + }); + + done(); + }); + + it('should construct Consent State Payloads if consent mappings is undefined but settings defaults are defined', (done) => { + + mParticle.forwarder.init( + { + tagId: 'tagId', + defaultAdStorageConsentSDK: 'Denied', + }, + reportService.cb, + false // Disable testMode so we can test init + ); + + var expectedInitialConsentPayload = [ + 'consent', + 'default', + { ad_storage: 'denied' }, + ]; + + // UETQ queues up events as array elements and then parses them internally. + // The first 3 elements of this array will be the consent payload + + window.uetq.length.should.eql(3); + window.uetq[0].should.equal('consent'); + window.uetq[1].should.equal('default'); + window.uetq[2].should.eql(expectedInitialConsentPayload[2]); + + var obj = { + EventDataType: MessageType.PageEvent, + EventName: 'Test Page Event', + CustomFlags: { + 'Bing.EventValue': 10, + }, + ConsentState: { + getGDPRConsentState: function () { + return { + marketing_consent: { + Consented: false, + Timestamp: 1557935884509, + ConsentDocument: 'Marketing_Consent', + Location: 'This is fake', + HardwareId: '123456', + }, + }; + }, + + getCCPAConsentState: function () { + return { + data_sale_opt_out: { + Consented: false, + Timestamp: Date.now(), + Document: 'some_consent', + }, + }; + }, + }, + }; + + mParticle.forwarder.process(obj); + + // UETQ should now have 7 elements + // The first 3 elements of this array will be the initial consent payload + // The 4th element will be the event payload + + window.uetq.length.should.eql(4); + window.uetq[3].should.eql({ + ea: 'pageLoad', + ec: 'This is my name!', + el: 'Test Page Event', + ev: 10, + }); + + done(); + }); + }); });