From 2b4175db3546cfcdc0a1ea5e1caca190204e3676 Mon Sep 17 00:00:00 2001 From: Jason Slavin Date: Thu, 19 Sep 2024 10:39:12 -0700 Subject: [PATCH 1/8] Adding a dynamic nav status button into the global nav to aid content QA in understanding which nav is active --- .../global-navigation/global-navigation.js | 2 +- .../dynamic-navigation.js | 4 +- libs/features/dynamic-navigation/status.css | 173 ++++++++++++ libs/features/dynamic-navigation/status.js | 133 ++++++++++ libs/utils/utils.js | 4 + test/features/dynamic-nav/dynamicNav.test.js | 2 +- test/features/dynamic-nav/mocks/status.html | 9 + test/features/dynamic-nav/status.test.js | 248 ++++++++++++++++++ 8 files changed, 571 insertions(+), 4 deletions(-) rename libs/features/{ => dynamic-navigation}/dynamic-navigation.js (90%) create mode 100644 libs/features/dynamic-navigation/status.css create mode 100644 libs/features/dynamic-navigation/status.js create mode 100644 test/features/dynamic-nav/mocks/status.html create mode 100644 test/features/dynamic-nav/status.test.js diff --git a/libs/blocks/global-navigation/global-navigation.js b/libs/blocks/global-navigation/global-navigation.js index f4c602bf86..694b0508dd 100644 --- a/libs/blocks/global-navigation/global-navigation.js +++ b/libs/blocks/global-navigation/global-navigation.js @@ -1018,7 +1018,7 @@ const getSource = async () => { const { locale, dynamicNavKey } = getConfig(); let url = getMetadata('gnav-source') || `${locale.contentRoot}/gnav`; if (dynamicNavKey) { - const { default: dynamicNav } = await import('../../features/dynamic-navigation.js'); + const { default: dynamicNav } = await import('../../features/dynamic-navigation/dynamic-navigation.js'); url = dynamicNav(url, dynamicNavKey); } return url; diff --git a/libs/features/dynamic-navigation.js b/libs/features/dynamic-navigation/dynamic-navigation.js similarity index 90% rename from libs/features/dynamic-navigation.js rename to libs/features/dynamic-navigation/dynamic-navigation.js index 7f901a2629..c7ae1674c8 100644 --- a/libs/features/dynamic-navigation.js +++ b/libs/features/dynamic-navigation/dynamic-navigation.js @@ -1,6 +1,6 @@ -import { getMetadata } from '../utils/utils.js'; +import { getMetadata } from '../../utils/utils.js'; -function isDynamicNavDisabled() { +export function isDynamicNavDisabled() { const dynamicNavDisableValues = getMetadata('dynamic-nav-disable'); if (!dynamicNavDisableValues) return false; diff --git a/libs/features/dynamic-navigation/status.css b/libs/features/dynamic-navigation/status.css new file mode 100644 index 0000000000..9cceb9e33a --- /dev/null +++ b/libs/features/dynamic-navigation/status.css @@ -0,0 +1,173 @@ +.dynamic-nav-status { + border: 2px solid white; + border-radius: 32px; + color: #eee; + font-size: 16px; + padding: 12px 24px; + cursor: pointer; + display: flex; + align-items: center; + margin: 12px; + position: relative; +} + +.dynamic-nav-status .title { + display: flex; +} + +.dynamic-nav-status.active { + background-color: #280; +} + +.dynamic-nav-status.enabled { + background-color: #ec4; +} + +.dynamic-nav-status.inactive { + background-color: #e20; +} + +.dns-badge { + border: 2px solid white; + border-radius: 32px; + background-color: transparent; + box-sizing: border-box; + color: #eee; + padding: 8px; + height: 12px; + width: 12px; + margin: 4px 8px 4px 0; + cursor: pointer; + display: flex; + align-items: center; + position: relative; +} + +.dns-badge::after { + content: ''; + display: block; + box-sizing: border-box; + position: absolute; + width: 6px; + height: 6px; + border-top: 2px solid; + border-right: 2px solid; + transform: rotate(45deg); + left: 5px; + bottom: 5px; + transition-duration: 0.2s; +} + +.dns-badge.dns-open::after{ + transform: rotate(135deg); + transition-duration: 0.2s; +} + +.dynamic-nav-status .hidden { + display: none; +} + +.dynamic-nav-status.enabled .title, +.dynamic-nav-status.enabled .dns-badge { + color: var(--feds-color-hamburger); + border-color: var(--feds-color-hamburger); +} + +.dynamic-nav-status .dns-close-container { + display: flex; + justify-content: flex-end; + width: 100%; + height: 10px; + padding: 2px; +} + +.dynamic-nav-status .dns-close { + cursor: pointer; + display: block; + position: absolute; + border: 2px solid white; + border-radius: 32px; + background-color: transparent; + color: #eee; + height: 20px; + width: 20px; + top: 6px; + right: 6px; + box-sizing: border-box; +} + +.dynamic-nav-status .dns-close::after { + content: 'x'; + display: block; + box-sizing: border-box; + position: absolute; + width: 6px; + height: 6px; + left: 4px; + top: -8px; + font-size: 18px; + font-weight: 600; +} + +.dynamic-nav-status .details { + position: absolute; + top: 60px; + right: 0; + background-color: #444; + min-width: 300px; + border-radius: 16px; + box-shadow: 0 0 10px #000; + font-size: 12px; + padding: 20px; + z-index: 1; +} + +.dynamic-nav-status .details::before { + content: ''; + width: 0; + height: 0; + position: absolute; + border-left: 15px solid transparent; + border-right: 15px solid transparent; + border-top: 15px solid #444; + top: -15px; + right: 75px; + rotate: 180deg; +} + +.dynamic-nav-status p{ + margin: 2px; +} + +.dynamic-nav-status .details p { + font-weight: 600; +} + +.dynamic-nav-status .details span { + font-weight: 300; +} + +.dynamic-nav-status .details .additional-info { + border-bottom: 1px solid white; +} + +.dynamic-nav-status .disable-values{ + min-width: 100%; +} + +.dynamic-nav-status .disable-values table { + border-collapse: collapse; + width: 100%; +} + +.dynamic-nav-status .disable-values caption{ + min-width: 100%; + text-align: left; + font-weight: 600; +} + +.dynamic-nav-status .disable-values th, +.dynamic-nav-status .disable-values td { + border: 1px solid rgb(160 160 160); + padding: 8px 10px; +} diff --git a/libs/features/dynamic-navigation/status.js b/libs/features/dynamic-navigation/status.js new file mode 100644 index 0000000000..84aa378907 --- /dev/null +++ b/libs/features/dynamic-navigation/status.js @@ -0,0 +1,133 @@ +import { createTag, getConfig, getMetadata, loadStyle } from '../../utils/utils.js'; +import { isDynamicNavDisabled } from './dynamic-navigation.js'; + +export const ACTIVE = 'active'; +export const ENABLED = 'enabled'; +export const INACTIVE = 'inactive'; +export const tooltipInfo = { + active: 'Displayed in green, this status appears when a user is on an entry page or a page with the Dynamic Nav enabled, indicating that the nav is fully functioning.', + enabled: 'Displayed in yellow, this status indicates that the Dynamic Nav is set to "on," but the user has not yet visited an entry page.', + inactive: 'Displayed in red, this status indicates that the Dynamic Nav is either not configured or has been disabled.', +}; + +const getCurrentSource = (status, storageSource, authoredSource) => { + if (status === 'on') { + return storageSource || authoredSource; + } + return authoredSource; +}; + +const getStatus = (status, disabled, storageSource) => { + if (status === 'entry') return ACTIVE; + + if (disabled) return INACTIVE; + + if (status === 'on' && storageSource) return ACTIVE; + + if (status === 'on' && !storageSource) return ENABLED; + + return INACTIVE; +}; + +const processDisableValues = (arr, elem) => { + if (arr === null || arr === undefined || arr.length === 0) return; + + const diableValueList = arr.split(','); + const table = createTag('table'); + + const tableDOM = ` + Disable Values + + + Key + Value + + + + `; + + table.innerHTML = tableDOM; + const tBody = table.querySelector('tbody'); + + diableValueList.forEach((pair) => { + const itemRow = createTag('tr'); + const [key, value] = pair.split(';'); + const keyElem = createTag('td'); + const valElem = createTag('td'); + keyElem.innerText = key; + valElem.innerText = value; + + itemRow.append(keyElem, valElem); + tBody.append(itemRow); + }); + + elem.append(table); +}; + +const returnPath = (url) => { + if (!url.includes('https://')) return ''; + const sourceUrl = new URL(url); + return sourceUrl.pathname; +}; + +const createStatusWidget = (dynamicNavKey) => { + const storedSource = window.sessionStorage.getItem('gnavSource'); + const authoredSource = getMetadata('gnav-source') || 'Metadata not found: site gnav source'; + const dynamicNavSetting = getMetadata('dynamic-nav'); + const currentSource = getCurrentSource(dynamicNavSetting, storedSource, authoredSource); + const dynamicNavDisableValues = getMetadata('dynamic-nav-disable'); + const status = getStatus(dynamicNavSetting, isDynamicNavDisabled(), storedSource); + const statusWidget = createTag('div', { class: 'dynamic-nav-status' }); + + statusWidget.innerHTML = ` + Dynamic Nav + + `; + + processDisableValues(dynamicNavDisableValues, statusWidget.querySelector('.disable-values')); + statusWidget.classList.add(status); + + statusWidget.addEventListener('click', () => { + statusWidget.querySelector('.details').classList.toggle('hidden'); + statusWidget.querySelector('.dns-badge').classList.toggle('dns-open'); + }); + + return statusWidget; +}; + +export default async function main() { + const { dynamicNavKey, miloLibs, codeRoot, env } = getConfig(); + if (env?.name === 'prod') return; + + loadStyle(`${miloLibs || `${codeRoot}/libs`}/features/dynamic-navigation/status.css`); + + const statusWidget = createStatusWidget(dynamicNavKey); + const topNav = document.querySelector('.feds-topnav'); + const fedsWrapper = document.querySelector('.feds-nav-wrapper'); + const dnsClose = statusWidget.querySelector('.dns-close'); + + dnsClose.addEventListener('click', () => { + topNav.removeChild(statusWidget); + }); + + fedsWrapper.after(statusWidget); +} diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 7f1a434d83..8d2640e364 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -1126,6 +1126,10 @@ export async function loadDeferred(area, blocks, config) { import('../features/personalization/preview.js') .then(({ default: decoratePreviewMode }) => decoratePreviewMode()); } + if (config?.dynamicNavKey) { + const { default: loadDNStatus } = await import('../features/dynamic-navigation/status.js'); + loadDNStatus(); + } } function initSidekick() { diff --git a/test/features/dynamic-nav/dynamicNav.test.js b/test/features/dynamic-nav/dynamicNav.test.js index 909abd9a44..a08c98ba22 100644 --- a/test/features/dynamic-nav/dynamicNav.test.js +++ b/test/features/dynamic-nav/dynamicNav.test.js @@ -1,7 +1,7 @@ import { readFile } from '@web/test-runner-commands'; import { expect } from '@esm-bundle/chai'; import { setConfig } from '../../../libs/utils/utils.js'; -import dynamicNav from '../../../libs/features/dynamic-navigation.js'; +import dynamicNav from '../../../libs/features/dynamic-navigation/dynamic-navigation.js'; describe('Dynamic nav', () => { beforeEach(() => { diff --git a/test/features/dynamic-nav/mocks/status.html b/test/features/dynamic-nav/mocks/status.html new file mode 100644 index 0000000000..7a425891e4 --- /dev/null +++ b/test/features/dynamic-nav/mocks/status.html @@ -0,0 +1,9 @@ +
+ +
diff --git a/test/features/dynamic-nav/status.test.js b/test/features/dynamic-nav/status.test.js new file mode 100644 index 0000000000..7cd9e115bb --- /dev/null +++ b/test/features/dynamic-nav/status.test.js @@ -0,0 +1,248 @@ +import { readFile } from '@web/test-runner-commands'; +import { expect } from '@esm-bundle/chai'; +import { getConfig, setConfig, createTag } from '../../../libs/utils/utils.js'; +import dynamicNav from '../../../libs/features/dynamic-navigation/dynamic-navigation.js'; +import status, { tooltipInfo, ACTIVE, INACTIVE, ENABLED } from '../../../libs/features/dynamic-navigation/status.js'; + +const statusText = (parentElement) => { + const info = { + additionalInfo: parentElement.querySelector('.additional-info span').innerText, + status: parentElement.querySelector('.details .status span').innerText, + setting: parentElement.querySelector('.details .setting span').innerText, + consumerKey: parentElement.querySelector('.details .consumer-key span').innerText, + match: parentElement.querySelector('.nav-source-info p:nth-child(1) span').innerText, + authoredSource: parentElement.querySelector('.nav-source-info p:nth-child(2) span').innerText, + storedSource: parentElement.querySelector('.nav-source-info p:nth-child(3) span').innerText, + }; + return info; +}; + +const GNAV_SOURCE = 'https://main--milo--adobecom.hlx.live/some-source-string'; + +describe('Dynamic Nav Status', () => { + beforeEach(async () => { + const conf = { dynamicNavKey: 'bacom' }; + document.body.innerHTML = await readFile({ path: './mocks/status.html' }); + document.head.innerHTML = ''; + setConfig(conf); + }); + + it('does not load the widget on production', () => { + const conf = getConfig(); + conf.env.name = 'prod'; + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + expect(statusWidget).to.be.null; + }); + + it('loads the status widget', () => { + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + expect(statusWidget).to.not.be.null; + }); + + it('loads the status widget in an active state', () => { + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'on'); + window.sessionStorage.setItem('gnavSource', 'some-source-string'); + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + expect(statusWidget.classList.contains(ACTIVE)).to.be.true; + }); + + it('loads the status widget in an enabled state', () => { + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'on'); + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + expect(statusWidget.classList.contains(ENABLED)).to.be.true; + }); + + it('loads the status widget in an inactive state', () => { + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + expect(statusWidget.classList.contains(INACTIVE)).to.be.true; + }); + + describe('content validation', () => { + it('displays the correct information to the user for the active state "entry"', () => { + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'entry'); + document.querySelector('meta[name="gnav-source"]').setAttribute('content', GNAV_SOURCE); + window.sessionStorage.setItem('gnavSource', GNAV_SOURCE); + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + const info = statusText(statusWidget); + expect(info.additionalInfo).to.equal(tooltipInfo[ACTIVE]); + expect(info.status).to.equal(ACTIVE); + expect(info.setting).to.equal('entry'); + expect(info.consumerKey).to.equal('bacom'); + expect(info.match).to.equal('true'); + expect(info.authoredSource).to.equal('/some-source-string'); + expect(info.storedSource).to.equal('/some-source-string'); + }); + + it('displays the correct information to the user for the active state "on"', () => { + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'on'); + document.querySelector('meta[name="gnav-source"]').setAttribute('content', 'https://main--milo--adobecom.hlx/test'); + window.sessionStorage.setItem('gnavSource', GNAV_SOURCE); + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + const info = statusText(statusWidget); + expect(info.additionalInfo).to.equal(tooltipInfo[ACTIVE]); + expect(info.status).to.equal(ACTIVE); + expect(info.setting).to.equal('on'); + expect(info.consumerKey).to.equal('bacom'); + expect(info.match).to.equal('false'); + expect(info.authoredSource).to.equal('/test'); + expect(info.storedSource).to.equal('/some-source-string'); + }); + + it('displays the correct information to the user for the active state "off"', () => { + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', ''); + document.querySelector('meta[name="gnav-source"]').setAttribute('content', 'https://main--milo--adobecom.hlx/test'); + window.sessionStorage.setItem('gnavSource', GNAV_SOURCE); + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + const info = statusText(statusWidget); + expect(info.additionalInfo).to.equal(tooltipInfo[INACTIVE]); + expect(info.status).to.equal(INACTIVE); + expect(info.setting).to.equal(''); + expect(info.consumerKey).to.equal('bacom'); + expect(info.match).to.equal('true'); + expect(info.authoredSource).to.equal('/test'); + expect(info.storedSource).to.equal('/test'); + }); + + it('displays the correct information to the user for the enabled state "on"', () => { + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'on'); + document.querySelector('meta[name="gnav-source"]').setAttribute('content', 'https://main--milo--adobecom.hlx/test'); + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + const info = statusText(statusWidget); + expect(info.additionalInfo).to.equal(tooltipInfo[ENABLED]); + expect(info.status).to.equal(ENABLED); + expect(info.setting).to.equal('on'); + expect(info.consumerKey).to.equal('bacom'); + expect(info.match).to.equal('true'); + expect(info.authoredSource).to.equal('/test'); + expect(info.storedSource).to.equal('/test'); + }); + }); + + describe('disabled values', () => { + it('loads the status widget in an inactive state when disable values are present', () => { + const disableTag = createTag('meta', { name: 'dynamic-nav-disable', content: 'PrimaryProductName;Commerce Cloud' }); + const ppn = createTag('meta', { name: 'primaryproductname', content: 'Commerce Cloud' }); + document.querySelector('head').append(disableTag, ppn); + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'on'); + document.querySelector('meta[name="gnav-source"]').setAttribute('content', 'https://main--milo--adobecom.hlx/test'); + window.sessionStorage.setItem('gnavSource', GNAV_SOURCE); + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + const info = statusText(statusWidget); + expect(info.additionalInfo).to.equal(tooltipInfo[INACTIVE]); + expect(info.status).to.equal(INACTIVE); + }); + + it('loads the status widget in an active state when disabled values present but not correct', () => { + const disableTag = createTag('meta', { name: 'dynamic-nav-disable', content: 'PrimaryProductName;Digital Media' }); + const ppn = createTag('meta', { name: 'primaryproductname', content: 'Commerce Cloud' }); + document.querySelector('head').append(disableTag, ppn); + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'on'); + document.querySelector('meta[name="gnav-source"]').setAttribute('content', 'https://main--milo--adobecom.hlx/test'); + window.sessionStorage.setItem('gnavSource', GNAV_SOURCE); + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + const info = statusText(statusWidget); + expect(info.additionalInfo).to.equal(tooltipInfo[ACTIVE]); + expect(info.status).to.equal(ACTIVE); + }); + + it('shows the correct disable values that are active in a table', () => { + const disableTag = createTag('meta', { name: 'dynamic-nav-disable', content: 'PrimaryProductName;Commerce Cloud' }); + const ppn = createTag('meta', { name: 'primaryproductname', content: 'Commerce Cloud' }); + document.querySelector('head').append(disableTag, ppn); + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'on'); + document.querySelector('meta[name="gnav-source"]').setAttribute('content', 'https://main--milo--adobecom.hlx/test'); + window.sessionStorage.setItem('gnavSource', GNAV_SOURCE); + + dynamicNav(); + status(); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + const info = statusText(statusWidget); + const disableValuesTable = statusWidget.querySelector('.disable-values'); + expect(info.additionalInfo).to.equal(tooltipInfo[INACTIVE]); + expect(info.status).to.equal(INACTIVE); + expect(disableValuesTable.querySelector('caption')).to.exist; + expect(disableValuesTable.querySelector('tbody tr td:nth-child(1)')).to.exist; + expect(disableValuesTable.querySelector('tbody tr td:nth-child(1)').innerText).to.equal('PrimaryProductName'); + expect(disableValuesTable.querySelector('tbody tr td:nth-child(2)')).to.exist; + expect(disableValuesTable.querySelector('tbody tr td:nth-child(2)').innerText).to.equal('Commerce Cloud'); + }); + }); + + describe('Event listeners', () => { + it('toggles the details', () => { + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'on'); + + dynamicNav(); + status(); + + const dynamicNavStatus = document.querySelector('.dynamic-nav-status'); + const details = dynamicNavStatus.querySelector('.details'); + + expect(details.classList.contains('hidden')).to.be.true; + dynamicNavStatus.click(); + expect(details.classList.contains('hidden')).to.be.false; + }); + + it('removes the whole status when close is clicked', () => { + document.querySelector('meta[name="dynamic-nav"]').setAttribute('content', 'on'); + + dynamicNav(); + status(); + + let dynamicNavStatus = document.querySelector('.dynamic-nav-status'); + const details = dynamicNavStatus.querySelector('.details'); + dynamicNavStatus.click(); + const closeButton = details.querySelector('.dns-close'); + closeButton.click(); + dynamicNavStatus = document.querySelector('.dynamic-nav-status'); + expect(dynamicNavStatus).to.be.null; + }); + }); + + afterEach(() => { + window.sessionStorage.clear(); + }); +}); From 2294df8a2fdbaf797f9fd6b35a7f6eda5e1e0d34 Mon Sep 17 00:00:00 2001 From: Jason Slavin Date: Wed, 25 Sep 2024 09:57:04 -0700 Subject: [PATCH 2/8] Code review comments: cleaned css and added prod check in loadDeferred to disable status. Updated tests --- libs/features/dynamic-navigation/status.css | 8 ++++---- libs/features/dynamic-navigation/status.js | 3 +-- libs/utils/utils.js | 2 +- test/features/dynamic-nav/status.test.js | 17 +++++++++++++---- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/libs/features/dynamic-navigation/status.css b/libs/features/dynamic-navigation/status.css index 9cceb9e33a..d597b87a4d 100644 --- a/libs/features/dynamic-navigation/status.css +++ b/libs/features/dynamic-navigation/status.css @@ -1,4 +1,4 @@ -.dynamic-nav-status { +.dynamic-nav-status { border: 2px solid white; border-radius: 32px; color: #eee; @@ -58,7 +58,7 @@ transition-duration: 0.2s; } -.dns-badge.dns-open::after{ +.dns-badge.dns-open::after { transform: rotate(135deg); transition-duration: 0.2s; } @@ -135,7 +135,7 @@ rotate: 180deg; } -.dynamic-nav-status p{ +.dynamic-nav-status p { margin: 2px; } @@ -160,7 +160,7 @@ width: 100%; } -.dynamic-nav-status .disable-values caption{ +.dynamic-nav-status .disable-values caption { min-width: 100%; text-align: left; font-weight: 600; diff --git a/libs/features/dynamic-navigation/status.js b/libs/features/dynamic-navigation/status.js index 84aa378907..1e7e733c96 100644 --- a/libs/features/dynamic-navigation/status.js +++ b/libs/features/dynamic-navigation/status.js @@ -115,8 +115,7 @@ const createStatusWidget = (dynamicNavKey) => { }; export default async function main() { - const { dynamicNavKey, miloLibs, codeRoot, env } = getConfig(); - if (env?.name === 'prod') return; + const { dynamicNavKey, miloLibs, codeRoot } = getConfig(); loadStyle(`${miloLibs || `${codeRoot}/libs`}/features/dynamic-navigation/status.css`); diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 8d2640e364..718510036f 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -1126,7 +1126,7 @@ export async function loadDeferred(area, blocks, config) { import('../features/personalization/preview.js') .then(({ default: decoratePreviewMode }) => decoratePreviewMode()); } - if (config?.dynamicNavKey) { + if (config?.dynamicNavKey && config?.env?.name !== 'prod') { const { default: loadDNStatus } = await import('../features/dynamic-navigation/status.js'); loadDNStatus(); } diff --git a/test/features/dynamic-nav/status.test.js b/test/features/dynamic-nav/status.test.js index 7cd9e115bb..45bf8bdcf0 100644 --- a/test/features/dynamic-nav/status.test.js +++ b/test/features/dynamic-nav/status.test.js @@ -1,6 +1,6 @@ import { readFile } from '@web/test-runner-commands'; import { expect } from '@esm-bundle/chai'; -import { getConfig, setConfig, createTag } from '../../../libs/utils/utils.js'; +import { getConfig, setConfig, createTag, loadDeferred } from '../../../libs/utils/utils.js'; import dynamicNav from '../../../libs/features/dynamic-navigation/dynamic-navigation.js'; import status, { tooltipInfo, ACTIVE, INACTIVE, ENABLED } from '../../../libs/features/dynamic-navigation/status.js'; @@ -27,17 +27,26 @@ describe('Dynamic Nav Status', () => { setConfig(conf); }); - it('does not load the widget on production', () => { + it('does not load the widget on production', async () => { const conf = getConfig(); conf.env.name = 'prod'; - dynamicNav(); - status(); + await loadDeferred(document, [], conf, () => {}); const statusWidget = document.querySelector('.dynamic-nav-status'); expect(statusWidget).to.be.null; }); + it('does load the widget on a lower env', async () => { + const conf = getConfig(); + conf.env.name = 'local'; + + await loadDeferred(document, [], conf, () => {}); + + const statusWidget = document.querySelector('.dynamic-nav-status'); + expect(statusWidget).to.exist; + }); + it('loads the status widget', () => { dynamicNav(); status(); From d04034194003dd92201e6ea0edaef85fd372c70b Mon Sep 17 00:00:00 2001 From: Jason Slavin Date: Wed, 25 Sep 2024 10:27:19 -0700 Subject: [PATCH 3/8] Moving styles into utils, as loading within module caused CLS --- libs/features/dynamic-navigation/status.js | 5 +---- libs/utils/utils.js | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libs/features/dynamic-navigation/status.js b/libs/features/dynamic-navigation/status.js index 1e7e733c96..02921a3021 100644 --- a/libs/features/dynamic-navigation/status.js +++ b/libs/features/dynamic-navigation/status.js @@ -115,10 +115,7 @@ const createStatusWidget = (dynamicNavKey) => { }; export default async function main() { - const { dynamicNavKey, miloLibs, codeRoot } = getConfig(); - - loadStyle(`${miloLibs || `${codeRoot}/libs`}/features/dynamic-navigation/status.css`); - + const { dynamicNavKey } = getConfig(); const statusWidget = createStatusWidget(dynamicNavKey); const topNav = document.querySelector('.feds-topnav'); const fedsWrapper = document.querySelector('.feds-nav-wrapper'); diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 718510036f..9dec87646f 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -1127,6 +1127,8 @@ export async function loadDeferred(area, blocks, config) { .then(({ default: decoratePreviewMode }) => decoratePreviewMode()); } if (config?.dynamicNavKey && config?.env?.name !== 'prod') { + const { miloLibs } = getConfig(); + loadStyle(`${miloLibs}/features/dynamic-navigation/status.css`); const { default: loadDNStatus } = await import('../features/dynamic-navigation/status.js'); loadDNStatus(); } From 2536c25eeb139acf38c92edc0740a99546623180 Mon Sep 17 00:00:00 2001 From: Jason Slavin Date: Wed, 25 Sep 2024 10:35:34 -0700 Subject: [PATCH 4/8] CSS typo --- libs/features/dynamic-navigation/status.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/features/dynamic-navigation/status.css b/libs/features/dynamic-navigation/status.css index d597b87a4d..71b8b50005 100644 --- a/libs/features/dynamic-navigation/status.css +++ b/libs/features/dynamic-navigation/status.css @@ -151,7 +151,7 @@ border-bottom: 1px solid white; } -.dynamic-nav-status .disable-values{ +.dynamic-nav-status .disable-values { min-width: 100%; } From 753280a872feaaad1ad661a72015df179a14c23e Mon Sep 17 00:00:00 2001 From: Jason Slavin Date: Thu, 26 Sep 2024 10:22:26 -0700 Subject: [PATCH 5/8] ESLint error --- libs/features/dynamic-navigation/status.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/features/dynamic-navigation/status.js b/libs/features/dynamic-navigation/status.js index 02921a3021..61fdc539fd 100644 --- a/libs/features/dynamic-navigation/status.js +++ b/libs/features/dynamic-navigation/status.js @@ -1,4 +1,4 @@ -import { createTag, getConfig, getMetadata, loadStyle } from '../../utils/utils.js'; +import { createTag, getConfig, getMetadata } from '../../utils/utils.js'; import { isDynamicNavDisabled } from './dynamic-navigation.js'; export const ACTIVE = 'active'; From 11ba48fc37af3d08799e9cff6b42d75e5806985b Mon Sep 17 00:00:00 2001 From: Jason Slavin Date: Thu, 26 Sep 2024 10:37:12 -0700 Subject: [PATCH 6/8] Change for clarity in utils --- libs/utils/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 9dec87646f..91460265fc 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -1127,7 +1127,7 @@ export async function loadDeferred(area, blocks, config) { .then(({ default: decoratePreviewMode }) => decoratePreviewMode()); } if (config?.dynamicNavKey && config?.env?.name !== 'prod') { - const { miloLibs } = getConfig(); + const { miloLibs } = config; loadStyle(`${miloLibs}/features/dynamic-navigation/status.css`); const { default: loadDNStatus } = await import('../features/dynamic-navigation/status.js'); loadDNStatus(); From 761a6bb6cbbd3676ba29572b7df897acdc97bcd5 Mon Sep 17 00:00:00 2001 From: Jason Slavin Date: Tue, 1 Oct 2024 11:07:39 -0700 Subject: [PATCH 7/8] PR requested changes --- libs/features/dynamic-navigation/status.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/libs/features/dynamic-navigation/status.js b/libs/features/dynamic-navigation/status.js index 61fdc539fd..0bc2f66ad4 100644 --- a/libs/features/dynamic-navigation/status.js +++ b/libs/features/dynamic-navigation/status.js @@ -29,13 +29,13 @@ const getStatus = (status, disabled, storageSource) => { return INACTIVE; }; -const processDisableValues = (arr, elem) => { - if (arr === null || arr === undefined || arr.length === 0) return; +const processDisableValues = (valueStr, elem) => { + if (!valueStr || valueStr.length === 0) return; - const diableValueList = arr.split(','); + const disableValueList = valueStr.split(','); const table = createTag('table'); - const tableDOM = ` + table.innerHTML = ` Disable Values @@ -46,10 +46,9 @@ const processDisableValues = (arr, elem) => { `; - table.innerHTML = tableDOM; const tBody = table.querySelector('tbody'); - diableValueList.forEach((pair) => { + disableValueList.forEach((pair) => { const itemRow = createTag('tr'); const [key, value] = pair.split(';'); const keyElem = createTag('td'); @@ -65,7 +64,7 @@ const processDisableValues = (arr, elem) => { }; const returnPath = (url) => { - if (!url.includes('https://')) return ''; + if (!url.startsWith('https://')) return ''; const sourceUrl = new URL(url); return sourceUrl.pathname; }; From c37c02cad040406b12a22345d4f9dd4700201c54 Mon Sep 17 00:00:00 2001 From: Jason Slavin Date: Mon, 7 Oct 2024 14:26:22 -0700 Subject: [PATCH 8/8] Changes requested from QA --- .../dynamic-navigation/dynamic-navigation.js | 8 +++++--- libs/features/dynamic-navigation/status.css | 6 ++++++ libs/features/dynamic-navigation/status.js | 15 ++++++++++----- test/features/dynamic-nav/status.test.js | 2 ++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/libs/features/dynamic-navigation/dynamic-navigation.js b/libs/features/dynamic-navigation/dynamic-navigation.js index c7ae1674c8..7354518a58 100644 --- a/libs/features/dynamic-navigation/dynamic-navigation.js +++ b/libs/features/dynamic-navigation/dynamic-navigation.js @@ -1,19 +1,21 @@ import { getMetadata } from '../../utils/utils.js'; -export function isDynamicNavDisabled() { +export function foundDisableValues() { const dynamicNavDisableValues = getMetadata('dynamic-nav-disable'); if (!dynamicNavDisableValues) return false; const metadataPairsMap = dynamicNavDisableValues.split(',').map((pair) => pair.split(';')); - return metadataPairsMap.some(([metadataKey, metadataContent]) => { + const foundValues = metadataPairsMap.filter(([metadataKey, metadataContent]) => { const metaTagContent = getMetadata(metadataKey.toLowerCase()); return (metaTagContent && metaTagContent.toLowerCase() === metadataContent.toLowerCase()); }); + + return foundValues.length ? foundValues : false; } export default function dynamicNav(url, key) { - if (isDynamicNavDisabled()) return url; + if (foundDisableValues()) return url; const metadataContent = getMetadata('dynamic-nav'); if (metadataContent === 'entry') { diff --git a/libs/features/dynamic-navigation/status.css b/libs/features/dynamic-navigation/status.css index 71b8b50005..7d816a8fe9 100644 --- a/libs/features/dynamic-navigation/status.css +++ b/libs/features/dynamic-navigation/status.css @@ -171,3 +171,9 @@ border: 1px solid rgb(160 160 160); padding: 8px 10px; } + +@media screen and (max-width: 600px) { + .dynamic-nav-status { + display: none; + } +} diff --git a/libs/features/dynamic-navigation/status.js b/libs/features/dynamic-navigation/status.js index 0bc2f66ad4..792f586765 100644 --- a/libs/features/dynamic-navigation/status.js +++ b/libs/features/dynamic-navigation/status.js @@ -1,5 +1,5 @@ import { createTag, getConfig, getMetadata } from '../../utils/utils.js'; -import { isDynamicNavDisabled } from './dynamic-navigation.js'; +import { foundDisableValues } from './dynamic-navigation.js'; export const ACTIVE = 'active'; export const ENABLED = 'enabled'; @@ -29,11 +29,12 @@ const getStatus = (status, disabled, storageSource) => { return INACTIVE; }; -const processDisableValues = (valueStr, elem) => { +const processDisableValues = (valueStr, elem, foundValues = false) => { if (!valueStr || valueStr.length === 0) return; const disableValueList = valueStr.split(','); const table = createTag('table'); + const flatValues = Array.isArray(foundValues) && foundValues.flat(); table.innerHTML = ` Disable Values @@ -41,6 +42,7 @@ const processDisableValues = (valueStr, elem) => { Key Value + Match? @@ -53,10 +55,12 @@ const processDisableValues = (valueStr, elem) => { const [key, value] = pair.split(';'); const keyElem = createTag('td'); const valElem = createTag('td'); + const matchElem = createTag('td'); keyElem.innerText = key; valElem.innerText = value; + matchElem.innerText = flatValues && flatValues.includes(value) ? 'yes' : 'no'; - itemRow.append(keyElem, valElem); + itemRow.append(keyElem, valElem, matchElem); tBody.append(itemRow); }); @@ -75,7 +79,8 @@ const createStatusWidget = (dynamicNavKey) => { const dynamicNavSetting = getMetadata('dynamic-nav'); const currentSource = getCurrentSource(dynamicNavSetting, storedSource, authoredSource); const dynamicNavDisableValues = getMetadata('dynamic-nav-disable'); - const status = getStatus(dynamicNavSetting, isDynamicNavDisabled(), storedSource); + const foundValues = foundDisableValues(); + const status = getStatus(dynamicNavSetting, foundValues.length >= 1, storedSource); const statusWidget = createTag('div', { class: 'dynamic-nav-status' }); statusWidget.innerHTML = ` @@ -102,7 +107,7 @@ const createStatusWidget = (dynamicNavKey) => { `; - processDisableValues(dynamicNavDisableValues, statusWidget.querySelector('.disable-values')); + processDisableValues(dynamicNavDisableValues, statusWidget.querySelector('.disable-values'), foundValues); statusWidget.classList.add(status); statusWidget.addEventListener('click', () => { diff --git a/test/features/dynamic-nav/status.test.js b/test/features/dynamic-nav/status.test.js index 45bf8bdcf0..2b9a261e61 100644 --- a/test/features/dynamic-nav/status.test.js +++ b/test/features/dynamic-nav/status.test.js @@ -217,6 +217,8 @@ describe('Dynamic Nav Status', () => { expect(disableValuesTable.querySelector('tbody tr td:nth-child(1)').innerText).to.equal('PrimaryProductName'); expect(disableValuesTable.querySelector('tbody tr td:nth-child(2)')).to.exist; expect(disableValuesTable.querySelector('tbody tr td:nth-child(2)').innerText).to.equal('Commerce Cloud'); + expect(disableValuesTable.querySelector('tbody tr td:nth-child(3)')).to.exist; + expect(disableValuesTable.querySelector('tbody tr td:nth-child(3)').innerText).to.equal('yes'); }); });