From aea08275ee3232fb21f2c17b6e98a0a9e79533af Mon Sep 17 00:00:00 2001 From: Bandana Laishram Date: Fri, 9 Aug 2024 01:34:49 +0530 Subject: [PATCH] Standalone GNav for Non-Milo Consumers (#2669) * Adding configuration for header component * Adding test cases * Adding css changes * Updated for css * Setting the origin to federal * Removing promise array * Adding redirect uri in meta * Updating meta insert function * Lint fix * Making error message descriptive --------- Co-authored-by: Snehal Sonawane --- libs/navigation/bootstrapper.js | 24 ++++++++-- libs/navigation/navigation.css | 15 ++++-- libs/navigation/navigation.js | 68 ++++++++++++++++++++++------ test/navigation/bootstrapper.test.js | 31 +++++++++---- test/navigation/navigation.test.js | 17 ++++++- 5 files changed, 122 insertions(+), 33 deletions(-) diff --git a/libs/navigation/bootstrapper.js b/libs/navigation/bootstrapper.js index e98941060b..e644702992 100644 --- a/libs/navigation/bootstrapper.js +++ b/libs/navigation/bootstrapper.js @@ -1,8 +1,6 @@ -export default async function bootstrapBlock(miloConfigs, blockConfig) { - const { miloLibs } = miloConfigs; +export default async function bootstrapBlock(miloLibs, blockConfig) { const { name, targetEl } = blockConfig; - const { getConfig, setConfig, createTag, loadLink, loadScript } = await import(`${miloLibs}/utils/utils.js`); - setConfig({ ...miloConfigs }); + const { getConfig, createTag, loadLink, loadScript } = await import(`${miloLibs}/utils/utils.js`); const { default: initBlock } = await import(`${miloLibs}/blocks/${name}/${name}.js`); const styles = [`${miloLibs}/blocks/${name}/${name}.css`, `${miloLibs}/navigation/navigation.css`]; @@ -12,6 +10,24 @@ export default async function bootstrapBlock(miloConfigs, blockConfig) { const block = createTag(targetEl, { class: name }); document.body[blockConfig.appendType](block); } + // Configure Unav components and redirect uri + if (blockConfig.targetEl === 'header') { + const metaTags = [ + { key: 'unavComponents', name: 'universal-nav' }, + { key: 'redirect', name: 'adobe-home-redirect' }, + ]; + metaTags.forEach((tag) => { + const { key } = tag; + if (blockConfig[key]) { + const metaTag = createTag('meta', { + name: tag.name, + content: blockConfig[key], + }); + document.head.append(metaTag); + } + }); + } + initBlock(document.querySelector(targetEl)); if (blockConfig.targetEl === 'footer') { const { loadPrivacy } = await import(`${miloLibs}/scripts/delayed.js`); diff --git a/libs/navigation/navigation.css b/libs/navigation/navigation.css index e3ef7f1241..96ac72d5de 100644 --- a/libs/navigation/navigation.css +++ b/libs/navigation/navigation.css @@ -1,5 +1,5 @@ /* Extracting the essential styles required for rendering the component independently */ - .global-footer, .dialog-modal { + .global-navigation, .global-footer, .dialog-modal { font-family: 'Adobe Clean', adobe-clean, 'Trebuchet MS', sans-serif; line-height: 27px; color: #2c2c2c; @@ -7,7 +7,7 @@ -webkit-font-smoothing: antialiased; } -.global-footer a, .dialog-modal a { +.global-navigation a, .global-footer a, .dialog-modal a { text-decoration: none; } @@ -15,7 +15,16 @@ color: #035FE6; } -.global-footer img { +.global-navigation img, .global-footer img { max-width: 100%; height: auto; } + +.global-navigation { + font-size: 18px; +} + +header.global-navigation { + height: 64px; + visibility: hidden; +} diff --git a/libs/navigation/navigation.js b/libs/navigation/navigation.js index 5329b5a87d..c74bcaed2e 100644 --- a/libs/navigation/navigation.js +++ b/libs/navigation/navigation.js @@ -1,35 +1,73 @@ -const blockConfig = { - footer: { +const blockConfig = [ + { + key: 'header', + name: 'global-navigation', + targetEl: 'header', + appendType: 'prepend', + params: ['imsClientId'], + }, + { + key: 'footer', name: 'global-footer', targetEl: 'footer', appendType: 'appendChild', + params: ['privacyId', 'privacyLoadDelay'], }, -}; +]; const envMap = { prod: 'https://www.adobe.com', stage: 'https://www.stage.adobe.com', - qa: 'https://feds--milo--adobecom.hlx.page', + qa: 'https://gnav--milo--adobecom.hlx.page', }; + +function getParamsConfigs(configs) { + return blockConfig.reduce((acc, block) => { + block.params.forEach((param) => { + const value = configs[block.key]?.[param]; + if (value !== undefined) { + acc[param] = value; + } + }); + return acc; + }, {}); +} + export default async function loadBlock(configs, customLib) { - const { footer, locale, env = 'prod' } = configs || {}; + const { header, footer, authoringPath, env = 'prod', locale = '' } = configs || {}; const branch = new URLSearchParams(window.location.search).get('navbranch'); const miloLibs = branch ? `https://${branch}--milo--adobecom.hlx.page` : customLib || envMap[env]; - + if (!header && !footer) { + console.error('Global navigation Error: header and footer configurations are missing.'); + return; + } // Relative path can't be used, as the script will run on consumer's app - const { default: bootstrapBlock } = await import(`${miloLibs}/libs/navigation/bootstrapper.js`); - const { default: locales } = await import(`${miloLibs}/libs/utils/locales.js`); + const [{ default: bootstrapBlock }, { default: locales }, { setConfig }] = await Promise.all([ + import(`${miloLibs}/libs/navigation/bootstrapper.js`), + import(`${miloLibs}/libs/utils/locales.js`), + import(`${miloLibs}/libs/utils/utils.js`), + ]); + + const paramConfigs = getParamsConfigs(configs, miloLibs); const clientConfig = { - origin: miloLibs, + origin: `https://main--federal--adobecom.hlx.${env === 'prod' ? 'live' : 'page'}`, miloLibs: `${miloLibs}/libs`, - pathname: `/${locale || ''}`, + pathname: `/${locale}`, locales: configs.locales || locales, + contentRoot: authoringPath || footer.authoringPath, + ...paramConfigs, }; - if (footer) { - const { authoringPath, privacyId, privacyLoadDelay = 3000 } = footer; - blockConfig.delay = privacyLoadDelay; - bootstrapBlock({ ...clientConfig, contentRoot: authoringPath, privacyId }, blockConfig.footer); - } + setConfig(clientConfig); + + blockConfig.forEach((block) => { + const configBlock = configs[block.key]; + if (configBlock) { + bootstrapBlock(`${miloLibs}/libs`, { + ...block, + ...(block.key === 'header' && { unavComponents: configBlock.unavComponents, redirect: configBlock.redirect }), + }); + } + }); } window.loadNavigation = loadBlock; diff --git a/test/navigation/bootstrapper.test.js b/test/navigation/bootstrapper.test.js index 4415788452..18379d4a27 100644 --- a/test/navigation/bootstrapper.test.js +++ b/test/navigation/bootstrapper.test.js @@ -4,21 +4,25 @@ import { stub, useFakeTimers, restore } from 'sinon'; import loadBlock from '../../libs/navigation/bootstrapper.js'; import fetchedFooter from '../blocks/global-footer/mocks/fetched-footer.js'; import placeholders from '../blocks/global-navigation/mocks/placeholders.js'; +import { setConfig } from '../../libs/utils/utils.js'; document.body.innerHTML = await readFile({ path: './mocks/body.html' }); const blockConfig = { - name: 'global-footer', - targetEl: 'footer', - appendType: 'append', - footer: { authoringPath: '/federal/home', privacyLoadDelay: 0 }, + footer: { + name: 'global-footer', + targetEl: 'footer', + appendType: 'append', + }, + header: { + name: 'global-navigation', + targetEl: 'header', + appendType: 'prepend', + unavComponents: 'profile', + }, }; -const miloConfigs = { - origin: 'https://feds--milo--adobecom.hlx.page', - miloLibs: 'http://localhost:2000/libs', - pathname: '/', -}; +const miloLibs = 'http://localhost:2000/libs'; const mockRes = ({ payload, status = 200, ok = true } = {}) => new Promise((resolve) => { resolve({ @@ -43,6 +47,7 @@ describe('Bootstrapper', async () => { if (url.includes('/footer.plain.html')) return mockRes({ payload: await readFile({ path: '../blocks/region-nav/mocks/regions.html' }) }); return null; }); + setConfig({ miloLibs, contentRoot: '/federal/dev' }); }); afterEach(() => { @@ -50,7 +55,7 @@ describe('Bootstrapper', async () => { }); it('Renders the footer block', async () => { - await loadBlock(miloConfigs, blockConfig); + await loadBlock(miloLibs, blockConfig.footer); const clock = useFakeTimers({ toFake: ['setTimeout'], shouldAdvanceTime: true, @@ -59,4 +64,10 @@ describe('Bootstrapper', async () => { const el = document.getElementsByTagName('footer'); expect(el).to.exist; }); + + it('Renders the header block', async () => { + await loadBlock(miloLibs, blockConfig.header); + const el = document.getElementsByTagName('header'); + expect(el).to.exist; + }); }); diff --git a/test/navigation/navigation.test.js b/test/navigation/navigation.test.js index 28b3bacc13..466452b269 100644 --- a/test/navigation/navigation.test.js +++ b/test/navigation/navigation.test.js @@ -6,8 +6,23 @@ document.body.innerHTML = await readFile({ path: './mocks/body.html' }); describe('Navigation component', async () => { it('Renders the footer block', async () => { - await loadBlock({ footer: { authoringPath: '/federal/home' }, env: 'qa' }, 'http://localhost:2000'); + await loadBlock({ authoringPath: '/federal/dev', footer: { privacyId: '12343' }, env: 'qa' }, 'http://localhost:2000'); const el = document.getElementsByTagName('footer'); expect(el).to.exist; }); + + it('Renders the header block', async () => { + await loadBlock({ authoringPath: '/federal/dev', header: { imsClientId: 'fedsmilo' }, env: 'qa' }, 'http://localhost:2000'); + const el = document.getElementsByTagName('header'); + expect(el).to.exist; + }); + + it('Does not render either header or footer if not found in configs', async () => { + document.body.innerHTML = await readFile({ path: './mocks/body.html' }); + await loadBlock({ authoringPath: '/federal/dev', env: 'qa' }, 'http://localhost:2000'); + const header = document.getElementsByTagName('header'); + const footer = document.getElementsByTagName('footer'); + expect(header).to.be.empty; + expect(footer).to.be.empty; + }); });