* spelling: access Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: additional Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: administrative Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: aggregate Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: assigns Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: attributes Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: autocomplete Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: because Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: between Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: bootstrap Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: calculating Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: captcha Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: character Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: chosen Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: commenter Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: competitor Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: componentize Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: contrast Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: corresponding Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: description Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: destroyed Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: destroys Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: discussion Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: episode Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: escaped Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: evaluates Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: expired Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: explicitly Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: facebook Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: fragment Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: functionality Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: improper Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: incentive Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: interfere Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: latest Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: message Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: minimum Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: moderator Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: mouseover Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: mutual Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: nonexistent Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: notification Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: occasionally Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: occurrence Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: occurs Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: octokit Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: offset Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: omitted Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: opacity Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: organization Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: organizations Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: overridden Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: override Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: overriding Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: prefill Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: previous Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: profile Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: recycling Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: registered Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: repositories Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: rescuing Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: response Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: returns Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: second Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: separator Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: services Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: subscriber Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: subscription Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: success Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: successful Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: successfully Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: suppress Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: test Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: thought Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: uniqueness Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: unknown Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: unproductive Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: unreachable Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: unsuccessful Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: utilities Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: utility Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: valid Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: voluntarily Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: vomited Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: website Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> * spelling: withholding Signed-off-by: Josh Soref <jsoref@users.noreply.github.com> Co-authored-by: Josh Soref <jsoref@users.noreply.github.com>
247 lines
8.8 KiB
JavaScript
247 lines
8.8 KiB
JavaScript
// TODO: Once this is merged, PR up the removal of this and in other tests and move it to testSetup.js
|
|
import '@testing-library/jest-dom';
|
|
import {
|
|
getByTestId,
|
|
getByLabelText,
|
|
getByText,
|
|
fireEvent,
|
|
waitFor,
|
|
} from '@testing-library/dom';
|
|
import {
|
|
getInstantClick,
|
|
initializeMobileMenu,
|
|
setCurrentPageIconLink,
|
|
initializeMemberMenu,
|
|
} from '../utilities';
|
|
|
|
// TODO: ★★★ These tests should be promoted to E2E tests once we have that in place. ★★★
|
|
describe('top navigation utilities', () => {
|
|
beforeEach(() => {
|
|
// Recreating the body element completely so as to remove any CSS classes
|
|
// that were added to it.
|
|
document.body = document.createElement('body');
|
|
});
|
|
|
|
describe('getInstantClick', () => {
|
|
it('should fail if not found in the alloted time.', async () => {
|
|
await expect(getInstantClick(50)).rejects.toEqual(
|
|
new Error('Unable to resolve InstantClick'),
|
|
);
|
|
});
|
|
|
|
it('should resolve InstantClick.', async () => {
|
|
global.InstantClick = {};
|
|
|
|
expect(await getInstantClick(50)).toEqual(InstantClick);
|
|
|
|
delete global.InstantClick;
|
|
});
|
|
});
|
|
|
|
describe('initializeMobileMenu', () => {
|
|
it('should open the hamburger menu', () => {
|
|
document.body.innerHTML = `
|
|
<button aria-label="nav-button-left" class="crayons-btn crayons-btn--ghost crayons-btn--icon-rounded js-hamburger-trigger inline-block m:hidden mx-2">
|
|
<svg><title>Navigation menu</title></svg>
|
|
</button>
|
|
`;
|
|
|
|
const navButton = getByLabelText(document.body, 'nav-button-left');
|
|
|
|
initializeMobileMenu([navButton], []);
|
|
|
|
expect(document.body.dataset.leftNavState).toBeUndefined();
|
|
navButton.click();
|
|
expect(document.body.dataset.leftNavState).toEqual('open');
|
|
});
|
|
|
|
it('should close the hamburger menu', () => {
|
|
document.body.innerHTML = `
|
|
<button aria-label="nav-button-left" class="crayons-btn crayons-btn--ghost crayons-btn--icon-rounded js-hamburger-trigger inline-block m:hidden mx-2">
|
|
<svg><title>Navigation menu</title></svg>
|
|
</button>
|
|
`;
|
|
const navButton = getByLabelText(document.body, 'nav-button-left');
|
|
|
|
initializeMobileMenu([navButton], []);
|
|
|
|
expect(document.body.dataset.leftNavState).toBeUndefined();
|
|
navButton.click();
|
|
expect(document.body.dataset.leftNavState).toEqual('open');
|
|
|
|
navButton.click();
|
|
expect(document.body.dataset.leftNavState).toEqual('closed');
|
|
});
|
|
|
|
it('should open the more menu', () => {
|
|
document.body.innerHTML = `
|
|
<a href="javascript:void(0)" class="crayons-link crayons-link--secondary crayons-link--block crayons-link--block--indented fs-s js-nav-more-trigger">More...</a>
|
|
<div class="hidden js-nav-more spec-nav-more">
|
|
<div class="flex justify-around p-4 mt-4 border-solid border-0 border-t-1 border-base-10">
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const navMoreLink = getByText(document.body, 'More...');
|
|
|
|
initializeMobileMenu([], [navMoreLink]);
|
|
|
|
navMoreLink.click();
|
|
|
|
expect(navMoreLink).toHaveClass('hidden');
|
|
});
|
|
});
|
|
|
|
describe('setCurrentPageIconLink', () => {
|
|
it('should set the current page icon', () => {
|
|
document.body.innerHTML = `
|
|
<a href="/connect" id="connect-link" class="crayons-header__link crayons-btn crayons-btn--ghost crayons-btn--icon-rounded" aria-label="Connect"></a>
|
|
<a href="/notifications" id="notifications-link" class="crayons-header__link crayons-btn crayons-btn--ghost crayons-btn--icon-rounded" aria-label="Notifications"></a>
|
|
`;
|
|
|
|
const connectLink = getByLabelText(document.body, 'Connect');
|
|
const notificationsLink = getByLabelText(document.body, 'Notifications');
|
|
|
|
const pageEntries = Object.entries({
|
|
'page-1': connectLink,
|
|
'page-2': notificationsLink,
|
|
});
|
|
|
|
const page = 'page-1';
|
|
|
|
setCurrentPageIconLink(page, pageEntries);
|
|
|
|
expect(connectLink).toHaveClass('crayons-header__link--current');
|
|
expect(notificationsLink).not.toHaveClass(
|
|
'crayons-header__link--current',
|
|
);
|
|
});
|
|
|
|
it('should set the current page icon and remove the previous current page icon styling', () => {
|
|
document.body.innerHTML = `
|
|
<a href="/connect" id="connect-link" class="crayons-header__link crayons-btn crayons-btn--ghost crayons-btn--icon-rounded" aria-label="Connect"></a>
|
|
<a href="/notifications" id="notifications-link" class="crayons-header__link crayons-header__link--current crayons-btn crayons-btn--ghost crayons-btn--icon-rounded" aria-label="Notifications"></a>
|
|
`;
|
|
|
|
const connectLink = getByLabelText(document.body, 'Connect');
|
|
const notificationsLink = getByLabelText(document.body, 'Notifications');
|
|
|
|
const pageEntries = Object.entries({
|
|
'page-1': connectLink,
|
|
'page-2': notificationsLink,
|
|
});
|
|
|
|
const page = 'page-1';
|
|
|
|
setCurrentPageIconLink(page, pageEntries);
|
|
|
|
expect(connectLink).toHaveClass('crayons-header__link--current');
|
|
expect(notificationsLink).not.toHaveClass(
|
|
'crayons-header__link--current',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('initializeMemberMenu', () => {
|
|
const initialMenuHTML = `
|
|
<div class="crayons-header__menu" id="crayons-header__menu" data-testid="menu-dropdown">
|
|
<button type="button" class="crayons-header__menu__trigger" id="member-menu-button" aria-expanded="false" aria-label="Navigation menu">
|
|
<span class="crayons-avatar crayons-avatar--l"><img class="crayons-avatar__image" alt="" id="nav-profile-image" src=""></span>
|
|
</button>
|
|
<div class="crayons-dropdown left-2 right-2 s:right-4 s:left-auto p-0 crayons-header__menu__dropdown inline-block">
|
|
<ul class="p-0" id="crayons-header__menu__dropdown__list">
|
|
<li id="user-profile-link-placeholder" class="border-0 border-b-1 border-solid border-base-20 p-1 mb-1">
|
|
<a id="first-nav-link" class="crayons-link crayons-link--block" href="">
|
|
</a>
|
|
</li>
|
|
<li class="px-1 py-1">
|
|
<a href="/signout_confirm" class="crayons-link crayons-link--block" id="last-nav-link">Sign Out</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
it('toggles dropdown menu expanded state', async () => {
|
|
document.body.innerHTML = initialMenuHTML;
|
|
|
|
const menuNavButton = getByLabelText(document.body, 'Navigation menu');
|
|
const memberMenu = getByTestId(document.body, 'menu-dropdown');
|
|
|
|
initializeMemberMenu(memberMenu, menuNavButton);
|
|
|
|
await waitFor(() =>
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
|
);
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false');
|
|
expect(memberMenu).not.toHaveClass('showing');
|
|
|
|
fireEvent.click(menuNavButton);
|
|
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('true');
|
|
expect(memberMenu).toHaveClass('showing');
|
|
|
|
fireEvent.click(menuNavButton);
|
|
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false');
|
|
expect(memberMenu).not.toHaveClass('showing');
|
|
});
|
|
|
|
it('closes menu on Escape key', async () => {
|
|
document.body.innerHTML = initialMenuHTML;
|
|
|
|
const menuNavButton = getByLabelText(document.body, 'Navigation menu');
|
|
const memberMenu = getByTestId(document.body, 'menu-dropdown');
|
|
|
|
initializeMemberMenu(memberMenu, menuNavButton);
|
|
|
|
await waitFor(() =>
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
|
);
|
|
|
|
fireEvent.click(menuNavButton);
|
|
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('true');
|
|
expect(memberMenu).toHaveClass('showing');
|
|
|
|
fireEvent.keyUp(memberMenu, { key: 'Escape' });
|
|
|
|
await waitFor(() =>
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
|
);
|
|
|
|
expect(memberMenu).not.toHaveClass('showing');
|
|
});
|
|
|
|
it('closes the menu on click outside', async () => {
|
|
document.body.innerHTML = `
|
|
<div data-testid="container">
|
|
${initialMenuHTML}
|
|
</div>
|
|
`;
|
|
|
|
const menuNavButton = getByLabelText(document.body, 'Navigation menu');
|
|
const memberMenu = getByTestId(document.body, 'menu-dropdown');
|
|
|
|
initializeMemberMenu(memberMenu, menuNavButton);
|
|
|
|
await waitFor(() =>
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
|
);
|
|
|
|
fireEvent.click(menuNavButton);
|
|
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('true');
|
|
expect(memberMenu).toHaveClass('showing');
|
|
|
|
// Click on a link in the menu to close load a page and close the menu.
|
|
memberMenu.querySelector('a').click();
|
|
|
|
await waitFor(() =>
|
|
expect(menuNavButton.getAttribute('aria-expanded')).toEqual('false'),
|
|
);
|
|
|
|
expect(memberMenu).not.toHaveClass('showing');
|
|
});
|
|
});
|
|
});
|