diff --git a/src/integrationCapture.ts b/src/integrationCapture.ts index e7fc64d9f..d0e455646 100644 --- a/src/integrationCapture.ts +++ b/src/integrationCapture.ts @@ -120,6 +120,19 @@ const integrationMappingExternal: IntegrationIdMapping = { mappedKey: 'SnapchatConversions.ClickId', output: IntegrationOutputs.CUSTOM_FLAGS, }, + + // Pinterest + // https://developers.pinterest.com/docs/track-conversions/track-conversions-in-the-api/ + // https://help.pinterest.com/en/business/article/pinterest-tag-parameters-and-cookies + epik: { + mappedKey: 'Pinterest.click_id', + output: IntegrationOutputs.CUSTOM_FLAGS, + }, + _epik: { + mappedKey: 'Pinterest.click_id', + output: IntegrationOutputs.CUSTOM_FLAGS, + }, + // Snapchat // https://developers.snap.com/api/marketing-api/Conversions-API/UsingTheAPI#sending-click-id _scid: { @@ -182,6 +195,26 @@ export default class IntegrationCapture { delete cookies['_fbc']; } + // Pinterest Rules + // epik and _epik both map to Pinterest.click_id. Prefer query params over + // localStorage and cookies (same rationale as Facebook fbclid vs _fbc). + // https://help.pinterest.com/en/business/article/pinterest-tag-parameters-and-cookies + const hasPinterestQuery = + !isEmpty(queryParams['epik']) || !isEmpty(queryParams['_epik']); + if (hasPinterestQuery) { + delete cookies['epik']; + delete cookies['_epik']; + delete localStorage['epik']; + delete localStorage['_epik']; + } else { + const hasPinterestLocalStorage = + !isEmpty(localStorage['epik']) || !isEmpty(localStorage['_epik']); + if (hasPinterestLocalStorage) { + delete cookies['epik']; + delete cookies['_epik']; + } + } + // ROKT Rules // If both rtid or rclid and RoktTransactionId are present, prioritize rtid/rclid // If RoktTransactionId is present in both cookies and localStorage, diff --git a/test/jest/integration-capture.spec.ts b/test/jest/integration-capture.spec.ts index e25cc21cf..b8ce33412 100644 --- a/test/jest/integration-capture.spec.ts +++ b/test/jest/integration-capture.spec.ts @@ -3,6 +3,35 @@ import IntegrationCapture, { } from '../../src/integrationCapture'; import { deleteAllCookies } from './utils'; +type FullCaptureSetup = { + url: string; + cookies?: string[]; + localStorage?: Record; + mockNow?: number; +}; + +/** Sets location from URL, optional cookies/localStorage, runs IntegrationCapture('all').capture(). */ +function clickIdsAfterFullCaptureAllMode(setup: FullCaptureSetup): Record { + const { url, cookies, localStorage: ls, mockNow } = setup; + if (mockNow !== undefined) { + jest.spyOn(Date, 'now').mockImplementation(() => mockNow); + } + const urlObj = new URL(url); + cookies?.forEach((c) => { + window.document.cookie = c; + }); + if (ls) { + for (const [key, val] of Object.entries(ls)) { + window.localStorage.setItem(key, val); + } + } + window.location.href = urlObj.href; + window.location.search = urlObj.search; + const integrationCapture = new IntegrationCapture('all'); + integrationCapture.capture(); + return integrationCapture.clickIds ?? {}; +} + describe('Integration Capture', () => { describe('constructor', () => { it('should initialize with clickIds as undefined', () => { @@ -28,6 +57,8 @@ describe('Integration Capture', () => { 'wbraid', 'ttclid', 'ScCid', + 'epik', + '_epik', '_scid' ]); }); @@ -81,82 +112,100 @@ describe('Integration Capture', () => { window.localStorage.clear(); }); - it('should return only Rokt keys from helpers when captureMode is roktonly (lowercase)', () => { - // Query params - const url = new URL('https://www.example.com/?fbclid=abc&gclid=g1&rtid=rt1&rclid=rc1'); - window.location.href = url.href; - window.location.search = url.search; - - // Cookies - document.cookie = '_fbp=54321'; - document.cookie = 'RoktTransactionId=xyz'; - - // Local storage - window.localStorage.setItem('RoktTransactionId', 'ls-rok'); - - const integrationCapture = new IntegrationCapture('roktonly'); - - const clickIds = integrationCapture.captureQueryParams(); - const clickIdCookies = integrationCapture.captureCookies(); - const clickIdLocalStorage = integrationCapture.captureLocalStorage(); - - expect(clickIds).toEqual({ rtid: 'rt1', rclid: 'rc1' }); - expect(clickIdCookies).toEqual({ RoktTransactionId: 'xyz' }); - expect(clickIdLocalStorage).toEqual({ RoktTransactionId: 'ls-rok' }); - }); - - it('should return all mapped keys from helpers when captureMode is all (lowercase)', () => { - jest.spyOn(Date, 'now').mockImplementation(() => 42); - // Query params - const url = new URL('https://www.example.com/?fbclid=abc&gclid=g1&rtid=rt1&rclid=rc1&ScCid=snap1'); - window.location.href = url.href; - window.location.search = url.search; - - // Cookies - document.cookie = '_fbp=54321'; - document.cookie = '_fbc=fb.1.1554763741205.abcdef'; - document.cookie = 'RoktTransactionId=xyz'; - - // Local storage - window.localStorage.setItem('RoktTransactionId', 'ls-rok'); - - const integrationCapture = new IntegrationCapture('all'); - - const clickIds = integrationCapture.captureQueryParams(); - const clickIdCookies = integrationCapture.captureCookies(); - const clickIdLocalStorage = integrationCapture.captureLocalStorage(); - - // fbclid is formatted with timestamp/domain index - expect(clickIds).toMatchObject({ fbclid: 'fb.2.42.abc', gclid: 'g1', rtid: 'rt1', rclid: 'rc1', ScCid: 'snap1' }); - expect(clickIdCookies).toMatchObject({ _fbp: '54321', _fbc: 'fb.1.1554763741205.abcdef', RoktTransactionId: 'xyz' }); - expect(clickIdLocalStorage).toEqual({ RoktTransactionId: 'ls-rok' }); - }); - - it('should NOT return mapped keys from helpers when captureMode is none (lowercase)', () => { - jest.spyOn(Date, 'now').mockImplementation(() => 42); - // Query params - const url = new URL('https://www.example.com/?fbclid=abc&gclid=g1&rtid=rt1&rclid=rc1&ScCid=snap1'); - window.location.href = url.href; - window.location.search = url.search; - - // Cookies - document.cookie = '_fbp=54321'; - document.cookie = '_fbc=fb.1.1554763741205.abcdef'; - document.cookie = 'RoktTransactionId=xyz'; - - // Local storage - window.localStorage.setItem('RoktTransactionId', 'ls-rok'); - - const integrationCapture = new IntegrationCapture('none'); - - const clickIds = integrationCapture.captureQueryParams(); - const clickIdCookies = integrationCapture.captureCookies(); - const clickIdLocalStorage = integrationCapture.captureLocalStorage(); - - expect(clickIds).toMatchObject({}); - expect(clickIdCookies).toMatchObject({}); - expect(clickIdLocalStorage).toEqual({}); - }); + const V2_MODE_HELPER_CASES: Array<{ + title: string; + captureMode: 'roktonly' | 'all' | 'none'; + url: string; + cookies: string[]; + mockNow?: number; + expectQuery: Record; + expectCookies: Record; + expectLocalStorage: Record; + }> = [ + { + title: 'should return only Rokt keys from helpers when captureMode is roktonly (lowercase)', + captureMode: 'roktonly', + url: 'https://www.example.com/?fbclid=abc&gclid=g1&rtid=rt1&rclid=rc1', + cookies: ['_fbp=54321', 'RoktTransactionId=xyz'], + expectQuery: { rtid: 'rt1', rclid: 'rc1' }, + expectCookies: { RoktTransactionId: 'xyz' }, + expectLocalStorage: { RoktTransactionId: 'ls-rok' }, + }, + { + title: 'should return all mapped keys from helpers when captureMode is all (lowercase)', + captureMode: 'all', + url: 'https://www.example.com/?fbclid=abc&gclid=g1&rtid=rt1&rclid=rc1&ScCid=snap1', + cookies: [ + '_fbp=54321', + '_fbc=fb.1.1554763741205.abcdef', + 'RoktTransactionId=xyz', + ], + mockNow: 42, + expectQuery: { + fbclid: 'fb.2.42.abc', + gclid: 'g1', + rtid: 'rt1', + rclid: 'rc1', + ScCid: 'snap1', + }, + expectCookies: { + _fbp: '54321', + _fbc: 'fb.1.1554763741205.abcdef', + RoktTransactionId: 'xyz', + }, + expectLocalStorage: { RoktTransactionId: 'ls-rok' }, + }, + { + title: 'should NOT return mapped keys from helpers when captureMode is none (lowercase)', + captureMode: 'none', + url: 'https://www.example.com/?fbclid=abc&gclid=g1&rtid=rt1&rclid=rc1&ScCid=snap1', + cookies: [ + '_fbp=54321', + '_fbc=fb.1.1554763741205.abcdef', + 'RoktTransactionId=xyz', + ], + mockNow: 42, + expectQuery: {}, + expectCookies: {}, + expectLocalStorage: {}, + }, + ]; + + V2_MODE_HELPER_CASES.forEach( + ({ + title, + captureMode, + url, + cookies, + mockNow, + expectQuery, + expectCookies, + expectLocalStorage, + }) => { + it(title, () => { + if (mockNow !== undefined) { + jest.spyOn(Date, 'now').mockImplementation(() => mockNow); + } + const urlObj = new URL(url); + window.location.href = urlObj.href; + window.location.search = urlObj.search; + cookies.forEach((c) => { + document.cookie = c; + }); + window.localStorage.setItem('RoktTransactionId', 'ls-rok'); + + const integrationCapture = new IntegrationCapture(captureMode); + + const clickIds = integrationCapture.captureQueryParams(); + const clickIdCookies = integrationCapture.captureCookies(); + const clickIdLocalStorage = integrationCapture.captureLocalStorage(); + + expect(clickIds).toMatchObject(expectQuery); + expect(clickIdCookies).toMatchObject(expectCookies); + expect(clickIdLocalStorage).toEqual(expectLocalStorage); + }); + }, + ); }); describe('#capture', () => { @@ -231,15 +280,11 @@ describe('Integration Capture', () => { describe('Google Click Ids', () => { it('should capture Google specific click ids', () => { - const url = new URL('https://www.example.com/?gclid=54321&gbraid=67890&wbraid=09876'); - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ + expect( + clickIdsAfterFullCaptureAllMode({ + url: 'https://www.example.com/?gclid=54321&gbraid=67890&wbraid=09876', + }), + ).toEqual({ gclid: '54321', gbraid: '67890', wbraid: '09876', @@ -248,67 +293,134 @@ describe('Integration Capture', () => { }); describe('SnapChat Click Ids', () => { - it('should capture Snapchat specific click ids', () => { - const url = new URL('https://www.example.com/?ScCid=1234'); - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - ScCid: '1234', - }); - }); - - it('should capture Snapchat specific click ids without being case sensitive', () => { - const url = new URL('https://www.example.com/?sccid=1234'); - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - ScCid: '1234', - }); - }); - - it('should capture _scid from cookies', () => { - const url = new URL('https://www.example.com/'); - - window.document.cookie = '_scid=cookie1-from-cookie'; - window.document.cookie = '_cookie1=4567'; - window.document.cookie = 'baz=qux'; - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); + const SNAPCHAT_FULL_CAPTURE: Array<{ + title: string; + setup: FullCaptureSetup; + expected: Record; + }> = [ + { + title: 'should capture Snapchat specific click ids', + setup: { url: 'https://www.example.com/?ScCid=1234' }, + expected: { ScCid: '1234' }, + }, + { + title: 'should capture Snapchat specific click ids without being case sensitive', + setup: { url: 'https://www.example.com/?sccid=1234' }, + expected: { ScCid: '1234' }, + }, + { + title: 'should capture _scid from cookies', + setup: { + url: 'https://www.example.com/', + cookies: [ + '_scid=cookie1-from-cookie', + '_cookie1=4567', + 'baz=qux', + ], + }, + expected: { _scid: 'cookie1-from-cookie' }, + }, + { + title: 'should capture both ScCid from query params and _scid from cookies', + setup: { + url: 'https://www.example.com/?ScCid=4567', + cookies: ['_scid=cookie1-from-cookie', '_cookie1=334455'], + }, + expected: { + ScCid: '4567', + _scid: 'cookie1-from-cookie', + }, + }, + ]; - expect(integrationCapture.clickIds).toEqual({ - _scid: 'cookie1-from-cookie', + SNAPCHAT_FULL_CAPTURE.forEach(({ title, setup, expected }) => { + it(title, () => { + expect(clickIdsAfterFullCaptureAllMode(setup)).toEqual(expected); }); }); + }); - it('should capture both ScCid from query params and _scid from cookies', () => { - const url = new URL('https://www.example.com/?ScCid=4567'); - - window.document.cookie = '_scid=cookie1-from-cookie'; - window.document.cookie = '_cookie1=334455'; - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); + describe('Pinterest Click Ids', () => { + const PINTEREST_FULL_CAPTURE: Array<{ + title: string; + setup: FullCaptureSetup; + expected: Record; + }> = [ + { + title: 'should capture Pinterest specific click ids from query params (_epik)', + setup: { url: 'https://www.example.com/?_epik=1234' }, + expected: { _epik: '1234' }, + }, + { + title: 'should capture Pinterest specific click ids from query params (epik)', + setup: { url: 'https://www.example.com/?epik=5678' }, + expected: { epik: '5678' }, + }, + { + title: 'should capture Pinterest specific click ids from cookies (_epik)', + setup: { + url: 'https://www.example.com/', + cookies: ['_epik=pinterest_cookie_value'], + }, + expected: { _epik: 'pinterest_cookie_value' }, + }, + { + title: 'should capture Pinterest specific click ids from cookies (epik)', + setup: { + url: 'https://www.example.com/', + cookies: ['epik=pinterest_cookie_value_epik'], + }, + expected: { epik: 'pinterest_cookie_value_epik' }, + }, + { + title: 'should capture Pinterest specific click ids from localStorage (_epik)', + setup: { + url: 'https://www.example.com/', + localStorage: { _epik: 'pinterest_localstorage_value' }, + }, + expected: { _epik: 'pinterest_localstorage_value' }, + }, + { + title: 'should capture Pinterest specific click ids from localStorage (epik)', + setup: { + url: 'https://www.example.com/', + localStorage: { epik: 'pinterest_localstorage_value_epik' }, + }, + expected: { epik: 'pinterest_localstorage_value_epik' }, + }, + { + title: 'should prefer Pinterest query param over cookie when both are present', + setup: { + url: 'https://www.example.com/?epik=from_query', + cookies: ['_epik=from_cookie', 'epik=from_cookie_epik'], + }, + expected: { epik: 'from_query' }, + }, + { + title: 'should prefer Pinterest query param over localStorage when both are present', + setup: { + url: 'https://www.example.com/?_epik=from_query', + localStorage: { + epik: 'from_ls', + _epik: 'from_ls_underscore', + }, + }, + expected: { _epik: 'from_query' }, + }, + { + title: 'should prefer Pinterest localStorage over cookie when both are present', + setup: { + url: 'https://www.example.com/', + cookies: ['epik=from_cookie', '_epik=from_cookie_us'], + localStorage: { _epik: 'from_ls' }, + }, + expected: { _epik: 'from_ls' }, + }, + ]; - expect(integrationCapture.clickIds).toEqual({ - ScCid: '4567', - _scid: 'cookie1-from-cookie', + PINTEREST_FULL_CAPTURE.forEach(({ title, setup, expected }) => { + it(title, () => { + expect(clickIdsAfterFullCaptureAllMode(setup)).toEqual(expected); }); }); }); @@ -399,156 +511,89 @@ describe('Integration Capture', () => { }); describe('Rokt Click Ids', () => { - it('should capture rtid via url param', () => { - const url = new URL('https://www.example.com/?rtid=54321'); - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - rtid: '54321', - }); - }); - - it('should capture rclid via url param', () => { - const url = new URL('https://www.example.com/?rclid=7183717'); - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - rclid: '7183717', - }); - }); - - it('should capture RoktTransactionId via cookies', () => { - window.document.cookie = 'RoktTransactionId=12345'; - - const url = new URL('https://www.example.com/'); - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - RoktTransactionId: '12345', - }); - }); - - it('should capture RoktTransactionId via local storage', () => { - jest.spyOn(Date, 'now').mockImplementation(() => 42); - - const url = new URL('https://www.example.com/'); - - window.location.href = url.href; - window.location.search = url.search; - - localStorage.setItem('RoktTransactionId', '54321'); - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - RoktTransactionId: '54321', - }); - }); - - it('should prioritize rtid over RoktTransactionId via cookies', () => { - jest.spyOn(Date, 'now').mockImplementation(() => 42); - - const url = new URL('https://www.example.com/?rtid=54321'); - - window.document.cookie = 'RoktTransactionId=12345'; - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - rtid: '54321', - }); - }); - - it('should prioritize rclid over RoktTransactionId via cookies', () => { - jest.spyOn(Date, 'now').mockImplementation(() => 42); - - const url = new URL('https://www.example.com/?rclid=7183717'); - - window.document.cookie = 'RoktTransactionId=12345'; - - window.location.href = url.href; - window.location.search = url.search; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - rclid: '7183717', - }); - }); - - it('should prioritize rtid over RoktTransactionId via local storage', () => { - jest.spyOn(Date, 'now').mockImplementation(() => 42); - - const url = new URL('https://www.example.com/?rtid=54321'); - - window.location.href = url.href; - window.location.search = url.search; - - localStorage.setItem('RoktTransactionId', '12345'); - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - rtid: '54321', - }); - }); - - it('should prioritize rclid over RoktTransactionId via local storage', () => { - jest.spyOn(Date, 'now').mockImplementation(() => 42); - - const url = new URL('https://www.example.com/?rclid=7183717'); - - window.location.href = url.href; - window.location.search = url.search; - - localStorage.setItem('RoktTransactionId', '12345'); - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); - - expect(integrationCapture.clickIds).toEqual({ - rclid: '7183717', - }); - }); - - it('should prioritize local storage over cookies', () => { - jest.spyOn(Date, 'now').mockImplementation(() => 42); - - const url = new URL('https://www.example.com/'); - - window.location.href = url.href; - window.location.search = url.search; - - localStorage.setItem('RoktTransactionId', '12345'); - window.document.cookie = 'RoktTransactionId=67890'; - - const integrationCapture = new IntegrationCapture('all'); - integrationCapture.capture(); + const ROKT_FULL_CAPTURE: Array<{ + title: string; + setup: FullCaptureSetup; + expected: Record; + }> = [ + { + title: 'should capture rtid via url param', + setup: { url: 'https://www.example.com/?rtid=54321' }, + expected: { rtid: '54321' }, + }, + { + title: 'should capture rclid via url param', + setup: { url: 'https://www.example.com/?rclid=7183717' }, + expected: { rclid: '7183717' }, + }, + { + title: 'should capture RoktTransactionId via cookies', + setup: { + url: 'https://www.example.com/', + cookies: ['RoktTransactionId=12345'], + }, + expected: { RoktTransactionId: '12345' }, + }, + { + title: 'should capture RoktTransactionId via local storage', + setup: { + url: 'https://www.example.com/', + localStorage: { RoktTransactionId: '54321' }, + mockNow: 42, + }, + expected: { RoktTransactionId: '54321' }, + }, + { + title: 'should prioritize rtid over RoktTransactionId via cookies', + setup: { + url: 'https://www.example.com/?rtid=54321', + cookies: ['RoktTransactionId=12345'], + mockNow: 42, + }, + expected: { rtid: '54321' }, + }, + { + title: 'should prioritize rclid over RoktTransactionId via cookies', + setup: { + url: 'https://www.example.com/?rclid=7183717', + cookies: ['RoktTransactionId=12345'], + mockNow: 42, + }, + expected: { rclid: '7183717' }, + }, + { + title: 'should prioritize rtid over RoktTransactionId via local storage', + setup: { + url: 'https://www.example.com/?rtid=54321', + localStorage: { RoktTransactionId: '12345' }, + mockNow: 42, + }, + expected: { rtid: '54321' }, + }, + { + title: 'should prioritize rclid over RoktTransactionId via local storage', + setup: { + url: 'https://www.example.com/?rclid=7183717', + localStorage: { RoktTransactionId: '12345' }, + mockNow: 42, + }, + expected: { rclid: '7183717' }, + }, + { + title: 'should prioritize local storage over cookies', + setup: { + url: 'https://www.example.com/', + cookies: ['RoktTransactionId=67890'], + localStorage: { RoktTransactionId: '12345' }, + mockNow: 42, + }, + expected: { RoktTransactionId: '12345' }, + }, + ]; - expect(integrationCapture.clickIds).toEqual({ - RoktTransactionId: '12345', + ROKT_FULL_CAPTURE.forEach(({ title, setup, expected }) => { + it(title, () => { + expect(clickIdsAfterFullCaptureAllMode(setup)).toEqual(expected); }); }); }); @@ -716,6 +761,7 @@ describe('Integration Capture', () => { _ttp: '0823422223.23234', ttclid: '12345', gclid: '123233.23131', + epik: 'pinterest123', ScCid: '456789', _scid: 'cookie1-value', invalidId: '12345', @@ -728,10 +774,31 @@ describe('Integration Capture', () => { 'Facebook.BrowserId': '54321', 'TikTok.Callback': '12345', 'GoogleEnhancedConversions.Gclid': '123233.23131', + 'Pinterest.click_id': 'pinterest123', 'SnapchatConversions.ClickId': '456789', 'SnapchatConversions.Cookie1': 'cookie1-value', }); }); + + it('should map both epik and _epik to Pinterest.click_id', () => { + const integrationCapture = new IntegrationCapture('all'); + expect(integrationCapture.filteredCustomFlagMappings.epik).toBeDefined(); + expect(integrationCapture.filteredCustomFlagMappings._epik).toBeDefined(); + + integrationCapture.clickIds = { + epik: 'pinterest_epik', + _epik: 'pinterest_underscore_epik', + }; + + const customFlags = integrationCapture.getClickIdsAsCustomFlags(); + const pinterestClickId = customFlags['Pinterest.click_id']; + + // Same mappedKey: last key wins depends on for-in order; value must be one of the inputs. + expect(pinterestClickId).toBeDefined(); + expect(['pinterest_epik', 'pinterest_underscore_epik']).toContain( + pinterestClickId, + ); + }); }); describe('#getClickIdsAsPartnerIdentites', () => { diff --git a/test/src/tests-integration-capture.ts b/test/src/tests-integration-capture.ts index cbd368366..f61429baa 100644 --- a/test/src/tests-integration-capture.ts +++ b/test/src/tests-integration-capture.ts @@ -23,6 +23,117 @@ declare global { const mParticle = window.mParticle as IMParticleInstanceManager; +/** Expected integration-capture custom flags from stubbed query params + _scid cookie */ +function expectCapturedSnapchatAndPinterestFlags( + customFlags: Record, +): void { + expect(customFlags['SnapchatConversions.ClickId'], 'Snapchat Click ID').to.equal('1234'); + expect(customFlags['SnapchatConversions.Cookie1'], 'Snapchat Cookie1').to.equal('cookie1-value'); + expect(customFlags['Pinterest.click_id'], 'Pinterest click id').to.equal('pinterest-qp-epik'); +} + +/** Expected Facebook + Google custom flags from stubbed capture */ +function expectCapturedGoogleFacebookFlags( + customFlags: Record, + facebookClickId: string, +): void { + expect(customFlags['Facebook.ClickId'], 'Facebook Click Id').to.equal(facebookClickId); + expect(customFlags['Facebook.BrowserId'], 'Facebook Browser Id').to.equal('54321'); + expect(customFlags['GoogleEnhancedConversions.Gclid'], 'Google Enhanced Conversions Gclid').to.equal('234'); + expect(customFlags['GoogleEnhancedConversions.Gbraid'], 'Google Enhanced Conversions Gbraid').to.equal('6574'); + expect(customFlags['GoogleEnhancedConversions.Wbraid'], 'Google Enhanced Conversions Wbraid').to.equal('1234111'); +} + +function expectStubbedIntegrationCaptureFlags( + customFlags: Record, + facebookClickId: string, +): void { + expectCapturedGoogleFacebookFlags(customFlags, facebookClickId); + expectCapturedSnapchatAndPinterestFlags(customFlags); +} + +const COMMERCE_TRANSACTION_ATTRS = { Id: 'foo-transaction-id', Revenue: 430, Tax: 30 }; +const COMMERCE_CUSTOM_ATTRS = { sale: true }; + +function createCommercePurchaseProducts() { + const product1 = mParticle.eCommerce.createProduct('iphone', 'iphoneSKU', 999, 1); + const product2 = mParticle.eCommerce.createProduct('galaxy', 'galaxySKU', 799, 1); + return [product1, product2] as const; +} + +function logCommercePurchaseAndGetEvent(customFlags: Record) { + const [product1, product2] = createCommercePurchaseProducts(); + mParticle.eCommerce.logProductAction( + mParticle.ProductActionType.Purchase, + [product1, product2], + COMMERCE_CUSTOM_ATTRS, + customFlags, + COMMERCE_TRANSACTION_ATTRS, + ); + return findEventFromRequest(fetchMock.calls(), 'purchase'); +} + +function logThreeEventsUploadAndParseBatch(): Record { + window.mParticle.logEvent('Test Event 1'); + window.mParticle.logEvent('Test Event 2'); + window.mParticle.logEvent('Test Event 3'); + window.mParticle.upload(); + expect(fetchMock.calls().length).to.greaterThan(1); + const lastCall = fetchMock.lastCall(); + return JSON.parse(lastCall[1].body as string) as Record; +} + +type CaptureCustomFlagCase = { + title: string; + kind: 'event' | 'pageView'; + usePassedInFacebook: boolean; +}; + +const CAPTURE_CUSTOM_FLAG_CASES: CaptureCustomFlagCase[] = [ + { + title: 'should add captured integrations to event custom flags', + kind: 'event', + usePassedInFacebook: false, + }, + { + title: 'should add captured integrations to event custom flags, prioritizing passed in custom flags', + kind: 'event', + usePassedInFacebook: true, + }, + { + title: 'should add captured integrations to page view custom flags', + kind: 'pageView', + usePassedInFacebook: false, + }, + { + title: 'should add captured integrations to page view custom flags, prioritizing passed in custom flags', + kind: 'pageView', + usePassedInFacebook: true, + }, +]; + +type CommerceCaptureCase = { + title: string; + customFlags: Record; + expectFooBar: boolean; + usePassedInFacebook: boolean; +}; + +const COMMERCE_CAPTURE_CASES: CommerceCaptureCase[] = [ + { + title: 'should add captured integrations to commerce event custom flags', + customFlags: { foo: 'bar' }, + expectFooBar: true, + usePassedInFacebook: false, + }, + { + title: 'should add captured integrations to commerce event custom flags, prioritizing passed in flags', + customFlags: { 'Facebook.ClickId': 'passed-in' }, + expectFooBar: false, + usePassedInFacebook: true, + }, +]; + describe('Integration Capture', () => { beforeEach(async function() { mParticle._resetForTests(MPConfig); @@ -58,6 +169,7 @@ describe('Integration Capture', () => { rclid: '7183717', wbraid: '1234111', ScCid: '1234', + epik: 'pinterest-qp-epik', }); integrationCapture.capture(); }); @@ -68,273 +180,94 @@ describe('Integration Capture', () => { deleteAllCookies(); }); - it('should add captured integrations to event custom flags', async () => { - await waitForCondition(hasIdentifyReturned); - mParticle.logEvent( - 'Test Event', - mParticle.EventType.Navigation, - { mykey: 'myvalue' } - ); - - const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Event'); - - const initialTimestamp = window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; - - expect(testEvent).to.have.property('data'); - expect(testEvent.data).to.have.property('event_name', 'Test Event'); - expect(testEvent.data).to.have.property('custom_flags'); - - expect(testEvent.data.custom_flags['Facebook.ClickId'], 'Facebook Click Id').to.equal(`fb.1.${initialTimestamp}.1234`); - expect(testEvent.data.custom_flags['Facebook.BrowserId'], 'Facebook Browser Id').to.equal('54321'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gclid'], 'Google Enhanced Conversions Gclid').to.equal('234'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gbraid'], 'Google Enhanced Conversions Gbraid').to.equal('6574'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Wbraid'], 'Google Enhanced Conversions Wbraid').to.equal('1234111'); - expect(testEvent.data.custom_flags['SnapchatConversions.ClickId'], 'Snapchat Click ID').to.equal('1234'); - expect(testEvent.data.custom_flags['SnapchatConversions.Cookie1'], 'Snapchat Cookie1').to.equal('cookie1-value'); - }); - - it('should add captured integrations to event custom flags, prioritizing passed in custom flags', async () => { - await waitForCondition(hasIdentifyReturned); - window.mParticle.logEvent( - 'Test Event', - mParticle.EventType.Navigation, - { mykey: 'myvalue' }, - { 'Facebook.ClickId': 'passed-in' }, - ); - - const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Event'); - - expect(testEvent).to.have.property('data'); - expect(testEvent.data).to.have.property('event_name', 'Test Event'); - expect(testEvent.data).to.have.property('custom_flags'); - - expect(testEvent.data.custom_flags['Facebook.ClickId'], 'Facebook Click Id').to.equal('passed-in'); - expect(testEvent.data.custom_flags['Facebook.BrowserId'], 'Facebook Browser Id').to.equal('54321'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gclid'], 'Google Enhanced Conversions Gclid').to.equal('234'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gbraid'], 'Google Enhanced Conversions Gbraid').to.equal('6574'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Wbraid'], 'Google Enhanced Conversions Wbraid').to.equal('1234111'); - expect(testEvent.data.custom_flags['SnapchatConversions.ClickId'], 'Snapchat Click ID').to.equal('1234'); - expect(testEvent.data.custom_flags['SnapchatConversions.Cookie1'], 'Snapchat Cookie1').to.equal('cookie1-value'); - }); - - it('should add captured integrations to page view custom flags', async () => { - await waitForCondition(hasIdentifyReturned); - - window.mParticle.logPageView( - 'Test Page View', - {'foo-attr': 'bar-attr'} - ); - - const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Page View'); - - const initialTimestamp = window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; - - expect(testEvent).to.have.property('data'); - expect(testEvent.data).to.have.property('screen_name', 'Test Page View'); - expect(testEvent.data).to.have.property('custom_flags'); - - expect(testEvent.data.custom_flags['Facebook.ClickId'], 'Facebook Click Id').to.equal(`fb.1.${initialTimestamp}.1234`); - expect(testEvent.data.custom_flags['Facebook.BrowserId'], 'Facebook Browser Id').to.equal('54321'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gclid'], 'Google Enhanced Conversions Gclid').to.equal('234'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gbraid'], 'Google Enhanced Conversions Gbraid').to.equal('6574'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Wbraid'], 'Google Enhanced Conversions Wbraid').to.equal('1234111'); - expect(testEvent.data.custom_flags['SnapchatConversions.ClickId'], 'Snapchat Click ID').to.equal('1234'); - expect(testEvent.data.custom_flags['SnapchatConversions.Cookie1'], 'Snapchat Cookie1').to.equal('cookie1-value'); - }); - - it('should add captured integrations to page view custom flags, prioritizing passed in custom flags', async () => { - await waitForCondition(hasIdentifyReturned); - - window.mParticle.logPageView( - 'Test Page View', - {'foo-attr': 'bar-attr'}, - {'Facebook.ClickId': 'passed-in'}, - ); - - const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Page View'); - - expect(testEvent).to.have.property('data'); - expect(testEvent.data).to.have.property('screen_name', 'Test Page View'); - expect(testEvent.data).to.have.property('custom_flags'); - - expect(testEvent.data.custom_flags['Facebook.ClickId'], 'Facebook Click Id').to.equal('passed-in'); - expect(testEvent.data.custom_flags['Facebook.BrowserId'], 'Facebook Browser Id').to.equal('54321'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gclid'], 'Google Enhanced Conversions Gclid').to.equal('234'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gbraid'], 'Google Enhanced Conversions Gbraid').to.equal('6574'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Wbraid'], 'Google Enhanced Conversions Wbraid').to.equal('1234111'); - expect(testEvent.data.custom_flags['SnapchatConversions.ClickId'], 'Snapchat Click ID').to.equal('1234'); - expect(testEvent.data.custom_flags['SnapchatConversions.Cookie1'], 'Snapchat Cookie1').to.equal('cookie1-value'); - }); - - it('should add captured integrations to commerce event custom flags', async () => { - await waitForCondition(hasIdentifyReturned); - - const product1 = mParticle.eCommerce.createProduct('iphone', 'iphoneSKU', 999, 1); - const product2 = mParticle.eCommerce.createProduct('galaxy', 'galaxySKU', 799, 1); - - const transactionAttributes = { - Id: 'foo-transaction-id', - Revenue: 430.00, - Tax: 30 - }; - - const customAttributes = {sale: true}; - const customFlags = {foo: 'bar'}; - - mParticle.eCommerce.logProductAction( - mParticle.ProductActionType.Purchase, - [product1, product2], - customAttributes, - customFlags, - transactionAttributes); - - const testEvent = findEventFromRequest(fetchMock.calls(), 'purchase'); - - const initialTimestamp = window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; - - expect(testEvent.data.product_action).to.have.property('action', 'purchase'); - expect(testEvent.data).to.have.property('custom_flags'); - - expect(testEvent.data.custom_flags['foo'], 'Custom Flag').to.equal('bar'); - expect(testEvent.data.custom_flags['Facebook.ClickId'], 'Facebook Click Id').to.equal(`fb.1.${initialTimestamp}.1234`); - expect(testEvent.data.custom_flags['Facebook.BrowserId'], 'Facebook Browser Id').to.equal('54321'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gclid'], 'Google Enhanced Conversions Gclid').to.equal('234'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gbraid'], 'Google Enhanced Conversions Gbraid').to.equal('6574'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Wbraid'], 'Google Enhanced Conversions Wbraid').to.equal('1234111'); - expect(testEvent.data.custom_flags['SnapchatConversions.ClickId'], 'Snapchat Click ID').to.equal('1234'); - expect(testEvent.data.custom_flags['SnapchatConversions.Cookie1'], 'Snapchat Cookie1').to.equal('cookie1-value'); - }); - - it('should add captured integrations to commerce event custom flags, prioritizing passed in flags', async () => { - await waitForCondition(hasIdentifyReturned); - - const product1 = mParticle.eCommerce.createProduct('iphone', 'iphoneSKU', 999, 1); - const product2 = mParticle.eCommerce.createProduct('galaxy', 'galaxySKU', 799, 1); - - const transactionAttributes = { - Id: 'foo-transaction-id', - Revenue: 430.00, - Tax: 30 - }; - - const customAttributes = {sale: true}; - const customFlags = { - 'Facebook.ClickId': 'passed-in' - }; - - mParticle.eCommerce.logProductAction( - mParticle.ProductActionType.Purchase, - [product1, product2], - customAttributes, - customFlags, - transactionAttributes); - - - const testEvent = findEventFromRequest(fetchMock.calls(), 'purchase'); - - expect(testEvent.data.product_action).to.have.property('action', 'purchase'); - expect(testEvent.data).to.have.property('custom_flags'); - - expect(testEvent.data.custom_flags['Facebook.ClickId'], 'Facebook Click Id').to.equal('passed-in'); - expect(testEvent.data.custom_flags['Facebook.BrowserId'], 'Facebook Browser Id').to.equal('54321'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gclid'], 'Google Enhanced Conversions Gclid').to.equal('234'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gbraid'], 'Google Enhanced Conversions Gbraid').to.equal('6574'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Wbraid'], 'Google Enhanced Conversions Wbraid').to.equal('1234111'); - expect(testEvent.data.custom_flags['SnapchatConversions.ClickId'], 'Snapchat Click ID').to.equal('1234'); - expect(testEvent.data.custom_flags['SnapchatConversions.Cookie1'], 'Snapchat Cookie1').to.equal('cookie1-value'); - }); - - it('should add captured integrations to commerce event custom flags', async () => { - await waitForCondition(hasIdentifyReturned); - - const product1 = mParticle.eCommerce.createProduct('iphone', 'iphoneSKU', 999, 1); - const product2 = mParticle.eCommerce.createProduct('galaxy', 'galaxySKU', 799, 1); - - const transactionAttributes = { - Id: 'foo-transaction-id', - Revenue: 430.00, - Tax: 30 - }; - - const customAttributes = {sale: true}; - const customFlags = {foo: 'bar'}; - - mParticle.eCommerce.logProductAction( - mParticle.ProductActionType.Purchase, - [product1, product2], - customAttributes, - customFlags, - transactionAttributes); - - const testEvent = findEventFromRequest(fetchMock.calls(), 'purchase'); - - const initialTimestamp = window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; - - expect(testEvent.data.product_action).to.have.property('action', 'purchase'); - expect(testEvent.data).to.have.property('custom_flags'); - - expect(testEvent.data.custom_flags['foo'], 'Custom Flag').to.equal('bar'); - expect(testEvent.data.custom_flags['Facebook.ClickId'], 'Facebook Click Id').to.equal(`fb.1.${initialTimestamp}.1234`); - expect(testEvent.data.custom_flags['Facebook.BrowserId'], 'Facebook Browser Id').to.equal('54321'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gclid'], 'Google Enhanced Conversions Gclid').to.equal('234'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gbraid'], 'Google Enhanced Conversions Gbraid').to.equal('6574'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Wbraid'], 'Google Enhanced Conversions Wbraid').to.equal('1234111'); - expect(testEvent.data.custom_flags['SnapchatConversions.ClickId'], 'Snapchat Click ID').to.equal('1234'); - expect(testEvent.data.custom_flags['SnapchatConversions.Cookie1'], 'Snapchat Cookie1').to.equal('cookie1-value'); + CAPTURE_CUSTOM_FLAG_CASES.forEach(({ title, kind, usePassedInFacebook }) => { + it(title, async () => { + await waitForCondition(hasIdentifyReturned); + + if (kind === 'event') { + if (usePassedInFacebook) { + mParticle.logEvent( + 'Test Event', + mParticle.EventType.Navigation, + { mykey: 'myvalue' }, + { 'Facebook.ClickId': 'passed-in' }, + ); + } else { + mParticle.logEvent( + 'Test Event', + mParticle.EventType.Navigation, + { mykey: 'myvalue' }, + ); + } + } else if (usePassedInFacebook) { + mParticle.logPageView( + 'Test Page View', + { 'foo-attr': 'bar-attr' }, + { 'Facebook.ClickId': 'passed-in' }, + ); + } else { + mParticle.logPageView('Test Page View', { 'foo-attr': 'bar-attr' }); + } + + const eventName = kind === 'event' ? 'Test Event' : 'Test Page View'; + const testEvent = findEventFromRequest(fetchMock.calls(), eventName); + const initialTimestamp = + window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; + + expect(testEvent).to.have.property('data'); + if (kind === 'event') { + expect(testEvent.data).to.have.property('event_name', 'Test Event'); + } else { + expect(testEvent.data).to.have.property('screen_name', 'Test Page View'); + } + expect(testEvent.data).to.have.property('custom_flags'); + + const facebookClickId = usePassedInFacebook + ? 'passed-in' + : `fb.1.${initialTimestamp}.1234`; + expectStubbedIntegrationCaptureFlags( + testEvent.data.custom_flags, + facebookClickId, + ); + }); }); - it('should add captured integrations to commerce event custom flags, prioritizing passed in flags', async () => { - await waitForCondition(hasIdentifyReturned); - - const product1 = mParticle.eCommerce.createProduct('iphone', 'iphoneSKU', 999, 1); - const product2 = mParticle.eCommerce.createProduct('galaxy', 'galaxySKU', 799, 1); - - const transactionAttributes = { - Id: 'foo-transaction-id', - Revenue: 430.00, - Tax: 30 - }; - - const customAttributes = {sale: true}; - const customFlags = { - 'Facebook.ClickId': 'passed-in' - }; - - mParticle.eCommerce.logProductAction( - mParticle.ProductActionType.Purchase, - [product1, product2], - customAttributes, - customFlags, - transactionAttributes); - - - const testEvent = findEventFromRequest(fetchMock.calls(), 'purchase'); - - expect(testEvent.data.product_action).to.have.property('action', 'purchase'); - expect(testEvent.data).to.have.property('custom_flags'); - - expect(testEvent.data.custom_flags['Facebook.ClickId'], 'Facebook Click Id').to.equal('passed-in'); - expect(testEvent.data.custom_flags['Facebook.BrowserId'], 'Facebook Browser Id').to.equal('54321'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gclid'], 'Google Enhanced Conversions Gclid').to.equal('234'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Gbraid'], 'Google Enhanced Conversions Gbraid').to.equal('6574'); - expect(testEvent.data.custom_flags['GoogleEnhancedConversions.Wbraid'], 'Google Enhanced Conversions Wbraid').to.equal('1234111'); - expect(testEvent.data.custom_flags['SnapchatConversions.ClickId'], 'Snapchat Click ID').to.equal('1234'); - expect(testEvent.data.custom_flags['SnapchatConversions.Cookie1'], 'Snapchat Cookie1').to.equal('cookie1-value'); - }); + COMMERCE_CAPTURE_CASES.forEach( + ({ title, customFlags, expectFooBar, usePassedInFacebook }) => { + it(title, async () => { + await waitForCondition(hasIdentifyReturned); + + const testEvent = logCommercePurchaseAndGetEvent(customFlags); + const initialTimestamp = + window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; + + expect(testEvent.data.product_action).to.have.property( + 'action', + 'purchase', + ); + expect(testEvent.data).to.have.property('custom_flags'); + + if (expectFooBar) { + expect(testEvent.data.custom_flags['foo'], 'Custom Flag').to.equal( + 'bar', + ); + } + + const facebookClickId = usePassedInFacebook + ? 'passed-in' + : `fb.1.${initialTimestamp}.1234`; + expectStubbedIntegrationCaptureFlags( + testEvent.data.custom_flags, + facebookClickId, + ); + }); + }, + ); it('should add captured integrations to batch as partner identities', async () => { await waitForCondition(hasIdentityCallInflightReturned); - window.mParticle.logEvent('Test Event 1'); - window.mParticle.logEvent('Test Event 2'); - window.mParticle.logEvent('Test Event 3'); - - window.mParticle.upload(); - - expect(fetchMock.calls().length).to.greaterThan(1); - - const lastCall = fetchMock.lastCall(); - const batch = JSON.parse(lastCall[1].body as string); + const batch = logThreeEventsUploadAndParseBatch(); expect(batch).to.have.property('partner_identities'); expect(batch.partner_identities).to.deep.equal({ @@ -346,16 +279,7 @@ describe('Integration Capture', () => { it('should add captured integrations to batch as integration attributes', async () => { await waitForCondition(hasIdentityCallInflightReturned); - window.mParticle.logEvent('Test Event 1'); - window.mParticle.logEvent('Test Event 2'); - window.mParticle.logEvent('Test Event 3'); - - window.mParticle.upload(); - - expect(fetchMock.calls().length).to.greaterThan(1); - - const lastCall = fetchMock.lastCall(); - const batch = JSON.parse(lastCall[1].body as string); + const batch = logThreeEventsUploadAndParseBatch(); expect(batch).to.have.property('integration_attributes'); expect(batch.integration_attributes['1277']).to.deep.equal({ @@ -368,16 +292,7 @@ describe('Integration Capture', () => { window.mParticle.setIntegrationAttribute(160, { 'client_id': '12354'}); - window.mParticle.logEvent('Test Event 1'); - window.mParticle.logEvent('Test Event 2'); - window.mParticle.logEvent('Test Event 3'); - - window.mParticle.upload(); - - expect(fetchMock.calls().length).to.greaterThan(1); - - const lastCall = fetchMock.lastCall(); - const batch = JSON.parse(lastCall[1].body as string); + const batch = logThreeEventsUploadAndParseBatch(); expect(batch).to.have.property('integration_attributes'); expect(batch.integration_attributes).to.have.property('1277'); @@ -397,16 +312,7 @@ describe('Integration Capture', () => { window.mParticle.setIntegrationAttribute(1277, { 'passbackconversiontrackingid': 'passed-in'}); window.mParticle.setIntegrationAttribute(160, { 'client_id': '12354'}); - window.mParticle.logEvent('Test Event 1'); - window.mParticle.logEvent('Test Event 2'); - window.mParticle.logEvent('Test Event 3'); - - window.mParticle.upload(); - - expect(fetchMock.calls().length).to.greaterThan(1); - - const lastCall = fetchMock.lastCall(); - const batch = JSON.parse(lastCall[1].body as string); + const batch = logThreeEventsUploadAndParseBatch(); expect(batch).to.have.property('integration_attributes'); expect(batch.integration_attributes['1277']).to.deep.equal({