Skip to content

Commit

Permalink
Personalization Tests (#1095)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrischrischris authored Aug 9, 2023
1 parent 1c46518 commit c38748c
Show file tree
Hide file tree
Showing 25 changed files with 618 additions and 97 deletions.
8 changes: 1 addition & 7 deletions libs/blocks/fragment/fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ const fragMap = {};

const removeHash = (url) => (url?.endsWith('#_dnt') ? url : url?.split('#')[0]);

// TODO: Can we just use a simple list of loaded fragments?

const isCircularRef = (href) => [...Object.values(fragMap)]
.some((tree) => {
const node = tree.find(href);
Expand Down Expand Up @@ -44,11 +42,7 @@ export default async function init(a) {
const resp = await fetch(`${a.href}.plain.html`);
if (resp.ok) {
const html = await resp.text();
let doc = (new DOMParser()).parseFromString(html, 'text/html');
if (doc.querySelector('.fragment-personalization')) {
const { fragmentPersonalization } = await import('../../features/personalization/personalization.js');
doc = await fragmentPersonalization(doc);
}
const doc = (new DOMParser()).parseFromString(html, 'text/html');
const sections = doc.querySelectorAll('body > div');
if (sections.length > 0) {
const fragment = createTag('div', { class: 'fragment', 'data-path': relHref });
Expand Down
76 changes: 11 additions & 65 deletions libs/features/personalization/personalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const CLASS_EL_DELETE = 'p13n-deleted';
const CLASS_EL_REPLACE = 'p13n-replaced';
const PAGE_URL = new URL(window.location.href);

/* c8 ignore start */
export const PERSONALIZATION_TAGS = {
chrome: () => navigator.userAgent.includes('Chrome') && !navigator.userAgent.includes('Mobile'),
firefox: () => navigator.userAgent.includes('Firefox') && !navigator.userAgent.includes('Mobile'),
Expand All @@ -15,6 +16,7 @@ export const PERSONALIZATION_TAGS = {
darkmode: () => window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches,
lightmode: () => !PERSONALIZATION_TAGS.darkmode(),
};
/* c8 ignore stop */

// Replace any non-alpha chars except comma, space and hyphen
const RE_KEY_REPLACE = /[^a-z0-9\- ,=]/g;
Expand Down Expand Up @@ -73,13 +75,15 @@ const fetchData = async (url, type = DATA_TYPE.JSON) => {
try {
const resp = await fetch(url);
if (!resp.ok) {
/* c8 ignore next 5 */
if (resp.status === 404) {
throw new Error('File not found');
}
throw new Error(`Invalid response: ${resp.status} ${resp.statusText}`);
}
return await resp[type]();
} catch (e) {
/* c8 ignore next 2 */
console.log(`Error loading content: ${url}`, e.message || e);
}
return null;
Expand All @@ -92,6 +96,7 @@ const consolidateObjects = (arr, prop) => arr.reduce((propMap, item) => {
return propMap;
}, {});

/* c8 ignore start */
function normalizePath(p) {
let path = p;

Expand All @@ -112,6 +117,7 @@ function normalizePath(p) {
}
return path;
}
/* c8 ignore stop */

const matchGlob = (searchStr, inputStr) => {
const pattern = searchStr.replace(/\*\*/g, '.*');
Expand Down Expand Up @@ -179,6 +185,7 @@ function handleCommands(commands, manifestId, rootEl = document) {

COMMANDS[cmd.action](selectorEl, cmd.target, manifestId);
} else {
/* c8 ignore next */
console.log('Invalid command found: ', cmd);
}
});
Expand All @@ -205,12 +212,13 @@ const getVariantInfo = (line, variantNames, variants) => {
variants[vn][action] = variants[vn][action] || [];

variants[vn][action].push({
selector: action === 'useblockcode' ? line[vn]?.split('/').pop() : normalizePath(selector),
selector: normalizePath(selector),
val: normalizePath(line[vn]),
});
} else if (VALID_COMMANDS.includes(action)) {
variants[vn].commands.push(variantInfo);
} else {
/* c8 ignore next 2 */
console.log('Invalid action found: ', line);
}
});
Expand All @@ -237,6 +245,7 @@ export function parseConfig(data) {
config.variantNames = variantNames;
return config;
} catch (e) {
/* c8 ignore next 3 */
console.log('error parsing personalization config:', e, experiences);
}
return null;
Expand Down Expand Up @@ -305,6 +314,7 @@ export async function getPersConfig(name, variantLabel, manifestData, manifestPa
const config = parseConfig(persData);

if (!config) {
/* c8 ignore next 3 */
console.log('Error loading personalization config: ', name || manifestPath);
return null;
}
Expand Down Expand Up @@ -335,75 +345,11 @@ export async function getPersConfig(name, variantLabel, manifestData, manifestPa
return config;
}

const getFPInfo = (fpTableRows) => {
const names = [];
const info = [...fpTableRows].reduce((infoObj, row) => {
const [actionEl, selectorEl, variantEl, fragment] = row.children;
if (actionEl?.innerText?.toLowerCase() === 'action') {
return infoObj;
}
const variantName = variantEl?.innerText?.toLowerCase();
if (!names.includes(variantName)) {
names.push(variantName);
infoObj[variantName] = [];
}
infoObj[variantName].push({
action: actionEl.innerText?.toLowerCase(),
selector: selectorEl.innerText?.toLowerCase(),
htmlFragment: fragment.firstElementChild,
});
return infoObj;
}, {});
return { info, names };
};

const modifyFragment = (selectedEl, action, htmlFragment, manifestId) => {
htmlFragment.dataset.manifestId = manifestId;
switch (action) {
case 'replace': case 'replacecontent':
selectedEl.replaceWith(htmlFragment);
break;
case 'insertbefore': case 'insertcontentbefore':
selectedEl.insertAdjacentElement('beforebegin', htmlFragment);
break;
case 'insertafter': case 'insertcontentafter':
selectedEl.insertAdjacentElement('afterend', htmlFragment);
break;
case 'remove': case 'removecontent':
selectedEl.insertAdjacentElement('beforebegin', createTag('div', { 'data-remove-manifest-id': manifestId }));
selectedEl.remove();
break;
default:
console.warn(`Unknown action: ${action}`);
}
};

const deleteMarkedEls = () => {
[...document.querySelectorAll(`.${CLASS_EL_DELETE}`)]
.forEach((el) => el.remove());
};

export async function fragmentPersonalization(el) {
const fpTable = el.querySelector('div.fragment-personalization');
if (!fpTable) return el;
const fpTableRows = fpTable.querySelectorAll(':scope > div');

const { info, names } = getFPInfo(fpTableRows);
fpTable.remove();

const manifestId = 'fragment-personalization';
const selectedVariant = getPersonalizationVariant(manifestId, names);
if (!selectedVariant) return el;

info[selectedVariant].forEach((cmd) => {
const selectedEl = el.querySelector(cmd.selector);
if (!selectedEl) return;
modifyFragment(selectedEl, cmd.action, cmd.htmlFragment, manifestId);
});

return el;
}

const normalizeFragPaths = ({ selector, val }) => ({
selector: normalizePath(selector),
val: normalizePath(val),
Expand Down
19 changes: 17 additions & 2 deletions libs/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,12 +375,27 @@ export async function loadTemplate() {
await Promise.all([styleLoaded, scriptLoaded]);
}

function checkForExpBlock(name, expBlocks) {
const expBlock = expBlocks?.[name];
if (!expBlock) return null;

const blockName = expBlock.split('/').pop();
return { blockPath: expBlock, blockName };
}

export async function loadBlock(block) {
const name = block.classList[0];
let name = block.classList[0];
const { miloLibs, codeRoot, expBlocks } = getConfig();

const base = miloLibs && MILO_BLOCKS.includes(name) ? miloLibs : codeRoot;
const path = expBlocks?.[name] ? `${expBlocks[name]}` : `${base}/blocks/${name}`;
let path = `${base}/blocks/${name}`;

const expBlock = checkForExpBlock(name, expBlocks);
if (expBlock) {
name = expBlock.blockName;
path = expBlock.blockPath;
}

const blockPath = `${path}/${name}`;

const styleLoaded = new Promise((resolve) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
<h3>The fragment has been replaced</h3>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "insertContentAfter",
"selector": ".marquee",
"page filter (optional)": "",
"param-newoffer=123": "",
"chrome": "/fragments/insertafter",
"firefox": "",
"android": "",
"ios": ""
}
],
":type": "sheet"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "insertContentBefore",
"selector": ".marquee",
"page filter (optional)": "",
"param-newoffer=123": "",
"chrome": "/fragments/insertbefore",
"firefox": "",
"android": "",
"ios": ""
}
],
":type": "sheet"
}
15 changes: 15 additions & 0 deletions test/features/personalization/mocks/manifestPageFilterExclude.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "replacePage",
"selector": "",
"page filter (optional)": "/no/match/**",
"param-newoffer=123": "",
"chrome": "/test/features/personalization/mocks/replacePage"
}
],
":type": "sheet"
}
15 changes: 15 additions & 0 deletions test/features/personalization/mocks/manifestPageFilterInclude.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "replacePage",
"selector": "",
"page filter (optional)": "/**",
"param-newoffer=123": "",
"chrome": "/test/features/personalization/mocks/replacePage"
}
],
":type": "sheet"
}
18 changes: 18 additions & 0 deletions test/features/personalization/mocks/manifestRemove.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "removeContent",
"selector": ".z-pattern",
"page filter (optional)": "",
"param-newoffer=123": "",
"chrome": "yes",
"firefox": "",
"android": "",
"ios": ""
}
],
":type": "sheet"
}
28 changes: 28 additions & 0 deletions test/features/personalization/mocks/manifestReplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "replaceContent",
"selector": ".how-to",
"page filter (optional)": "",
"param-newoffer=123": "",
"chrome": "",
"firefox": "/drafts/rwoblesk/personalization-testing/fragments/milo-replace-content-firefox-accordion",
"android": "",
"ios": ""
},
{
"action": "replaceContent",
"selector": "#features-of-milo-experimentation-platform",
"page filter (optional)": "",
"param-newoffer=123": "",
"chrome": "/fragments/milo-replace-content-chrome-howto-h2",
"firefox": "",
"android": "",
"ios": ""
}
],
":type": "sheet"
}
18 changes: 18 additions & 0 deletions test/features/personalization/mocks/manifestReplaceFragment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "replaceFragment",
"selector": "/fragments/replaceme",
"page filter (optional)": "",
"param-newoffer=123": "",
"chrome": "/fragments/fragmentreplaced",
"firefox": "",
"android": "",
"ios": ""
}
],
":type": "sheet"
}
15 changes: 15 additions & 0 deletions test/features/personalization/mocks/manifestReplacePage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "replacePage",
"selector": "",
"page filter (optional)": "",
"param-newoffer=123": "",
"chrome": "/test/features/personalization/mocks/replacePage"
}
],
":type": "sheet"
}
Loading

0 comments on commit c38748c

Please sign in to comment.