From cdd1952ecc4b6f4f41d061ea614991ef6db38db9 Mon Sep 17 00:00:00 2001 From: Mira Fedas <30750556+mirafedas@users.noreply.github.com> Date: Wed, 20 Mar 2024 21:41:58 +0100 Subject: [PATCH] MWPW-140481: Delayed modal (#1939) added delayed modal Co-authored-by: Blaine Gunn --- libs/blocks/modal/modal.js | 41 ++++++++++++-- test/blocks/modals/modals.test.js | 56 ++++++++++++++++++- .../manifestInsertContentAfter.json | 10 ++++ 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/libs/blocks/modal/modal.js b/libs/blocks/modal/modal.js index d8c34964d5..443ff92c29 100644 --- a/libs/blocks/modal/modal.js +++ b/libs/blocks/modal/modal.js @@ -176,14 +176,45 @@ export async function getModal(details, custom) { return dialog; } +export function getHashParams(hashStr) { + if (!hashStr) return {}; + return hashStr.split(':').reduce((params, part) => { + if (part.startsWith('#')) { + params.hash = part; + } else { + const [key, val] = part.split('='); + if (key === 'delay' && parseInt(val, 10) > 0) { + params.delay = parseInt(val, 10) * 1000; + } + } + return params; + }, {}); +} + +export function delayedModal(el) { + const { hash, delay } = getHashParams(el?.dataset.modalHash); + if (!delay || !hash) return false; + el.classList.add('hide-block'); + const modalOpenEvent = new Event(`${hash}:modalOpen`); + const pagesModalWasShownOn = window.sessionStorage.getItem(`shown:${hash}`); + el.dataset.modalHash = hash; + el.href = hash; + if (!pagesModalWasShownOn?.includes(window.location.pathname)) { + setTimeout(() => { + window.location.replace(hash); + sendAnalytics(modalOpenEvent); + window.sessionStorage.setItem(`shown:${hash}`, `${pagesModalWasShownOn || ''} ${window.location.pathname}`); + }, delay); + } + return true; +} + // Deep link-based export default function init(el) { const { modalHash } = el.dataset; - if (window.location.hash === modalHash && !document.querySelector(`div.dialog-modal${modalHash}`)) { - const details = findDetails(window.location.hash, el); - if (details) return getModal(details); - } - return null; + if (delayedModal(el) || window.location.hash !== modalHash || document.querySelector(`div.dialog-modal${modalHash}`)) return null; + const details = findDetails(window.location.hash, el); + return details ? getModal(details) : null; } // Click-based modal diff --git a/test/blocks/modals/modals.test.js b/test/blocks/modals/modals.test.js index 9a825b2953..daf6ddc950 100644 --- a/test/blocks/modals/modals.test.js +++ b/test/blocks/modals/modals.test.js @@ -1,11 +1,26 @@ import { readFile, sendKeys } from '@web/test-runner-commands'; import { expect } from '@esm-bundle/chai'; +import sinon from 'sinon'; import { delay, waitForElement, waitForRemoval } from '../../helpers/waitfor.js'; -import init, { getModal } from '../../../libs/blocks/modal/modal.js'; document.body.innerHTML = await readFile({ path: './mocks/body.html' }); +const { + default: init, + getModal, + getHashParams, + delayedModal, +} = await import('../../../libs/blocks/modal/modal.js'); describe('Modals', () => { + beforeEach(() => { + // eslint-disable-next-line no-underscore-dangle + window._satellite = { track: sinon.spy() }; + }); + + afterEach(() => { + sinon.restore(); + }); + it('Doesnt load modals on page load with no hash', async () => { window.location.hash = ''; const modal = document.querySelector('.dialog-modal'); @@ -169,4 +184,43 @@ describe('Modals', () => { // Test passing, means there was no error thrown await hashChangeTriggered; }); + + it('validates and returns proper hash parameters', () => { + expect(getHashParams()).to.deep.equal({}); + expect(getHashParams('#delayed-modal:delay=0')).to.deep.equal({ hash: '#delayed-modal' }); + expect(getHashParams('#delayed-modal:delay=1')).to.deep.equal({ + delay: 1000, + hash: '#delayed-modal', + }); + }); + + it('shows the modal with a delay, and remembers it was shown on this page', async () => { + window.sessionStorage.removeItem('shown:#delayed-modal'); + const el = document.createElement('a'); + el.setAttribute('data-modal-hash', '#delayed-modal:delay=1'); + expect(delayedModal(el)).to.be.true; + await delay(1000); + expect(el.classList.contains('hide-block')).to.be.true; + const modal = waitForElement('#delayed-modal'); + expect(modal).to.be.not.null; + expect(window.sessionStorage.getItem('shown:#delayed-modal').includes(window.location.pathname)).to.be.true; + // eslint-disable-next-line no-underscore-dangle + expect(window._satellite.track.called).to.be.true; + window.sessionStorage.removeItem('shown:#delayed-modal'); + el.remove(); + }); + + it('does not show the modal if it was shown on this page', async () => { + const el = document.createElement('a'); + el.setAttribute('data-modal-hash', '#dm:delay=1'); + window.sessionStorage.setItem('shown:#dm', window.location.pathname); + expect(delayedModal(el)).to.be.true; + await delay(1000); + // eslint-disable-next-line no-underscore-dangle + expect(window._satellite.track.called).to.be.false; + const modal = document.querySelector('#dm'); + expect(modal).to.not.exist; + window.sessionStorage.removeItem('shown:#dm'); + el.remove(); + }); }); diff --git a/test/features/personalization/mocks/deprecatedActions/manifestInsertContentAfter.json b/test/features/personalization/mocks/deprecatedActions/manifestInsertContentAfter.json index 59c00ee71e..15b8d8facb 100644 --- a/test/features/personalization/mocks/deprecatedActions/manifestInsertContentAfter.json +++ b/test/features/personalization/mocks/deprecatedActions/manifestInsertContentAfter.json @@ -12,6 +12,16 @@ "firefox": "", "android": "", "ios": "" + }, + { + "action": "insertContentAfter", + "selector": "main > div", + "page filter (optional)": "", + "param-newoffer=123": "", + "chrome": "/fragments/insertafter#delayed-modal:delay=1", + "firefox": "", + "android": "", + "ios": "" } ], ":type": "sheet"