From fcca29ea0c37c910f118bbb4f1036860536f1323 Mon Sep 17 00:00:00 2001 From: AdobeLinhart Date: Thu, 22 Aug 2024 12:57:01 -0600 Subject: [PATCH 01/15] Initial checkin - Working state. --- .../personalization/personalization.js | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index 4586cc158b..055684b581 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -541,7 +541,7 @@ export function parseManifestVariants(data, manifestPath, targetId) { } /* c8 ignore start */ -function parsePlaceholders(placeholders, config, selectedVariantName = '') { +function parsePlaceholders(placeholders, config, selectedVariantName = '') { if (!placeholders?.length || selectedVariantName === 'default') return config; const valueNames = [ selectedVariantName.toLowerCase(), @@ -561,9 +561,36 @@ function parsePlaceholders(placeholders, config, selectedVariantName = '') { }, {}); config.placeholders = { ...(config.placeholders || {}), ...results }; } + + setRegionalMetadata(placeholders, config) + return config; } +function setRegionalMetadata(placeholders, config) { + if (!config.locale.ietf !== 'en-US') { + // const metaEl = document.createElement('martech-metadata'); + let html = ''; + + Object.values(config.placeholders).forEach((item, i) => { + const usCol = placeholders[i]['en-us'] || placeholders[i]['us'] || placeholders[i]['en']; + + if(!usCol) return; // may not be necessary? + + html += ` +
+
${item}
+
${usCol}
+
+ `; + }); + + const metaEl = createTag('div', { class: 'martech-metadata' }, html); + document.head.appendChild(metaEl); + // Append to head or body? + } +} + const checkForParamMatch = (paramStr) => { const [name, val] = paramStr.split('param-')[1].split('='); if (!name) return false; From 488627babf6d854775646268fe27eaf0e9bdaa77 Mon Sep 17 00:00:00 2001 From: AdobeLinhart Date: Thu, 22 Aug 2024 15:47:34 -0600 Subject: [PATCH 02/15] Optimization. Good State. --- .../personalization/personalization.js | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index 055684b581..afef72d36f 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -562,33 +562,30 @@ function parsePlaceholders(placeholders, config, selectedVariantName = '') { config.placeholders = { ...(config.placeholders || {}), ...results }; } - setRegionalMetadata(placeholders, config) + createMartechMetadataBlock(placeholders, config) return config; } -function setRegionalMetadata(placeholders, config) { - if (!config.locale.ietf !== 'en-US') { - // const metaEl = document.createElement('martech-metadata'); - let html = ''; +function createMartechMetadataBlock(placeholders, config) { + if (config.locale.ietf === 'en-US') return; - Object.values(config.placeholders).forEach((item, i) => { - const usCol = placeholders[i]['en-us'] || placeholders[i]['us'] || placeholders[i]['en']; + const section = createTag('div'); + const metaEl = createTag('div', { class: 'martech-metadata' }); - if(!usCol) return; // may not be necessary? + section.append(metaEl); - html += ` -
-
${item}
-
${usCol}
-
- `; - }); + const firstRow = placeholders[0]; + const usCol = firstRow['en-us'] || firstRow['us'] || firstRow['en'] || firstRow['key']; + + if(!usCol) return; - const metaEl = createTag('div', { class: 'martech-metadata' }, html); - document.head.appendChild(metaEl); - // Append to head or body? - } + Object.values(config.placeholders).forEach((item, i) => { + const row = createTag('div', undefined, `
${item}
${usCol}
`); + metaEl.appendChild(row); + }); + + document.querySelector('main').appendChild(section); } const checkForParamMatch = (paramStr) => { From 3495e9c698f75c45ac7a8503c052f8ce8dd5fad4 Mon Sep 17 00:00:00 2001 From: AdobeLinhart Date: Thu, 22 Aug 2024 15:57:08 -0600 Subject: [PATCH 03/15] Semi-colon fix. --- libs/features/personalization/personalization.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index afef72d36f..7d3c0b0c4a 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -562,7 +562,7 @@ function parsePlaceholders(placeholders, config, selectedVariantName = '') { config.placeholders = { ...(config.placeholders || {}), ...results }; } - createMartechMetadataBlock(placeholders, config) + createMartechMetadataBlock(placeholders, config); return config; } From e915779a9e3cc8e910356dfb65b6a11b44d3d73b Mon Sep 17 00:00:00 2001 From: AdobeLinhart Date: Thu, 22 Aug 2024 16:07:29 -0600 Subject: [PATCH 04/15] Linting fixes. --- .../personalization/personalization.js | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index 7d3c0b0c4a..daec21ecb1 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -540,8 +540,29 @@ export function parseManifestVariants(data, manifestPath, targetId) { return null; } +function createMartechMetadataBlock(placeholders, config) { + if (config.locale.ietf === 'en-US') return; + + const section = createTag('div'); + const metaEl = createTag('div', { class: 'martech-metadata' }); + + section.append(metaEl); + + const firstRow = placeholders[0]; + const usCol = firstRow['en-us'] || firstRow.us || firstRow.en || firstRow.key; + + if (!usCol) return; + + Object.values(config.placeholders).forEach((item) => { + const row = createTag('div', undefined, `
${item}
${usCol}
`); + metaEl.appendChild(row); + }); + + document.querySelector('main').appendChild(section); +} + /* c8 ignore start */ -function parsePlaceholders(placeholders, config, selectedVariantName = '') { +function parsePlaceholders(placeholders, config, selectedVariantName = '') { if (!placeholders?.length || selectedVariantName === 'default') return config; const valueNames = [ selectedVariantName.toLowerCase(), @@ -567,27 +588,6 @@ function parsePlaceholders(placeholders, config, selectedVariantName = '') { return config; } -function createMartechMetadataBlock(placeholders, config) { - if (config.locale.ietf === 'en-US') return; - - const section = createTag('div'); - const metaEl = createTag('div', { class: 'martech-metadata' }); - - section.append(metaEl); - - const firstRow = placeholders[0]; - const usCol = firstRow['en-us'] || firstRow['us'] || firstRow['en'] || firstRow['key']; - - if(!usCol) return; - - Object.values(config.placeholders).forEach((item, i) => { - const row = createTag('div', undefined, `
${item}
${usCol}
`); - metaEl.appendChild(row); - }); - - document.querySelector('main').appendChild(section); -} - const checkForParamMatch = (paramStr) => { const [name, val] = paramStr.split('param-')[1].split('='); if (!name) return false; From 52287ed6b49ff2340857990149a12459a5e9033f Mon Sep 17 00:00:00 2001 From: AdobeLinhart Date: Fri, 23 Aug 2024 09:28:17 -0600 Subject: [PATCH 05/15] Unit test file creation. --- .../personalization/placeholders.test.js | 307 ++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 test/features/personalization/placeholders.test.js diff --git a/test/features/personalization/placeholders.test.js b/test/features/personalization/placeholders.test.js new file mode 100644 index 0000000000..4a8fee7eee --- /dev/null +++ b/test/features/personalization/placeholders.test.js @@ -0,0 +1,307 @@ +import { expect } from '@esm-bundle/chai'; +import { readFile } from '@web/test-runner-commands'; +import { stub } from 'sinon'; +import { getConfig, loadBlock } from '../../../libs/utils/utils.js'; +import initFragments from '../../../libs/blocks/fragment/fragment.js'; +import { init, handleFragmentCommand } from '../../../libs/features/personalization/personalization.js'; +import mepSettings from './mepSettings.js'; + +document.head.innerHTML = await readFile({ path: './mocks/metadata.html' }); +document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); + +// Add custom keys so tests doesn't rely on real data +const config = getConfig(); +config.env = { name: 'prod' }; + +const getFetchPromise = (data, type = 'json') => new Promise((resolve) => { + resolve({ + ok: true, + [type]: () => data, + }); +}); + +const setFetchResponse = (data, type = 'json') => { + window.fetch = stub().returns(getFetchPromise(data, type)); +}; + +// Note that the manifestPath doesn't matter as we stub the fetch +describe('replace action', () => { + it('with a CSS Selector, it should replace an element with a fragment', async () => { + let manifestJson = await readFile({ path: './mocks/actions/manifestReplace.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + expect(document.querySelector('#features-of-milo-experimentation-platform')).to.not.be.null; + expect(document.querySelector('.how-to')).to.not.be.null; + const parentEl = document.querySelector('#features-of-milo-experimentation-platform')?.parentElement; + + await init(mepSettings); + expect(document.querySelector('#features-of-milo-experimentation-platform')).to.be.null; + const el = parentEl.firstElementChild.firstElementChild; + expect(el.href) + .to.equal('http://localhost:2000/test/features/personalization/mocks/fragments/milo-replace-content-chrome-howto-h2'); + expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + // .how-to should not be changed as it is targeted to firefox + expect(document.querySelector('.how-to')).to.not.be.null; + }); + + it('with a fragment selector, it should replace a fragment in the document', async () => { + document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); + + let manifestJson = await readFile({ path: './mocks/actions/manifestReplace.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + expect(document.querySelector('a[href="/fragments/replaceme"]')).to.exist; + expect(document.querySelector('a[href="/fragments/inline-replaceme#_inline"]')).to.exist; + await init(mepSettings); + expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + + const fragmentResp = await readFile({ path: './mocks/fragments/fragmentReplaced.plain.html' }); + const inlineFragmentResp = await readFile({ path: './mocks/fragments/inlineFragReplaced.plain.html' }); + + window.fetch = stub(); + window.fetch.withArgs('http://localhost:2000/test/features/personalization/mocks/fragments/fragmentReplaced.plain.html') + .returns(getFetchPromise(fragmentResp, 'text')); + window.fetch.withArgs('http://localhost:2000/test/features/personalization/mocks/fragments/inlineFragReplaced.plain.html') + .returns(getFetchPromise(inlineFragmentResp, 'text')); + + const replacemeFrag = document.querySelector('a[href="/fragments/replaceme"]'); + await initFragments(replacemeFrag); + expect(document.querySelector('a[href="/fragments/replaceme"]')).to.be.null; + expect(document.querySelector('div[data-path="/test/features/personalization/mocks/fragments/fragmentReplaced"]')).to.exist; + + const inlineReplacemeFrag = document.querySelector('a[href="/fragments/inline-replaceme#_inline"]'); + await initFragments(inlineReplacemeFrag); + expect(document.querySelector('a[href="/fragments/inline-replaceme#_inline"]')).to.be.null; + expect(document.querySelector('.inlinefragmentreplaced')).to.exist; + }); +}); + +describe('insertAfter action', async () => { + it('insertContentAfter should add fragment after target content and fragment', async () => { + let manifestJson = await readFile({ path: './mocks/actions/manifestInsertAfter.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + expect(document.querySelector('a[href="/fragments/insertafter"]')).to.be.null; + expect(document.querySelector('a[href="/fragments/insertafterfragment"]')).to.be.null; + await init(mepSettings); + expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + + let fragment = document.querySelector('a[href="/test/features/personalization/mocks/fragments/insertafter"]'); + expect(fragment).to.not.be.null; + + expect(fragment.parentElement.previousElementSibling.className).to.equal('marquee'); + + fragment = document.querySelector('a[href="/test/features/personalization/mocks/fragments/insertafterfragment"]'); + expect(fragment).to.not.be.null; + + expect(fragment.parentElement.previousElementSibling.querySelector('a[href="/fragments/insertaround"]')).to.exist; + }); +}); + +describe('insertBefore action', async () => { + it('insertContentBefore should add fragment before target content and fragment', async () => { + let manifestJson = await readFile({ path: './mocks/actions/manifestInsertBefore.json' }); + + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + expect(document.querySelector('a[href="/fragments/insertbefore"]')).to.be.null; + await init(mepSettings); + expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + + let fragment = document.querySelector('a[href="/test/features/personalization/mocks/fragments/insertbefore"]'); + expect(fragment).to.not.be.null; + + expect(fragment.parentElement.parentElement.children[1].className).to.equal('marquee'); + + fragment = document.querySelector('a[href="/test/features/personalization/mocks/fragments/insertbeforefragment"]'); + expect(fragment).to.not.be.null; + + expect(fragment.parentElement.nextElementSibling.querySelector('a[href="/fragments/insertaround"]')).to.exist; + }); +}); + +describe('prependToSection action', async () => { + it('appendToSection should add fragment to beginning of section', async () => { + let manifestJson = await readFile({ path: './mocks/actions/manifestPrependToSection.json' }); + + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + expect(document.querySelector('a[href="/test/features/personalization/mocks/fragments/prependToSection"]')).to.be.null; + await init(mepSettings); + expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + + const fragment = document.querySelector('main > div:nth-child(2) > div:first-child a[href="/test/features/personalization/mocks/fragments/prependToSection"]'); + expect(fragment).to.not.be.null; + }); +}); + +describe('appendToSection action', async () => { + it('appendToSection should add fragment to end of section', async () => { + config.mep = { handleFragmentCommand }; + let manifestJson = await readFile({ path: './mocks/actions/manifestAppendToSection.json' }); + + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + expect(document.querySelector('a[href="/test/features/personalization/mocks/fragments/appendToSection"]')).to.be.null; + await init(mepSettings); + expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + + const fragment = document.querySelector('main > div:nth-child(2) > div:last-child a[href="/test/features/personalization/mocks/fragments/appendToSection"]'); + expect(fragment).to.not.be.null; + }); +}); + +describe('remove action', () => { + before(async () => { + let manifestJson = await readFile({ path: './mocks/actions/manifestRemove.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + mepSettings.mepButton = 'off'; + await init(mepSettings); + expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + }); + it('remove should remove content', async () => { + expect(document.querySelector('.z-pattern')).to.be.null; + }); + + it('remove should remove fragment', async () => { + const removeMeFrag = document.querySelector('a[href="/fragments/removeme"]'); + await initFragments(removeMeFrag); + expect(document.querySelector('a[href="/fragments/removeme"]')).to.be.null; + }); + + it('removeContent should tag but not remove content in preview', async () => { + document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); + + let manifestJson = await readFile({ path: './mocks/actions/manifestRemove.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + setTimeout(async () => { + expect(document.querySelector('.z-pattern')).to.not.be.null; + mepSettings.mepButton = false; + await init(mepSettings); + expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + + expect(document.querySelector('.z-pattern')).to.not.be.null; + expect(document.querySelector('.z-pattern').dataset.removedManifestId).to.not.be.null; + + const removeMeFrag = document.querySelector('a[href="/fragments/removeme"]'); + await initFragments(removeMeFrag); + expect(document.querySelector('a[href="/fragments/removeme"]')).to.not.be.null; + expect(document.querySelector('a[href="/fragments/removeme"]').dataset.removedManifestId).to.not.be.null; + }, 50); + }); +}); + +describe('useBlockCode action', async () => { + it('useBlockCode should override a current block with the custom block code provided', async () => { + let manifestJson = await readFile({ path: './mocks/actions/manifestUseBlockCode.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + await init(mepSettings); + expect(getConfig().mep.experiments[0].selectedVariant.useblockcode[0] + .targetManifestId).to.equal(false); + + expect(getConfig().mep.blocks).to.deep.equal({ promo: 'http://localhost:2000/test/features/personalization/mocks/promo' }); + const promoBlock = document.querySelector('.promo'); + expect(promoBlock.textContent?.trim()).to.equal('Old Promo Block'); + await loadBlock(promoBlock); + expect(promoBlock.textContent?.trim()).to.equal('New Promo!'); + }); + + it('useBlockCode should be able to use a new type of block', async () => { + let manifestJson = await readFile({ path: './mocks/actions/manifestUseBlockCode2.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + await init(mepSettings); + expect(getConfig().mep.experiments[0].selectedVariant.useblockcode[0] + .targetManifestId).to.equal(false); + + expect(getConfig().mep.blocks).to.deep.equal({ myblock: 'http://localhost:2000/test/features/personalization/mocks/myblock' }); + const myBlock = document.querySelector('.myblock'); + expect(myBlock.textContent?.trim()).to.equal('This block does not exist'); + await loadBlock(myBlock); + expect(myBlock.textContent?.trim()).to.equal('My New Block!'); + }); +}); + +describe('custom actions', async () => { + it('should not add custom configuration if not needed', async () => { + let manifestJson = await readFile({ path: './mocks/actions/manifestReplace.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + await init(mepSettings); + expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + expect(getConfig().mep.custom).to.be.undefined; + }); + + it('should add a custom action configuration', async () => { + let manifestJson = await readFile({ path: './mocks/actions/manifestCustomAction.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + await init(mepSettings); + expect(getConfig().mep.inBlock).to.deep.equal({ + 'my-block': { + commands: [{ + action: 'replace', + target: '/fragments/fragmentreplaced', + manifestId: false, + targetManifestId: false, + }, + { + action: 'replace', + target: '/fragments/new-large-menu', + manifestId: false, + selector: '.large-menu', + targetManifestId: false, + }], + fragments: { + '/fragments/sub-menu': { + action: 'replace', + target: '/fragments/even-more-new-sub-menu', + manifestId: false, + targetManifestId: false, + }, + '/fragments/new-sub-menu': { + action: 'replace', + target: '/fragments/even-more-new-sub-menu', + manifestId: false, + targetManifestId: false, + }, + }, + }, + }); + }); + + it('Only fragments in the first section should be preloaded', async () => { + document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); + + let manifestJson = await readFile({ path: './mocks/actions/manifestPreloadFrags.json' }); + manifestJson = JSON.parse(manifestJson); + setFetchResponse(manifestJson); + + // This fragment is in the 1st section and should be preloaded + const lcpLink = 'link[href^="/test/features/personalization/mocks/fragments/fragmentReplaced"]'; + + // This fragment is in the 3rd section and should not be preloaded + const notLcpLink = 'link[href^="/test/features/personalization/mocks/fragments/inlineFragReplaced"]'; + + expect(document.querySelector(lcpLink)).not.to.exist; + expect(document.querySelector(notLcpLink)).not.to.exist; + + await init(mepSettings); + + expect(document.querySelector(lcpLink)).to.exist; + expect(document.querySelector(notLcpLink)).not.to.exist; + }); +}); From e13156471875b96e26fafe5629d87d0e47f7330a Mon Sep 17 00:00:00 2001 From: AdobeLinhart Date: Mon, 26 Aug 2024 17:49:49 -0600 Subject: [PATCH 06/15] Placeholders update/fix. --- .../personalization/personalization.js | 24 +++++++------------ libs/martech/attributes.js | 10 ++++++-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index daec21ecb1..5168152ab7 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -540,25 +540,17 @@ export function parseManifestVariants(data, manifestPath, targetId) { return null; } -function createMartechMetadataBlock(placeholders, config) { +function createMartechMetadataBlock(placeholders, config, column) { if (config.locale.ietf === 'en-US') return; + config.mep.analyticLocalization ??= {}; - const section = createTag('div'); - const metaEl = createTag('div', { class: 'martech-metadata' }); + placeholders.forEach((item, i) => { + const firstRow = placeholders[i]; + const usCol = firstRow['en-us'] || firstRow.us || firstRow.en || firstRow.key; - section.append(metaEl); - - const firstRow = placeholders[0]; - const usCol = firstRow['en-us'] || firstRow.us || firstRow.en || firstRow.key; - - if (!usCol) return; - - Object.values(config.placeholders).forEach((item) => { - const row = createTag('div', undefined, `
${item}
${usCol}
`); - metaEl.appendChild(row); + if (!usCol) return; + config.mep.analyticLocalization[item[column]] = usCol; }); - - document.querySelector('main').appendChild(section); } /* c8 ignore start */ @@ -583,7 +575,7 @@ function parsePlaceholders(placeholders, config, selectedVariantName = '') { config.placeholders = { ...(config.placeholders || {}), ...results }; } - createMartechMetadataBlock(placeholders, config); + createMartechMetadataBlock(placeholders, config, val); return config; } diff --git a/libs/martech/attributes.js b/libs/martech/attributes.js index 9bb2b3dc5f..b561aa6d43 100644 --- a/libs/martech/attributes.js +++ b/libs/martech/attributes.js @@ -4,8 +4,14 @@ const LEAD_UNDERSCORES = /^_+|_+$/g; export function processTrackingLabels(text, config, charLimit) { let analyticsValue = text?.replace(INVALID_CHARACTERS, ' ').replace(LEAD_UNDERSCORES, '').trim(); if (config) { - const { analyticLocalization, loc = analyticLocalization?.[analyticsValue] } = config; - if (loc) analyticsValue = loc; + const { analyticLocalization, mepLoc = analyticLocalization?.[analyticsValue] } = config.mep; + + if (mepLoc) { + analyticsValue = mepLoc; + } else { + const { analyticLocalization, loc = analyticLocalization?.[analyticsValue] } = config; + if (loc) analyticsValue = loc; + } } if (charLimit) return analyticsValue.slice(0, charLimit); return analyticsValue; From 48952e6e9bebeff728757d943fb634f942c1f34b Mon Sep 17 00:00:00 2001 From: AdobeLinhart Date: Tue, 27 Aug 2024 12:35:12 -0600 Subject: [PATCH 07/15] Fixed US data values. --- .../personalization/personalization.js | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index 5168152ab7..e57c838a74 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -540,17 +540,23 @@ export function parseManifestVariants(data, manifestPath, targetId) { return null; } -function createMartechMetadataBlock(placeholders, config, column) { +function createMartechMetadata(placeholders, config, column) { if (config.locale.ietf === 'en-US') return; - config.mep.analyticLocalization ??= {}; - placeholders.forEach((item, i) => { - const firstRow = placeholders[i]; - const usCol = firstRow['en-us'] || firstRow.us || firstRow.en || firstRow.key; + import('../../martech/attributes.js').then(({processTrackingLabels}) => { + config.mep.analyticLocalization ??= {}; - if (!usCol) return; - config.mep.analyticLocalization[item[column]] = usCol; - }); + placeholders.forEach((item, i) => { + const firstRow = placeholders[i]; + let usValue = firstRow['en-us'] || firstRow.us || firstRow.en || firstRow.key; + + if (!usValue) return; + + usValue = processTrackingLabels(usValue); + const translatedValue = processTrackingLabels(item[column]); + config.mep.analyticLocalization[translatedValue] = usValue; + }); + }); } /* c8 ignore start */ @@ -575,7 +581,7 @@ function parsePlaceholders(placeholders, config, selectedVariantName = '') { config.placeholders = { ...(config.placeholders || {}), ...results }; } - createMartechMetadataBlock(placeholders, config, val); + createMartechMetadata(placeholders, config, val); return config; } From f8e7bf5fc803bc3533178d8cd869960bfeb26ae5 Mon Sep 17 00:00:00 2001 From: AdobeLinhart Date: Tue, 27 Aug 2024 13:33:13 -0600 Subject: [PATCH 08/15] Unit testing WIP. --- .../personalization/personalization.js | 8 ++- .../createMartechMetadata.test.js | 70 +++++++++++++++++++ .../personalization/mocks/placeholders.js | 49 +++++++++++++ 3 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 test/features/personalization/createMartechMetadata.test.js create mode 100644 test/features/personalization/mocks/placeholders.js diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index e57c838a74..c9d78dad34 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -540,10 +540,10 @@ export function parseManifestVariants(data, manifestPath, targetId) { return null; } -function createMartechMetadata(placeholders, config, column) { +export function createMartechMetadata(placeholders, config, column) { if (config.locale.ietf === 'en-US') return; - import('../../martech/attributes.js').then(({processTrackingLabels}) => { + import('../../martech/attributes.js').then(({ processTrackingLabels }) => { config.mep.analyticLocalization ??= {}; placeholders.forEach((item, i) => { @@ -556,7 +556,9 @@ function createMartechMetadata(placeholders, config, column) { const translatedValue = processTrackingLabels(item[column]); config.mep.analyticLocalization[translatedValue] = usValue; }); - }); + + console.log(getConfig().mep.analyticLocalization); + }); } /* c8 ignore start */ diff --git a/test/features/personalization/createMartechMetadata.test.js b/test/features/personalization/createMartechMetadata.test.js new file mode 100644 index 0000000000..10087a6f8f --- /dev/null +++ b/test/features/personalization/createMartechMetadata.test.js @@ -0,0 +1,70 @@ +import { expect } from '@esm-bundle/chai'; +import { readFile } from '@web/test-runner-commands'; +import { stub } from 'sinon'; +import { getConfig, loadBlock } from '../../../libs/utils/utils.js'; +import initFragments from '../../../libs/blocks/fragment/fragment.js'; +import { init, createMartechMetadata } from '../../../libs/features/personalization/personalization.js'; +import mepSettings from './mepSettings.js'; +import placeholders from './mocks/placeholders.js'; + +document.head.innerHTML = await readFile({ path: './mocks/metadata.html' }); +document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); + +// Rest of the code... +const config = getConfig(); +config.locale = { ietf: 'fr-fr' }; +config.mep = {}; + +const getFetchPromise = (data, type = 'json') => new Promise((resolve) => { + resolve({ + ok: true, + [type]: () => data, + }); +}); + +const setFetchResponse = (data, type = 'json') => { + window.fetch = stub().returns(getFetchPromise(data, type)); +}; + +// Note that the manifestPath doesn't matter as we stub the fetch +describe('replace action', () => { + it('testing create martech metadata output', async () => { + // let { analyticLocalization } = getConfig().mep; + // expect(getConfig().mep).to.deep.equal({}); + + createMartechMetadata(placeholders, config, 'fr'); + + console.log(getConfig().mep.analyticLocalization); + + expect(getConfig().mep.analyticLocalization).to.deep.equal({ + locale: { ietf: 'fr-fr' }, + mep: { + analyticLocalization: { + 'value1 fr': 'value1 en us', + 'value2 fr': 'value2 en us', + 'bonjour fr': 'Hello en us', + 'buy now fr': 'buy now en us', + 'try now fr': 'try now en us', + }, + }, + }); + // analyticLocalization = getConfig().mep.analyticLocalization; + + // let manifestJson = await readFile({ path: './mocks/actions/manifestReplace.json' }); + // manifestJson = JSON.parse(manifestJson); + // setFetchResponse(manifestJson); + + // expect(document.querySelector('#features-of-milo-experimentation-platform')).to.not.be.null; + // expect(document.querySelector('.how-to')).to.not.be.null; + // const parentEl = document.querySelector('#features-of-milo-experimentation-platform')?.parentElement; + + // await init(mepSettings); + // expect(document.querySelector('#features-of-milo-experimentation-platform')).to.be.null; + // const el = parentEl.firstElementChild.firstElementChild; + // expect(el.href) + // .to.equal('http://localhost:2000/test/features/personalization/mocks/fragments/milo-replace-content-chrome-howto-h2'); + // expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); + // // .how-to should not be changed as it is targeted to firefox + // expect(document.querySelector('.how-to')).to.not.be.null; + }); +}); diff --git a/test/features/personalization/mocks/placeholders.js b/test/features/personalization/mocks/placeholders.js new file mode 100644 index 0000000000..03a42937b6 --- /dev/null +++ b/test/features/personalization/mocks/placeholders.js @@ -0,0 +1,49 @@ +const placeholders = [ + { + key: 'test-placeholder', + 'mobile-device & us': 'US Mobile Value', + us: 'value1-us', + 'en-us': 'value1-en-us', + 'ca & not fr': 'value1-ca-not-fr', + fr: 'value1-fr', + 'mobile-device': 'value1-mobile', + }, + { + key: 'test-placeholder2', + 'mobile-device & us': 'US Mobile Value2', + us: 'value2-us', + 'en-us': 'value2-en-us', + 'ca & not fr': 'value2-ca-not-fr', + fr: 'value2-fr', + 'mobile-device': 'value2-mobile', + }, + { + key: 'marquee-headline', + 'mobile-device & us': 'hello US mobile', + us: 'hello-us', + 'en-us': 'Hello-en-us', + 'ca & not fr': 'hello-ca-not-fr', + fr: 'bonjour-fr', + 'mobile-device': 'hello-mobile', + }, + { + key: 'marquee-hollow', + 'mobile-device & us': 'buy-now-mobile-us', + us: 'buy-now-us', + 'en-us': 'buy-now-en-us', + 'ca & not fr': 'buy-now-ca-not-fr', + fr: 'buy-now-fr', + 'mobile-device': 'buy-now-mobile', + }, + { + key: 'marquee-solid', + 'mobile-device & us': 'try-now-mobile-us', + us: 'try-now-us', + 'en-us': 'try-now-en-us', + 'ca & not fr': 'try-now-ca-not-fr', + fr: 'try-now-fr', + 'mobile-device': 'try-now-mobile', + }, +]; + +export default placeholders; From c849df0fdf98ae388d42fbf45bd6abb4490e30bf Mon Sep 17 00:00:00 2001 From: vgoodric Date: Tue, 27 Aug 2024 14:12:46 -0600 Subject: [PATCH 09/15] fix unit test --- .../personalization/personalization.js | 6 +- .../createMartechMetadata.test.js | 72 ++++--------------- 2 files changed, 15 insertions(+), 63 deletions(-) diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js index c9d78dad34..0eb1aeacb3 100644 --- a/libs/features/personalization/personalization.js +++ b/libs/features/personalization/personalization.js @@ -540,10 +540,10 @@ export function parseManifestVariants(data, manifestPath, targetId) { return null; } -export function createMartechMetadata(placeholders, config, column) { +export async function createMartechMetadata(placeholders, config, column) { if (config.locale.ietf === 'en-US') return; - import('../../martech/attributes.js').then(({ processTrackingLabels }) => { + await import('../../martech/attributes.js').then(({ processTrackingLabels }) => { config.mep.analyticLocalization ??= {}; placeholders.forEach((item, i) => { @@ -556,8 +556,6 @@ export function createMartechMetadata(placeholders, config, column) { const translatedValue = processTrackingLabels(item[column]); config.mep.analyticLocalization[translatedValue] = usValue; }); - - console.log(getConfig().mep.analyticLocalization); }); } diff --git a/test/features/personalization/createMartechMetadata.test.js b/test/features/personalization/createMartechMetadata.test.js index 10087a6f8f..76ce753258 100644 --- a/test/features/personalization/createMartechMetadata.test.js +++ b/test/features/personalization/createMartechMetadata.test.js @@ -1,70 +1,24 @@ import { expect } from '@esm-bundle/chai'; -import { readFile } from '@web/test-runner-commands'; -import { stub } from 'sinon'; -import { getConfig, loadBlock } from '../../../libs/utils/utils.js'; -import initFragments from '../../../libs/blocks/fragment/fragment.js'; -import { init, createMartechMetadata } from '../../../libs/features/personalization/personalization.js'; -import mepSettings from './mepSettings.js'; +import { createMartechMetadata } from '../../../libs/features/personalization/personalization.js'; import placeholders from './mocks/placeholders.js'; -document.head.innerHTML = await readFile({ path: './mocks/metadata.html' }); -document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); - -// Rest of the code... -const config = getConfig(); -config.locale = { ietf: 'fr-fr' }; -config.mep = {}; - -const getFetchPromise = (data, type = 'json') => new Promise((resolve) => { - resolve({ - ok: true, - [type]: () => data, - }); -}); - -const setFetchResponse = (data, type = 'json') => { - window.fetch = stub().returns(getFetchPromise(data, type)); +const config = { + locale: { ietf: 'fr-fr' }, + mep: {}, }; // Note that the manifestPath doesn't matter as we stub the fetch describe('replace action', () => { it('testing create martech metadata output', async () => { - // let { analyticLocalization } = getConfig().mep; - // expect(getConfig().mep).to.deep.equal({}); - - createMartechMetadata(placeholders, config, 'fr'); - - console.log(getConfig().mep.analyticLocalization); - - expect(getConfig().mep.analyticLocalization).to.deep.equal({ - locale: { ietf: 'fr-fr' }, - mep: { - analyticLocalization: { - 'value1 fr': 'value1 en us', - 'value2 fr': 'value2 en us', - 'bonjour fr': 'Hello en us', - 'buy now fr': 'buy now en us', - 'try now fr': 'try now en us', - }, - }, + expect(config.mep).to.deep.equal({}); + + await createMartechMetadata(placeholders, config, 'fr'); + expect(config.mep.analyticLocalization).to.deep.equal({ + 'value1 fr': 'value1 en us', + 'value2 fr': 'value2 en us', + 'bonjour fr': 'Hello en us', + 'buy now fr': 'buy now en us', + 'try now fr': 'try now en us', }); - // analyticLocalization = getConfig().mep.analyticLocalization; - - // let manifestJson = await readFile({ path: './mocks/actions/manifestReplace.json' }); - // manifestJson = JSON.parse(manifestJson); - // setFetchResponse(manifestJson); - - // expect(document.querySelector('#features-of-milo-experimentation-platform')).to.not.be.null; - // expect(document.querySelector('.how-to')).to.not.be.null; - // const parentEl = document.querySelector('#features-of-milo-experimentation-platform')?.parentElement; - - // await init(mepSettings); - // expect(document.querySelector('#features-of-milo-experimentation-platform')).to.be.null; - // const el = parentEl.firstElementChild.firstElementChild; - // expect(el.href) - // .to.equal('http://localhost:2000/test/features/personalization/mocks/fragments/milo-replace-content-chrome-howto-h2'); - // expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - // // .how-to should not be changed as it is targeted to firefox - // expect(document.querySelector('.how-to')).to.not.be.null; }); }); From dc6787038f7dc34ce543e07f862cc26c69cf0a94 Mon Sep 17 00:00:00 2001 From: vgoodric Date: Tue, 27 Aug 2024 14:22:56 -0600 Subject: [PATCH 10/15] unit test working --- .../createMartechMetadata.test.js | 10 + .../personalization/mocks/placeholders-new.js | 22 ++ .../personalization/placeholders.test.js | 307 ------------------ 3 files changed, 32 insertions(+), 307 deletions(-) create mode 100644 test/features/personalization/mocks/placeholders-new.js delete mode 100644 test/features/personalization/placeholders.test.js diff --git a/test/features/personalization/createMartechMetadata.test.js b/test/features/personalization/createMartechMetadata.test.js index 76ce753258..a3418015e1 100644 --- a/test/features/personalization/createMartechMetadata.test.js +++ b/test/features/personalization/createMartechMetadata.test.js @@ -1,6 +1,7 @@ import { expect } from '@esm-bundle/chai'; import { createMartechMetadata } from '../../../libs/features/personalization/personalization.js'; import placeholders from './mocks/placeholders.js'; +import newPlaceholders from './mocks/placeholders-new.js'; const config = { locale: { ietf: 'fr-fr' }, @@ -20,5 +21,14 @@ describe('replace action', () => { 'buy now fr': 'buy now en us', 'try now fr': 'try now en us', }); + await createMartechMetadata(newPlaceholders, config, 'fr'); + expect(config.mep.analyticLocalization).to.deep.equal({ + 'new fr': 'new en us', + 'value1 fr': 'value1 en us', + 'value2 fr': 'new2 en us', + 'bonjour fr': 'Hello en us', + 'buy now fr': 'buy now en us', + 'try now fr': 'try now en us', + }); }); }); diff --git a/test/features/personalization/mocks/placeholders-new.js b/test/features/personalization/mocks/placeholders-new.js new file mode 100644 index 0000000000..06c9c7352b --- /dev/null +++ b/test/features/personalization/mocks/placeholders-new.js @@ -0,0 +1,22 @@ +const newPlaceholders = [ + { + key: 'test-placeholder', + 'mobile-device & us': 'US Mobile Value', + us: 'value1-us', + 'en-us': 'new-en-us', + 'ca & not fr': 'value1-ca-not-fr', + fr: 'new-fr', + 'mobile-device': 'value1-mobile', + }, + { + key: 'test-placeholder2', + 'mobile-device & us': 'US Mobile Value2', + us: 'value2-us', + 'en-us': 'new2-en-us', + 'ca & not fr': 'value2-ca-not-fr', + fr: 'value2-fr', + 'mobile-device': 'value2-mobile', + }, +]; + +export default newPlaceholders; diff --git a/test/features/personalization/placeholders.test.js b/test/features/personalization/placeholders.test.js deleted file mode 100644 index 4a8fee7eee..0000000000 --- a/test/features/personalization/placeholders.test.js +++ /dev/null @@ -1,307 +0,0 @@ -import { expect } from '@esm-bundle/chai'; -import { readFile } from '@web/test-runner-commands'; -import { stub } from 'sinon'; -import { getConfig, loadBlock } from '../../../libs/utils/utils.js'; -import initFragments from '../../../libs/blocks/fragment/fragment.js'; -import { init, handleFragmentCommand } from '../../../libs/features/personalization/personalization.js'; -import mepSettings from './mepSettings.js'; - -document.head.innerHTML = await readFile({ path: './mocks/metadata.html' }); -document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); - -// Add custom keys so tests doesn't rely on real data -const config = getConfig(); -config.env = { name: 'prod' }; - -const getFetchPromise = (data, type = 'json') => new Promise((resolve) => { - resolve({ - ok: true, - [type]: () => data, - }); -}); - -const setFetchResponse = (data, type = 'json') => { - window.fetch = stub().returns(getFetchPromise(data, type)); -}; - -// Note that the manifestPath doesn't matter as we stub the fetch -describe('replace action', () => { - it('with a CSS Selector, it should replace an element with a fragment', async () => { - let manifestJson = await readFile({ path: './mocks/actions/manifestReplace.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - expect(document.querySelector('#features-of-milo-experimentation-platform')).to.not.be.null; - expect(document.querySelector('.how-to')).to.not.be.null; - const parentEl = document.querySelector('#features-of-milo-experimentation-platform')?.parentElement; - - await init(mepSettings); - expect(document.querySelector('#features-of-milo-experimentation-platform')).to.be.null; - const el = parentEl.firstElementChild.firstElementChild; - expect(el.href) - .to.equal('http://localhost:2000/test/features/personalization/mocks/fragments/milo-replace-content-chrome-howto-h2'); - expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - // .how-to should not be changed as it is targeted to firefox - expect(document.querySelector('.how-to')).to.not.be.null; - }); - - it('with a fragment selector, it should replace a fragment in the document', async () => { - document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); - - let manifestJson = await readFile({ path: './mocks/actions/manifestReplace.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - expect(document.querySelector('a[href="/fragments/replaceme"]')).to.exist; - expect(document.querySelector('a[href="/fragments/inline-replaceme#_inline"]')).to.exist; - await init(mepSettings); - expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - - const fragmentResp = await readFile({ path: './mocks/fragments/fragmentReplaced.plain.html' }); - const inlineFragmentResp = await readFile({ path: './mocks/fragments/inlineFragReplaced.plain.html' }); - - window.fetch = stub(); - window.fetch.withArgs('http://localhost:2000/test/features/personalization/mocks/fragments/fragmentReplaced.plain.html') - .returns(getFetchPromise(fragmentResp, 'text')); - window.fetch.withArgs('http://localhost:2000/test/features/personalization/mocks/fragments/inlineFragReplaced.plain.html') - .returns(getFetchPromise(inlineFragmentResp, 'text')); - - const replacemeFrag = document.querySelector('a[href="/fragments/replaceme"]'); - await initFragments(replacemeFrag); - expect(document.querySelector('a[href="/fragments/replaceme"]')).to.be.null; - expect(document.querySelector('div[data-path="/test/features/personalization/mocks/fragments/fragmentReplaced"]')).to.exist; - - const inlineReplacemeFrag = document.querySelector('a[href="/fragments/inline-replaceme#_inline"]'); - await initFragments(inlineReplacemeFrag); - expect(document.querySelector('a[href="/fragments/inline-replaceme#_inline"]')).to.be.null; - expect(document.querySelector('.inlinefragmentreplaced')).to.exist; - }); -}); - -describe('insertAfter action', async () => { - it('insertContentAfter should add fragment after target content and fragment', async () => { - let manifestJson = await readFile({ path: './mocks/actions/manifestInsertAfter.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - expect(document.querySelector('a[href="/fragments/insertafter"]')).to.be.null; - expect(document.querySelector('a[href="/fragments/insertafterfragment"]')).to.be.null; - await init(mepSettings); - expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - - let fragment = document.querySelector('a[href="/test/features/personalization/mocks/fragments/insertafter"]'); - expect(fragment).to.not.be.null; - - expect(fragment.parentElement.previousElementSibling.className).to.equal('marquee'); - - fragment = document.querySelector('a[href="/test/features/personalization/mocks/fragments/insertafterfragment"]'); - expect(fragment).to.not.be.null; - - expect(fragment.parentElement.previousElementSibling.querySelector('a[href="/fragments/insertaround"]')).to.exist; - }); -}); - -describe('insertBefore action', async () => { - it('insertContentBefore should add fragment before target content and fragment', async () => { - let manifestJson = await readFile({ path: './mocks/actions/manifestInsertBefore.json' }); - - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - expect(document.querySelector('a[href="/fragments/insertbefore"]')).to.be.null; - await init(mepSettings); - expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - - let fragment = document.querySelector('a[href="/test/features/personalization/mocks/fragments/insertbefore"]'); - expect(fragment).to.not.be.null; - - expect(fragment.parentElement.parentElement.children[1].className).to.equal('marquee'); - - fragment = document.querySelector('a[href="/test/features/personalization/mocks/fragments/insertbeforefragment"]'); - expect(fragment).to.not.be.null; - - expect(fragment.parentElement.nextElementSibling.querySelector('a[href="/fragments/insertaround"]')).to.exist; - }); -}); - -describe('prependToSection action', async () => { - it('appendToSection should add fragment to beginning of section', async () => { - let manifestJson = await readFile({ path: './mocks/actions/manifestPrependToSection.json' }); - - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - expect(document.querySelector('a[href="/test/features/personalization/mocks/fragments/prependToSection"]')).to.be.null; - await init(mepSettings); - expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - - const fragment = document.querySelector('main > div:nth-child(2) > div:first-child a[href="/test/features/personalization/mocks/fragments/prependToSection"]'); - expect(fragment).to.not.be.null; - }); -}); - -describe('appendToSection action', async () => { - it('appendToSection should add fragment to end of section', async () => { - config.mep = { handleFragmentCommand }; - let manifestJson = await readFile({ path: './mocks/actions/manifestAppendToSection.json' }); - - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - expect(document.querySelector('a[href="/test/features/personalization/mocks/fragments/appendToSection"]')).to.be.null; - await init(mepSettings); - expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - - const fragment = document.querySelector('main > div:nth-child(2) > div:last-child a[href="/test/features/personalization/mocks/fragments/appendToSection"]'); - expect(fragment).to.not.be.null; - }); -}); - -describe('remove action', () => { - before(async () => { - let manifestJson = await readFile({ path: './mocks/actions/manifestRemove.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - mepSettings.mepButton = 'off'; - await init(mepSettings); - expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - }); - it('remove should remove content', async () => { - expect(document.querySelector('.z-pattern')).to.be.null; - }); - - it('remove should remove fragment', async () => { - const removeMeFrag = document.querySelector('a[href="/fragments/removeme"]'); - await initFragments(removeMeFrag); - expect(document.querySelector('a[href="/fragments/removeme"]')).to.be.null; - }); - - it('removeContent should tag but not remove content in preview', async () => { - document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); - - let manifestJson = await readFile({ path: './mocks/actions/manifestRemove.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - setTimeout(async () => { - expect(document.querySelector('.z-pattern')).to.not.be.null; - mepSettings.mepButton = false; - await init(mepSettings); - expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - - expect(document.querySelector('.z-pattern')).to.not.be.null; - expect(document.querySelector('.z-pattern').dataset.removedManifestId).to.not.be.null; - - const removeMeFrag = document.querySelector('a[href="/fragments/removeme"]'); - await initFragments(removeMeFrag); - expect(document.querySelector('a[href="/fragments/removeme"]')).to.not.be.null; - expect(document.querySelector('a[href="/fragments/removeme"]').dataset.removedManifestId).to.not.be.null; - }, 50); - }); -}); - -describe('useBlockCode action', async () => { - it('useBlockCode should override a current block with the custom block code provided', async () => { - let manifestJson = await readFile({ path: './mocks/actions/manifestUseBlockCode.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - await init(mepSettings); - expect(getConfig().mep.experiments[0].selectedVariant.useblockcode[0] - .targetManifestId).to.equal(false); - - expect(getConfig().mep.blocks).to.deep.equal({ promo: 'http://localhost:2000/test/features/personalization/mocks/promo' }); - const promoBlock = document.querySelector('.promo'); - expect(promoBlock.textContent?.trim()).to.equal('Old Promo Block'); - await loadBlock(promoBlock); - expect(promoBlock.textContent?.trim()).to.equal('New Promo!'); - }); - - it('useBlockCode should be able to use a new type of block', async () => { - let manifestJson = await readFile({ path: './mocks/actions/manifestUseBlockCode2.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - await init(mepSettings); - expect(getConfig().mep.experiments[0].selectedVariant.useblockcode[0] - .targetManifestId).to.equal(false); - - expect(getConfig().mep.blocks).to.deep.equal({ myblock: 'http://localhost:2000/test/features/personalization/mocks/myblock' }); - const myBlock = document.querySelector('.myblock'); - expect(myBlock.textContent?.trim()).to.equal('This block does not exist'); - await loadBlock(myBlock); - expect(myBlock.textContent?.trim()).to.equal('My New Block!'); - }); -}); - -describe('custom actions', async () => { - it('should not add custom configuration if not needed', async () => { - let manifestJson = await readFile({ path: './mocks/actions/manifestReplace.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - await init(mepSettings); - expect(getConfig().mep.commands[0].targetManifestId).to.equal(false); - expect(getConfig().mep.custom).to.be.undefined; - }); - - it('should add a custom action configuration', async () => { - let manifestJson = await readFile({ path: './mocks/actions/manifestCustomAction.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - await init(mepSettings); - expect(getConfig().mep.inBlock).to.deep.equal({ - 'my-block': { - commands: [{ - action: 'replace', - target: '/fragments/fragmentreplaced', - manifestId: false, - targetManifestId: false, - }, - { - action: 'replace', - target: '/fragments/new-large-menu', - manifestId: false, - selector: '.large-menu', - targetManifestId: false, - }], - fragments: { - '/fragments/sub-menu': { - action: 'replace', - target: '/fragments/even-more-new-sub-menu', - manifestId: false, - targetManifestId: false, - }, - '/fragments/new-sub-menu': { - action: 'replace', - target: '/fragments/even-more-new-sub-menu', - manifestId: false, - targetManifestId: false, - }, - }, - }, - }); - }); - - it('Only fragments in the first section should be preloaded', async () => { - document.body.innerHTML = await readFile({ path: './mocks/personalization.html' }); - - let manifestJson = await readFile({ path: './mocks/actions/manifestPreloadFrags.json' }); - manifestJson = JSON.parse(manifestJson); - setFetchResponse(manifestJson); - - // This fragment is in the 1st section and should be preloaded - const lcpLink = 'link[href^="/test/features/personalization/mocks/fragments/fragmentReplaced"]'; - - // This fragment is in the 3rd section and should not be preloaded - const notLcpLink = 'link[href^="/test/features/personalization/mocks/fragments/inlineFragReplaced"]'; - - expect(document.querySelector(lcpLink)).not.to.exist; - expect(document.querySelector(notLcpLink)).not.to.exist; - - await init(mepSettings); - - expect(document.querySelector(lcpLink)).to.exist; - expect(document.querySelector(notLcpLink)).not.to.exist; - }); -}); From b532778fe5dd7b2913c67ca1a1198ab7d77c7a7f Mon Sep 17 00:00:00 2001 From: vgoodric Date: Tue, 27 Aug 2024 14:30:27 -0600 Subject: [PATCH 11/15] add before each --- test/features/personalization/createMartechMetadata.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/features/personalization/createMartechMetadata.test.js b/test/features/personalization/createMartechMetadata.test.js index a3418015e1..9b504b17cf 100644 --- a/test/features/personalization/createMartechMetadata.test.js +++ b/test/features/personalization/createMartechMetadata.test.js @@ -10,6 +10,9 @@ const config = { // Note that the manifestPath doesn't matter as we stub the fetch describe('replace action', () => { + beforeEach(() => { + config.mep = {}; + }); it('testing create martech metadata output', async () => { expect(config.mep).to.deep.equal({}); From 9fb40a0f8137f8201c244284389f0a670b272dcd Mon Sep 17 00:00:00 2001 From: vgoodric Date: Tue, 27 Aug 2024 14:33:40 -0600 Subject: [PATCH 12/15] fix linter issue of using same name above --- libs/martech/attributes.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/martech/attributes.js b/libs/martech/attributes.js index b561aa6d43..43ac5690df 100644 --- a/libs/martech/attributes.js +++ b/libs/martech/attributes.js @@ -4,8 +4,7 @@ const LEAD_UNDERSCORES = /^_+|_+$/g; export function processTrackingLabels(text, config, charLimit) { let analyticsValue = text?.replace(INVALID_CHARACTERS, ' ').replace(LEAD_UNDERSCORES, '').trim(); if (config) { - const { analyticLocalization, mepLoc = analyticLocalization?.[analyticsValue] } = config.mep; - + const { analyticLocalization: mepObj, mepLoc = mepObj?.[analyticsValue] } = config.mep; if (mepLoc) { analyticsValue = mepLoc; } else { From 177db359eb41fd723ae3364fc4decd36a3640269 Mon Sep 17 00:00:00 2001 From: vgoodric Date: Tue, 27 Aug 2024 14:50:18 -0600 Subject: [PATCH 13/15] update processTrackingLabels --- libs/martech/attributes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/martech/attributes.js b/libs/martech/attributes.js index 43ac5690df..e2a0f103eb 100644 --- a/libs/martech/attributes.js +++ b/libs/martech/attributes.js @@ -4,11 +4,12 @@ const LEAD_UNDERSCORES = /^_+|_+$/g; export function processTrackingLabels(text, config, charLimit) { let analyticsValue = text?.replace(INVALID_CHARACTERS, ' ').replace(LEAD_UNDERSCORES, '').trim(); if (config) { - const { analyticLocalization: mepObj, mepLoc = mepObj?.[analyticsValue] } = config.mep; + const { analyticLocalization, mep } = config; + const mepLoc = mep?.analyticLocalization?.[analyticsValue]; if (mepLoc) { analyticsValue = mepLoc; } else { - const { analyticLocalization, loc = analyticLocalization?.[analyticsValue] } = config; + const loc = analyticLocalization?.[analyticsValue]; if (loc) analyticsValue = loc; } } From 1722063103acc4898e75402684126a97fdd9d9c0 Mon Sep 17 00:00:00 2001 From: vgoodric Date: Tue, 27 Aug 2024 14:59:01 -0600 Subject: [PATCH 14/15] add coverage to attributes.js unit test --- test/martech/attributes.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/martech/attributes.test.js b/test/martech/attributes.test.js index 0e6e89e748..1758d80c8c 100644 --- a/test/martech/attributes.test.js +++ b/test/martech/attributes.test.js @@ -57,4 +57,13 @@ describe('Analytics', async () => { }, 20); expect(processedString).to.equal('Buy now'); }); + it('should process tracking labels with foreign locale and MEP placeholder', () => { + const translatedString = 'Comprar ahora'; + const processedString = processTrackingLabels(translatedString, { + locale: { ietf: 'es-ES' }, + analyticLocalization: { 'Comprar ahora': 'Buy now' }, + mep: { analyticLocalization: { 'Comprar ahora': 'Buy right now' } }, + }, 20); + expect(processedString).to.equal('Buy right now'); + }); }); From a500f507f6926dbbf6ddfcee1aa8a73662d7dc3b Mon Sep 17 00:00:00 2001 From: AdobeLinhart Date: Wed, 28 Aug 2024 12:06:32 -0600 Subject: [PATCH 15/15] Unit test updates. --- .../createMartechMetadata.test.js | 24 ++- .../personalization/mocks/placeholders-new.js | 22 --- .../personalization/mocks/placeholders.js | 148 ++++++++++++------ 3 files changed, 120 insertions(+), 74 deletions(-) delete mode 100644 test/features/personalization/mocks/placeholders-new.js diff --git a/test/features/personalization/createMartechMetadata.test.js b/test/features/personalization/createMartechMetadata.test.js index 9b504b17cf..ba6a94eae2 100644 --- a/test/features/personalization/createMartechMetadata.test.js +++ b/test/features/personalization/createMartechMetadata.test.js @@ -1,7 +1,6 @@ import { expect } from '@esm-bundle/chai'; import { createMartechMetadata } from '../../../libs/features/personalization/personalization.js'; import placeholders from './mocks/placeholders.js'; -import newPlaceholders from './mocks/placeholders-new.js'; const config = { locale: { ietf: 'fr-fr' }, @@ -9,14 +8,14 @@ const config = { }; // Note that the manifestPath doesn't matter as we stub the fetch -describe('replace action', () => { +describe('test martech metadata creation', () => { beforeEach(() => { config.mep = {}; }); - it('testing create martech metadata output', async () => { + it('test two non US manifests', async () => { expect(config.mep).to.deep.equal({}); - await createMartechMetadata(placeholders, config, 'fr'); + await createMartechMetadata(placeholders.geoTest, config, 'fr'); expect(config.mep.analyticLocalization).to.deep.equal({ 'value1 fr': 'value1 en us', 'value2 fr': 'value2 en us', @@ -24,7 +23,7 @@ describe('replace action', () => { 'buy now fr': 'buy now en us', 'try now fr': 'try now en us', }); - await createMartechMetadata(newPlaceholders, config, 'fr'); + await createMartechMetadata(placeholders.secondManifestTest, config, 'fr'); expect(config.mep.analyticLocalization).to.deep.equal({ 'new fr': 'new en us', 'value1 fr': 'value1 en us', @@ -34,4 +33,19 @@ describe('replace action', () => { 'try now fr': 'try now en us', }); }); + it('test one manifest non US withou en-us keys', async () => { + await createMartechMetadata(placeholders.keyTest, config, 'fr'); + expect(config.mep.analyticLocalization).to.deep.equal({ + 'value1 fr': 'test placeholder', + 'value2 fr': 'test placeholder2', + 'bonjour fr': 'marquee headline', + 'buy now fr': 'marquee hollow', + 'try now fr': 'marquee solid', + }); + }); + it('test one manifest en-US', async () => { + config.locale.ietf = 'en-US'; + await createMartechMetadata(placeholders.keyTest, config, 'us'); + expect(config.mep).to.deep.equal({}); + }); }); diff --git a/test/features/personalization/mocks/placeholders-new.js b/test/features/personalization/mocks/placeholders-new.js deleted file mode 100644 index 06c9c7352b..0000000000 --- a/test/features/personalization/mocks/placeholders-new.js +++ /dev/null @@ -1,22 +0,0 @@ -const newPlaceholders = [ - { - key: 'test-placeholder', - 'mobile-device & us': 'US Mobile Value', - us: 'value1-us', - 'en-us': 'new-en-us', - 'ca & not fr': 'value1-ca-not-fr', - fr: 'new-fr', - 'mobile-device': 'value1-mobile', - }, - { - key: 'test-placeholder2', - 'mobile-device & us': 'US Mobile Value2', - us: 'value2-us', - 'en-us': 'new2-en-us', - 'ca & not fr': 'value2-ca-not-fr', - fr: 'value2-fr', - 'mobile-device': 'value2-mobile', - }, -]; - -export default newPlaceholders; diff --git a/test/features/personalization/mocks/placeholders.js b/test/features/personalization/mocks/placeholders.js index 03a42937b6..6cf3a8a9d3 100644 --- a/test/features/personalization/mocks/placeholders.js +++ b/test/features/personalization/mocks/placeholders.js @@ -1,49 +1,103 @@ -const placeholders = [ - { - key: 'test-placeholder', - 'mobile-device & us': 'US Mobile Value', - us: 'value1-us', - 'en-us': 'value1-en-us', - 'ca & not fr': 'value1-ca-not-fr', - fr: 'value1-fr', - 'mobile-device': 'value1-mobile', - }, - { - key: 'test-placeholder2', - 'mobile-device & us': 'US Mobile Value2', - us: 'value2-us', - 'en-us': 'value2-en-us', - 'ca & not fr': 'value2-ca-not-fr', - fr: 'value2-fr', - 'mobile-device': 'value2-mobile', - }, - { - key: 'marquee-headline', - 'mobile-device & us': 'hello US mobile', - us: 'hello-us', - 'en-us': 'Hello-en-us', - 'ca & not fr': 'hello-ca-not-fr', - fr: 'bonjour-fr', - 'mobile-device': 'hello-mobile', - }, - { - key: 'marquee-hollow', - 'mobile-device & us': 'buy-now-mobile-us', - us: 'buy-now-us', - 'en-us': 'buy-now-en-us', - 'ca & not fr': 'buy-now-ca-not-fr', - fr: 'buy-now-fr', - 'mobile-device': 'buy-now-mobile', - }, - { - key: 'marquee-solid', - 'mobile-device & us': 'try-now-mobile-us', - us: 'try-now-us', - 'en-us': 'try-now-en-us', - 'ca & not fr': 'try-now-ca-not-fr', - fr: 'try-now-fr', - 'mobile-device': 'try-now-mobile', - }, -]; +const placeholders = { + geoTest: [ + { + key: 'test-placeholder', + 'mobile-device & us': 'US Mobile Value', + us: 'value1-us', + 'en-us': 'value1-en-us', + 'ca & not fr': 'value1-ca-not-fr', + fr: 'value1-fr', + 'mobile-device': 'value1-mobile', + }, + { + key: 'test-placeholder2', + 'mobile-device & us': 'US Mobile Value2', + us: 'value2-us', + 'en-us': 'value2-en-us', + 'ca & not fr': 'value2-ca-not-fr', + fr: 'value2-fr', + 'mobile-device': 'value2-mobile', + }, + { + key: 'marquee-headline', + 'mobile-device & us': 'hello US mobile', + us: 'hello-us', + 'en-us': 'Hello-en-us', + 'ca & not fr': 'hello-ca-not-fr', + fr: 'bonjour-fr', + 'mobile-device': 'hello-mobile', + }, + { + key: 'marquee-hollow', + 'mobile-device & us': 'buy-now-mobile-us', + us: 'buy-now-us', + 'en-us': 'buy-now-en-us', + 'ca & not fr': 'buy-now-ca-not-fr', + fr: 'buy-now-fr', + 'mobile-device': 'buy-now-mobile', + }, + { + key: 'marquee-solid', + 'mobile-device & us': 'try-now-mobile-us', + us: 'try-now-us', + 'en-us': 'try-now-en-us', + 'ca & not fr': 'try-now-ca-not-fr', + fr: 'try-now-fr', + 'mobile-device': 'try-now-mobile', + }, + ], + secondManifestTest: [ + { + key: 'test-placeholder', + 'mobile-device & us': 'US Mobile Value', + us: 'value1-us', + 'en-us': 'new-en-us', + 'ca & not fr': 'value1-ca-not-fr', + fr: 'new-fr', + 'mobile-device': 'value1-mobile', + }, + { + key: 'test-placeholder2', + 'mobile-device & us': 'US Mobile Value2', + us: 'value2-us', + 'en-us': 'new2-en-us', + 'ca & not fr': 'value2-ca-not-fr', + fr: 'value2-fr', + 'mobile-device': 'value2-mobile', + }, + ], + keyTest: [ + { + key: 'test-placeholder', + 'mobile-device & us': 'US Mobile Value', + fr: 'value1-fr', + 'mobile-device': 'value1-mobile', + }, + { + key: 'test-placeholder2', + 'mobile-device & us': 'US Mobile Value2', + fr: 'value2-fr', + 'mobile-device': 'value2-mobile', + }, + { + key: 'marquee-headline', + 'mobile-device & us': 'hello US mobile', + fr: 'bonjour-fr', + 'mobile-device': 'hello-mobile', + }, + { + key: 'marquee-hollow', + 'mobile-device & us': 'buy-now-mobile-us', + fr: 'buy-now-fr', + 'mobile-device': 'buy-now-mobile', + }, + { + key: 'marquee-solid', + 'mobile-device & us': 'try-now-mobile-us', + fr: 'try-now-fr', + 'mobile-device': 'try-now-mobile', + }, + ], +}; export default placeholders;