Skip to content

Commit

Permalink
MWPW-143673: Load Milo fragment modals from co links (#1972)
Browse files Browse the repository at this point in the history
* MWPW-143673: Load Milo fragment modals from co links
  • Loading branch information
yesil authored Mar 13, 2024
1 parent 6414f18 commit 40670a4
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 97 deletions.
24 changes: 23 additions & 1 deletion libs/blocks/merch/merch.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,43 @@ a[is='checkout-link'] > span {
content: "\00a0";
}

@media (max-width: 1200px) {
@media (max-width: 1199px) {
#checkout-link-modal {
height: 100vh;
border-radius: 0;
margin-top: 0;
margin-bottom: 0;
}
}

#checkout-link-modal {
display: flex;
}

#checkout-link-modal > .section {
width: 100%;
height: auto;
}

@media (min-width: 1200px) {
#checkout-link-modal.twp,
#checkout-link-modal.twp .milo-iframe,
#checkout-link-modal.d2p,
#checkout-link-modal.d2p .milo-iframe {
height: 850px;
}

#checkout-link-modal.crm,
#checkout-link-modal.crm .milo-iframe {
height: 640px;
}
}

#checkout-link-modal iframe {
flex: 1;
}

#checkout-link-modal iframe.twp,
#checkout-link-modal iframe.d2p {
max-height: 850px;
}
Expand Down
70 changes: 51 additions & 19 deletions libs/blocks/merch/merch.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { createTag, getConfig, loadScript, localizeLink } from '../../utils/utils.js';
import {
createTag, getConfig, loadArea, loadScript, loadStyle, localizeLink,
} from '../../utils/utils.js';
import { replaceKey } from '../../features/placeholders.js';

export const priceLiteralsURL = 'https://milo.adobe.com/libs/commerce/price-literals.json';
export const checkoutLinkConfigURL = 'https://milo.adobe.com/libs/commerce/checkout-link.json';
export const PRICE_LITERALS_URL = 'https://milo.adobe.com/libs/commerce/price-literals.json';
export const CHECKOUT_LINK_CONFIG_PATH = '/commerce/checkout-link.json'; // relative to libs.

export const PRICE_TEMPLATE_DISCOUNT = 'discount';
export const PRICE_TEMPLATE_OPTICAL = 'optical';
Expand Down Expand Up @@ -47,7 +49,7 @@ const LOADING_ENTITLEMENTS = 'loading-entitlements';
let log;
let upgradeOffer = null;

export function polyfills() {
export async function polyfills() {
if (polyfills.promise) return polyfills.promise;
let isSupported = false;
document.createElement('div', {
Expand Down Expand Up @@ -78,9 +80,9 @@ export async function fetchEntitlements() {
return fetchEntitlements.promise;
}

export async function fetchCheckoutLinkConfigs() {
export async function fetchCheckoutLinkConfigs(base = '') {
fetchCheckoutLinkConfigs.promise = fetchCheckoutLinkConfigs.promise
?? fetch(checkoutLinkConfigURL).catch(() => {
?? fetch(`${base}${CHECKOUT_LINK_CONFIG_PATH}`).catch(() => {
log?.error('Failed to fetch checkout link configs');
}).then((mappings) => {
if (!mappings?.ok) return undefined;
Expand All @@ -90,7 +92,12 @@ export async function fetchCheckoutLinkConfigs() {
}

export async function getCheckoutLinkConfig(productFamily) {
const checkoutLinkConfigs = await fetchCheckoutLinkConfigs();
let { base } = getConfig();
if (/\.page$/.test(document.location.origin)) {
/* c8 ignore next 2 */
base = base.replace('.live', '.page');
}
const checkoutLinkConfigs = await fetchCheckoutLinkConfigs(base);
const { locale: { region } } = getConfig();
const productFamilyConfigs = checkoutLinkConfigs.data?.filter(
({ [NAME_PRODUCT_FAMILY]: mappingProductFamily }) => mappingProductFamily === productFamily,
Expand All @@ -115,7 +122,7 @@ export async function getCheckoutLinkConfig(productFamily) {
}

export async function getDownloadAction(options, imsSignedInPromise, offerFamily) {
if (options.entitlement !== true) return null;
if (options.entitlement !== true) return undefined;
const loggedIn = await imsSignedInPromise;
if (!loggedIn) return undefined;
const entitlements = await fetchEntitlements();
Expand All @@ -140,7 +147,7 @@ export async function getDownloadAction(options, imsSignedInPromise, offerFamily
}

export async function getUpgradeAction(options, imsSignedInPromise, productFamily) {
if (options.entitlement === false) return null;
if (options.entitlement === false) return undefined;
const loggedIn = await imsSignedInPromise;
if (!loggedIn) return undefined;
const entitlements = await fetchEntitlements();
Expand All @@ -164,34 +171,59 @@ export async function getUpgradeAction(options, imsSignedInPromise, productFamil
return undefined;
}

async function openExternalModal(url, getModal, offerType) {
const iframe = createTag('iframe', {
async function openFragmentModal(path, getModal) {
const root = createTag('div');
root.style.visibility = 'hidden';
createTag('a', { href: `${path}` }, '', { parent: root });
const modal = await getModal(null, {
id: 'checkout-link-modal',
content: root,
closeEvent: 'closeModal',
class: 'commerce-frame',
});
await loadArea(modal);
root.style.visibility = '';
return modal;
}

async function openExternalModal(url, getModal) {
await loadStyle(`${getConfig().base}/blocks/iframe/iframe.css`);
const root = createTag('div', { class: 'milo-iframe' });
createTag('iframe', {
src: url,
frameborder: '0',
marginwidth: '0',
marginheight: '0',
allowfullscreen: 'true',
loading: 'lazy',
class: offerType === OFFER_TYPE_TRIAL ? 'twp' : 'd2p',
});
}, '', { parent: root });
return getModal(null, {
id: 'checkout-link-modal',
content: iframe,
content: root,
closeEvent: 'closeModal',
class: ['commerce-frame'],
class: 'commerce-frame',
});
}

export async function openModal(e, url, offerType) {
e.preventDefault();
e.stopImmediatePropagation();
const { getModal } = await import('../modal/modal.js');
if (/^https?:/.test(url)) {
openExternalModal(url, getModal, offerType);
const offerTypeClass = offerType === OFFER_TYPE_TRIAL ? 'twp' : 'crm';
let modal;
if (/\/fragments\//.test(url)) {
const fragmentPath = url.split(/hlx.(page|live)/).pop();
modal = await openFragmentModal(fragmentPath, getModal);
} else if (/^https?:/.test(url)) {
modal = await openExternalModal(url, getModal);
}
if (modal) {
modal.classList.add(offerTypeClass);
}
}

export async function getModalAction(offers, options, productFamily) {
if (options.modal !== true) return null;
if (options.modal !== true) return undefined;
const checkoutLinkConfig = await getCheckoutLinkConfig(productFamily);
if (!checkoutLinkConfig) return undefined;
const [{ offerType }] = offers;
Expand Down Expand Up @@ -227,7 +259,7 @@ export async function initService(force = false) {
initService.promise = initService.promise ?? polyfills().then(async () => {
const commerceLib = await import('../../deps/commerce.js');
const { env, commerce = {}, locale } = getConfig();
commerce.priceLiteralsURL = priceLiteralsURL;
commerce.priceLiteralsURL = PRICE_LITERALS_URL;
const service = await commerceLib.init(() => ({
env,
commerce,
Expand Down
4 changes: 2 additions & 2 deletions libs/blocks/modal/modal.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
z-index: 103;
}

.dialog-modal.commerce-frame .fragment,
.dialog-modal.commerce-frame .section {
.dialog-modal.commerce-frame > .fragment,
.dialog-modal.commerce-frame > .section {
height: 100vh;
}

Expand Down
6 changes: 3 additions & 3 deletions libs/deps/commerce.js

Large diffs are not rendered by default.

46 changes: 42 additions & 4 deletions test/blocks/merch/merch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import merch, {
buildCta,
getCheckoutContext,
initService,
priceLiteralsURL,
PRICE_LITERALS_URL,
fetchCheckoutLinkConfigs,
getCheckoutLinkConfig,
getDownloadAction,
Expand Down Expand Up @@ -50,12 +50,21 @@ const CHECKOUT_LINK_CONFIGS = {
BUY_NOW_PATH: 'X',
LOCALE: 'fr',
},
{ PRODUCT_FAMILY: 'CC_ALL_APPS', DOWNLOAD_URL: 'https://creativecloud.adobe.com/apps/download', LOCALE: '' }],
{ PRODUCT_FAMILY: 'CC_ALL_APPS', DOWNLOAD_URL: 'https://creativecloud.adobe.com/apps/download', LOCALE: '' },
{
PRODUCT_FAMILY: 'PREMIERE',
DOWNLOAD_TEXT: 'Download',
DOWNLOAD_URL: 'https://creativecloud.adobe.com/apps/download/premiere',
FREE_TRIAL_PATH: '/test/blocks/merch/mocks/fragments/twp',
BUY_NOW_PATH: '',
LOCALE: '',
},
],
};

const config = {
codeRoot: '/libs',
commerce: { priceLiteralsURL },
commerce: { priceLiteralsURL: PRICE_LITERALS_URL },
env: { name: 'prod' },
imsClientId: 'test_client_id',
placeholders: { 'upgrade-now': 'Upgrade Now', download: 'Download' },
Expand Down Expand Up @@ -129,6 +138,8 @@ describe('Merch Block', () => {
await initService(true);
Log.reset();
Log.use(Log.Plugins.quietFilter);
fetchCheckoutLinkConfigs.promise = undefined;
await fetchCheckoutLinkConfigs('http://localhost:3000/libs');
});

afterEach(() => {
Expand Down Expand Up @@ -456,7 +467,7 @@ describe('Merch Block', () => {
it('fetchCheckoutLinkConfigs: returns null if mapping cannot be fetched', async () => {
fetchCheckoutLinkConfigs.promise = undefined;
setCheckoutLinkConfigs(null);
const mappings = await fetchCheckoutLinkConfigs();
const mappings = await fetchCheckoutLinkConfigs('http://localhost:2000/libs');
expect(mappings).to.be.undefined;
setCheckoutLinkConfigs(CHECKOUT_LINK_CONFIGS);
fetchCheckoutLinkConfigs.promise = undefined;
Expand Down Expand Up @@ -549,6 +560,33 @@ describe('Merch Block', () => {
document.querySelector('.modal-curtain').click();
});

it('renders Milo TWP modal', async () => {
mockIms();
const el = document.querySelector('.merch.cta.milo.twp');
const cta = await merch(el);
const { nodeName, textContent } = await cta.onceSettled();
expect(nodeName).to.equal('A');
expect(textContent).to.equal('Free Trial');
expect(cta.getAttribute('href')).to.equal('#');
cta.click();
await delay(100);
let modal = document.getElementById('checkout-link-modal');
expect(modal.querySelector('[data-path]').dataset.path).to.equal('/test/blocks/merch/mocks/fragments/twp');
expect(modal.querySelector('h1').innerText).to.equal('twp modal');
document.querySelector('.modal-curtain').click();
await delay(100);
const [,,,, checkoutLinkConfig] = CHECKOUT_LINK_CONFIGS.data;
checkoutLinkConfig.FREE_TRIAL_PATH = 'http://main--milo--adobecom.hlx.page/test/blocks/merch/mocks/fragments/twp-url';
await cta.render();
cta.click();
await delay(100);
modal = document.getElementById('checkout-link-modal');
expect(modal.querySelector('h1').innerText).to.equal('twp modal #2');
expect(modal.querySelector('[data-path]').dataset.path).to.equal('/test/blocks/merch/mocks/fragments/twp-url');
document.querySelector('.modal-curtain').click();
await delay(100);
});

it('renders D2P modal', async () => {
mockIms();
const el = document.querySelector('.merch.cta.d2p');
Expand Down
4 changes: 4 additions & 0 deletions test/blocks/merch/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ <h2>CTAs</h2>
<p>TWP modal: <a class="merch cta twp"
href="/tools/ost?osi=illustrator-trial&type=checkoutUrl&modal=true&entitlement=true">CTA Free Trial</a>
</p>

<p>Milo TWP modal: <a class="merch cta milo twp"
href="/tools/ost?osi=premiere-trial&type=checkoutUrl&modal=true&entitlement=true">CTA Free Trial</a>
</p>
<p>D2P modal: <a class="merch cta d2p"
href="/tools/ost?osi=illustrator&type=checkoutUrl&modal=true&entitlement=true">CTA Buy Now</a>
</p>
Expand Down
2 changes: 2 additions & 0 deletions test/blocks/merch/mocks/embed-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const getConfig = () => config;

export const setConfig = (c) => { config = c; };

export const loadArea = stub();

export const loadScript = stub();

export const loadStyle = stub();
Expand Down
4 changes: 2 additions & 2 deletions test/blocks/merch/mocks/fetch.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFile } from '@web/test-runner-commands';
import sinon from 'sinon';

import { priceLiteralsURL } from '../../../../libs/blocks/merch/merch.js';
import { PRICE_LITERALS_URL } from '../../../../libs/blocks/merch/merch.js';

export async function mockFetch() {
// this path allows to import this mock from tests for other blocks (e.g. commerce)
Expand All @@ -24,7 +24,7 @@ export async function mockFetch() {
sinon.stub(window, 'fetch').callsFake((...args) => {
const { href, pathname, searchParams } = new URL(String(args[0]));
// literals mock
if (href === priceLiteralsURL) {
if (href === PRICE_LITERALS_URL) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve(literals),
Expand Down
5 changes: 5 additions & 0 deletions test/blocks/merch/mocks/fragments/twp-url.plain.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<body>
<div>
<h1>twp modal #2</h1>
</div>
</body>
5 changes: 5 additions & 0 deletions test/blocks/merch/mocks/fragments/twp.plain.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<body>
<div>
<h1>twp modal</h1>
</div>
</body>
Loading

0 comments on commit 40670a4

Please sign in to comment.