Skip to content

Commit

Permalink
MWPW-137634 footer unit tests (adobecom#1471)
Browse files Browse the repository at this point in the history
* feat: footer unit tests

* feat: unit tests for mobile, tablet, and wide screen layouts

* hotfix

* optimizing

* Improving Code Readability

* hotfix: added link for favicon

* hotfix

* hotfix

* increased branches code coverage

* increased branches code coverage for global footer

---------

Co-authored-by: Narcis Radu <[email protected]>
  • Loading branch information
robert-bogos and narcis-radu authored Nov 14, 2023
1 parent 19eb173 commit 7c51d4c
Show file tree
Hide file tree
Showing 9 changed files with 575 additions and 1 deletion.
1 change: 1 addition & 0 deletions libs/blocks/global-navigation/utilities/keyboard/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ selectors.popupItems = `
${selectors.privacyLink}
`;

// This method covers focusable elements only, so we aren’t interested in SVGs for example.
const isElementVisible = (elem) => !!(
elem
&& elem instanceof HTMLElement
Expand Down
275 changes: 275 additions & 0 deletions test/blocks/global-footer/global-footer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import { expect } from '@esm-bundle/chai';
import sinon, { stub } from 'sinon';
import { readFile } from '@web/test-runner-commands';
import {
allElementsVisible,
visibleSelectorsDesktop,
visibleSelectorsMobile,
createFullGlobalFooter,
insertDummyElementOnTop,
waitForFooterToDecorate,
allSelectors,
} from './test-utilities.js';
import baseFooter from './mocks/base-footer.js';
import fetchedFooter from './mocks/fetched-footer.js';
import icons from './mocks/icons.js';
import { isElementVisible, mockRes } from '../global-navigation/test-utilities.js';
import placeholders from '../global-navigation/mocks/placeholders.js';

describe('global footer', () => {
let clock = null;
beforeEach(async () => {
document.body.innerHTML = baseFooter;
clock = sinon.useFakeTimers({
toFake: ['setTimeout'],
shouldAdvanceTime: true,
});

stub(window, 'fetch').callsFake(async (url) => {
if (url.includes('/footer')) {
return mockRes({
payload: fetchedFooter(
{ regionPickerHash: '/fragments/regions#langnav' },
),
});
}
if (url.includes('/placeholders')) return mockRes({ payload: placeholders });
if (url.includes('icons.svg')) return mockRes({ payload: icons });
if (url.includes('/regions.plain.html')) return mockRes({ payload: await readFile({ path: '../region-nav/mocks/regions.html' }) });
return null;
});
});

afterEach(() => {
clock.restore();
window.fetch.restore();
document.body.innerHTML = '';
});

describe('wide screen', async () => {
it('should render the footer on wide screen', async () => {
await createFullGlobalFooter({ waitForDecoration: true, viewport: 'wide' });

Object.keys(allSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(true));

expect(allElementsVisible(
visibleSelectorsDesktop,
document.querySelector(allSelectors.container),
)).to.equal(true);
});
});

describe('desktop', () => {
describe('basic sanity tests', () => {
it('should have footer', async () => {
await createFullGlobalFooter({ waitForDecoration: true });
expect(document.querySelector('footer')).to.exist;

Object.keys(allSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(true));

expect(allElementsVisible(
visibleSelectorsDesktop,
document.querySelector(allSelectors.container),
)).to.equal(true);
});

it('should handle failed fetch for footer content', async () => {
window.fetch.restore();
stub(window, 'fetch').callsFake((url) => {
if (url.includes('/footer')) {
return mockRes({
payload: null,
ok: false,
status: 400,
});
}
if (url.includes('/placeholders')) return mockRes({ payload: placeholders });
if (url.includes('icons.svg')) return mockRes({ payload: icons });
return null;
});

const globalFooter = await createFullGlobalFooter({ waitForDecoration: false });
expect(await globalFooter.decorateContent()).to.equal(undefined);
});

it('should handle missing elements', async () => {
const globalFooter = await createFullGlobalFooter({ waitForDecoration: true });
globalFooter.body = document.createElement('div');
expect(await globalFooter.decorateGrid()).to.equal('');
expect(await globalFooter.decorateProducts()).to.equal('');
expect(await globalFooter.decorateRegionPicker()).to.equal('');
expect(await globalFooter.decorateSocial()).to.equal('');
expect(await globalFooter.decoratePrivacy()).to.equal('');
});
});

describe('conditional render tests', () => {
const { container, ...childSelectors } = allSelectors;
it('should render the footer when in viewport', async () => {
await createFullGlobalFooter({ waitForDecoration: true });

Object.keys(allSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(true));

expect(allElementsVisible(
visibleSelectorsDesktop,
document.querySelector(allSelectors.container),
)).to.equal(true);
});

it('should render the footer after 3s when outside of the 300px range of the viewport', async () => {
insertDummyElementOnTop({ height: window.innerHeight + 400 });

await createFullGlobalFooter({ waitForDecoration: false });

Object.keys(childSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(false));

clock.tick(3000);
await waitForFooterToDecorate();

Object.keys(allSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(true));
});

it('should render the footer when outside of the viewport, but within 300px range', async () => {
insertDummyElementOnTop({ height: window.innerHeight + 200 });
const startTime = performance.now();
await createFullGlobalFooter({ waitForDecoration: true });
const endTime = performance.now();
const timeDiff = endTime - startTime;

Object.keys(allSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(true));
// footer decoration should take less than 3s if within 300px range of viewport
expect(timeDiff < 3000).to.equal(true);
});

it('should render the footer when outside of the 300px viewport range, but scrolled into view earlier than 3s', async () => {
insertDummyElementOnTop({ height: window.innerHeight + 400 });
const startTime = performance.now();
await createFullGlobalFooter({ waitForDecoration: false });

Object.keys(childSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(false));

window.scrollBy(0, window.innerHeight);
await waitForFooterToDecorate();

Object.keys(allSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(true));

const endTime = performance.now();
const timeDiff = endTime - startTime;
// footer decoration should take less than 3s when scrolled into view
expect(timeDiff < 3000).to.equal(true);

expect(allElementsVisible(
visibleSelectorsDesktop,
document.querySelector(allSelectors.container),
)).to.equal(true);
});
});

describe('region picker tests', () => {
it('should handle non-empty hash', async () => {
await createFullGlobalFooter({ waitForDecoration: true });

const regionPickerElem = document.querySelector(allSelectors.regionPicker);
regionPickerElem.dispatchEvent(new Event('click'));

expect(regionPickerElem.getAttribute('href') === '#langnav').to.equal(true);
expect(regionPickerElem.getAttribute('aria-expanded')).to.equal('true');

window.dispatchEvent(new Event('milo:modal:closed'));
expect(regionPickerElem.getAttribute('aria-expanded')).to.equal('false');
});

it('should handle empty hash', async () => {
window.fetch.restore();
stub(window, 'fetch').callsFake(async (url) => {
if (url.includes('/footer')) {
return mockRes({
payload: fetchedFooter(
{ regionPickerHash: '' },
),
});
}
if (url.includes('/placeholders')) return mockRes({ payload: placeholders });
if (url.includes('icons.svg')) return mockRes({ payload: icons });
return null;
});

await createFullGlobalFooter({ waitForDecoration: true });
const regionPickerElem = document.querySelector(allSelectors.regionPicker);
expect(regionPickerElem.getAttribute('href') === '#').to.equal(true);

regionPickerElem.dispatchEvent(new Event('click'));
expect(regionPickerElem.getAttribute('aria-expanded')).to.equal('true');

document.body.dispatchEvent(new Event('click', { bubbles: true }));
expect(regionPickerElem.getAttribute('aria-expanded')).to.equal('false');
});
});
});

describe('small desktop', async () => {
it('should render the footer on small desktop', async () => {
await createFullGlobalFooter({ waitForDecoration: true, viewport: 'smallDesktop' });

Object.keys(allSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(true));

expect(allElementsVisible(
visibleSelectorsDesktop,
document.querySelector(allSelectors.container),
)).to.equal(true);
});
});

describe('mobile', () => {
it('should render the footer on mobile', async () => {
await createFullGlobalFooter({ waitForDecoration: true, viewport: 'mobile' });

Object.keys(allSelectors).forEach((key) => expect(
document.querySelector(allSelectors[key]) instanceof HTMLElement,
).to.equal(true));

expect(allElementsVisible(
visibleSelectorsMobile,
document.querySelector(allSelectors.container),
)).to.equal(true);
});

it('should open/close dropdowns on click', async () => {
await createFullGlobalFooter({ waitForDecoration: true, viewport: 'mobile' });

for (const dropdown of Array.from(document.getElementsByClassName('feds-menu-section'))) {
const header = dropdown.querySelector('.feds-menu-headline');
const links = Array.from(dropdown.getElementsByClassName('feds-navLink'));

// open the dropdown
header.dispatchEvent(new Event('click'));
for (const link of links) {
expect(isElementVisible(link)).to.equal(true);
}
// close the dropdown
header.dispatchEvent(new Event('click'));
for (const link of links) {
expect(isElementVisible(link)).to.equal(false);
}
}
});
});
});
1 change: 1 addition & 0 deletions test/blocks/global-footer/mocks/base-footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default '<footer class="global-footer"></footer>';
Loading

0 comments on commit 7c51d4c

Please sign in to comment.