import { setLanguage, UtagContainer } from '@cmsgov/design-system'; import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Header from './Header'; function makeHeader(props = {}) { props = Object.assign( { t: (key: string) => key, }, props ); return { user: userEvent.setup(), ...render(
), }; } function getMenuToggle() { return screen.getByRole('button', { name: /^(Open|Close) menu$/ }); } function getMenu() { return document.querySelector('#hc-c-menu'); } function expectMenuToBeOpen() { expect(getMenuToggle()).toHaveAttribute('aria-expanded', 'true'); expect(getMenu()).not.toHaveAttribute('hidden'); } function expectMenuToBeClosed() { expect(getMenuToggle()).toHaveAttribute('aria-expanded', 'false'); expect(getMenu()).toHaveAttribute('hidden'); } describe('Header', function () { let tealiumMock: jest.Mock; beforeEach(() => { tealiumMock = jest.fn(); (window as any as UtagContainer).utag = { link: tealiumMock }; }); it('renders full/homepage header', () => { const { container } = makeHeader({}); expect(container).toMatchSnapshot(); }); it('renders Direct Enrollment banner', () => { const { container } = makeHeader({ deConsumer: true, deBrokerName: 'Foo', }); expect(container).toMatchSnapshot(); }); it('renders logged-in header with firstName', () => { const { container } = makeHeader({ loggedIn: true, firstName: 'John' }); expect(container).toMatchSnapshot(); }); it('renders logged-in header without firstName', () => { const { container } = makeHeader({ loggedIn: true }); expect(container).toMatchSnapshot(); }); it('renders Spanish header', () => { setLanguage('es'); const { container } = makeHeader(); expect(container).toMatchSnapshot(); setLanguage('en'); }); it('passes correct props to SkipNav', () => { makeHeader({ skipNavHref: '', onSkipNavClick: () => jest.fn(), }); const skipNav = screen.getByText('Skip to main content'); expect(skipNav).toBeInTheDocument(); }); it('re-renders with updated links', () => { const { rerender } = render(
); // You always get the logout link and locale link with any custom links unless you // explicitly disable them const links = screen.getAllByRole('link'); expect(links.length).toBe(2); const newProps = { links: [{ href: '/foo', label: 'Foo', ariaLabel: 'Bar in Foo' }] }; rerender(
); const newLinks = screen.getAllByRole('link'); expect(newLinks.length).toBe(5); }); it('should add spanish toggle if logged in', () => { makeHeader({ loggedIn: true, hideLanguageSwitch: false }); const links = screen.getAllByRole('link'); expect(links.length).toBe(2); const languageSwitch = screen.getByText('Español'); expect(languageSwitch).toBeInTheDocument(); }); it('should not add Spanish toggle when hideLanguageSwitch set', () => { makeHeader({ hideLanguageSwitch: true }); const links = screen.getAllByRole('link'); expect(links.length).toBe(3); const languageSwitch = screen.queryByText('Español'); expect(languageSwitch).not.toBeInTheDocument(); }); it('should not add Login Link when hideLoginLink set', () => { makeHeader({ hideLoginLink: true }); const loginLink = screen.queryByText('Log in'); expect(loginLink).not.toBeInTheDocument(); }); it('should not add Logout Link when hideLogoutLink set', () => { makeHeader({ loggedIn: true, hideLogoutLink: true, links: [] }); const logoutLink = screen.queryByText('Log out'); expect(logoutLink).not.toBeInTheDocument(); }); it('renders links with absolute URLs if provided a primaryDomain prop', () => { const { container } = makeHeader({ primaryDomain: 'https://www.healthcare.gov' }); expect(container).toMatchSnapshot(); }); it('should render custom classes provided for Logo', () => { const customClass = 'custom-class-logo'; const { baseElement } = makeHeader({ logoClassName: customClass, }); const logo = baseElement.querySelector('.hc-c-logo'); expect(logo.className.includes(customClass)).toBe(true); }); it('should render custom class and id provided for language switch link', () => { const customClass = 'custom-class-lang'; makeHeader({ languageLinkClassName: customClass, }); const languageSwitch = screen.getAllByText('Español')[0]; expect(languageSwitch).toBeInTheDocument(); expect(languageSwitch.className.includes(customClass)).toBe(true); }); it('should render custom class and id provided for login link', () => { const customClass = 'custom-class-login'; makeHeader({ loginLinkClassName: customClass }); const loginLink = screen.queryAllByText('Log in')[0]; expect(loginLink).toBeInTheDocument(); expect(loginLink.className.includes(customClass)).toBe(true); }); it('should render custom classes provided within links', () => { const customClass = 'custom-class-link'; const { baseElement } = makeHeader({ links: [ { label: 'Custom Link', href: '/', className: customClass, ariaLabel: 'Custom Link', }, ], }); const actionMenuLink = baseElement.querySelector(`.hc-c-logged-out-links__link.${customClass}`); const menuLink = baseElement.querySelector(`.hc-c-menu__link.${customClass}`); expect(actionMenuLink).toBeTruthy(); expect(menuLink).toBeTruthy(); }); it('renders headerBottom inside the header and outside the actions nav', () => { makeHeader({ headerBottom:

Bottom content

}); const content = screen.getByText('Bottom content'); expect(screen.getByRole('banner', { name: 'global' })).toContainElement(content); expect( screen.getByRole('navigation', { name: 'Profile, applications, and coverage' }) ).not.toContainElement(content); }); describe('menu', () => { it('changes the toggle label when clicked', async () => { const { user } = makeHeader(); expect(getMenuToggle()).toHaveAccessibleName('Open menu'); await user.click(getMenuToggle()); expect(getMenuToggle()).toHaveAccessibleName('Close menu'); }); it('opens and closes with Enter and Space on the toggle', async () => { const { user } = makeHeader({ loggedIn: true }); getMenuToggle().focus(); await user.keyboard('{Enter}'); expectMenuToBeOpen(); await user.keyboard('{Enter}'); expectMenuToBeClosed(); await user.keyboard('[Space]'); expectMenuToBeOpen(); await user.keyboard('[Space]'); expectMenuToBeClosed(); }); it('asks a controlled menu to toggle when the toggle is clicked', async () => { const onMenuToggle = jest.fn(); const { user } = makeHeader({ isMenuOpen: false, onMenuToggle, }); expectMenuToBeClosed(); await user.click(getMenuToggle()); expect(onMenuToggle).toHaveBeenCalled(); }); describe('Escape key', () => { it('closes the menu and returns focus to the toggle', async () => { const { user } = makeHeader({ loggedIn: true }); await user.click(getMenuToggle()); expectMenuToBeOpen(); tealiumMock.mockClear(); getMenu().querySelector('a').focus(); await user.keyboard('{Escape}'); expectMenuToBeClosed(); expect(getMenuToggle()).toHaveFocus(); expect(tealiumMock).not.toHaveBeenCalled(); }); it('leaves a closed menu closed', async () => { const { user } = makeHeader({ loggedIn: true }); getMenuToggle().focus(); await user.keyboard('{Escape}'); expectMenuToBeClosed(); }); it('asks an open controlled menu to close', async () => { const onMenuToggle = jest.fn(); const { user } = makeHeader({ loggedIn: true, isMenuOpen: true, onMenuToggle }); getMenu().querySelector('a').focus(); await user.keyboard('{Escape}'); expect(onMenuToggle).toHaveBeenCalledTimes(1); expect(getMenuToggle()).toHaveFocus(); }); it('does not toggle a closed controlled menu', async () => { const onMenuToggle = jest.fn(); const { user } = makeHeader({ loggedIn: true, isMenuOpen: false, onMenuToggle }); getMenuToggle().focus(); await user.keyboard('{Escape}'); expect(onMenuToggle).not.toHaveBeenCalled(); }); }); describe('focus leaving the menu', () => { const headerBottom = After the header; it('closes the menu when Tab moves past the last menu link', async () => { const { user } = makeHeader({ loggedIn: true, headerBottom }); await user.click(getMenuToggle()); expectMenuToBeOpen(); tealiumMock.mockClear(); const links = getMenu().querySelectorAll('a'); links[links.length - 1].focus(); await user.tab(); expectMenuToBeClosed(); expect(screen.getByRole('link', { name: 'After the header' })).toHaveFocus(); expect(tealiumMock).not.toHaveBeenCalled(); }); it('closes the menu when Shift+Tab moves before the toggle', async () => { const { user } = makeHeader({ loggedIn: true }); await user.click(getMenuToggle()); expectMenuToBeOpen(); await user.tab({ shift: true }); expectMenuToBeClosed(); }); it('keeps the menu open while focus moves between the toggle and menu links', async () => { const { user } = makeHeader({ loggedIn: true, headerBottom }); await user.click(getMenuToggle()); await user.tab(); expectMenuToBeOpen(); await user.tab({ shift: true }); expectMenuToBeOpen(); }); it('keeps the menu open when focus leaves the page', async () => { const { user } = makeHeader({ loggedIn: true }); await user.click(getMenuToggle()); fireEvent.focusOut(getMenuToggle(), { relatedTarget: null }); expectMenuToBeOpen(); }); it('asks an open controlled menu to close', async () => { const onMenuToggle = jest.fn(); const { user } = makeHeader({ loggedIn: true, isMenuOpen: true, onMenuToggle, headerBottom, }); expectMenuToBeOpen(); const links = getMenu().querySelectorAll('a'); links[links.length - 1].focus(); await user.tab(); expect(onMenuToggle).toHaveBeenCalledTimes(1); }); it('does not toggle a closed controlled menu', async () => { const onMenuToggle = jest.fn(); const { user } = makeHeader({ loggedIn: true, isMenuOpen: false, onMenuToggle, headerBottom, }); getMenuToggle().focus(); await user.tab(); expect(onMenuToggle).not.toHaveBeenCalled(); }); }); describe('clicking outside', () => { const headerBottom =

Outside the menu

; it('closes the menu without moving focus when non-focusable content outside is clicked', async () => { const { user } = makeHeader({ loggedIn: true, headerBottom }); await user.click(getMenuToggle()); expectMenuToBeOpen(); tealiumMock.mockClear(); await user.click(screen.getByText('Outside the menu')); expectMenuToBeClosed(); expect(getMenuToggle()).not.toHaveFocus(); expect(tealiumMock).not.toHaveBeenCalled(); }); it('closes the menu when non-focusable content outside is touched', async () => { const { user } = makeHeader({ loggedIn: true, headerBottom }); await user.click(getMenuToggle()); expectMenuToBeOpen(); // fireEvent.touchStart dispatches a misnamed event under @testing-library/preact. fireEvent( screen.getByText('Outside the menu'), new TouchEvent('touchstart', { bubbles: true }) ); expectMenuToBeClosed(); }); it('keeps the menu open when clicking inside it', async () => { const { user } = makeHeader({ loggedIn: true, headerBottom }); await user.click(getMenuToggle()); await user.click(getMenu()); expectMenuToBeOpen(); }); it('closes the menu once when the toggle is clicked', async () => { const { user } = makeHeader({ loggedIn: true, headerBottom }); await user.click(getMenuToggle()); tealiumMock.mockClear(); await user.click(getMenuToggle()); expectMenuToBeClosed(); expect(tealiumMock).toHaveBeenCalledTimes(1); expect(tealiumMock.mock.calls[0][0]).toHaveProperty('text', 'menu closed'); }); it('asks an open controlled menu to close when non-focusable content outside is clicked', async () => { const onMenuToggle = jest.fn(); const { user } = makeHeader({ loggedIn: true, isMenuOpen: true, onMenuToggle, headerBottom, }); expectMenuToBeOpen(); await user.click(screen.getByText('Outside the menu')); expect(onMenuToggle).toHaveBeenCalledTimes(1); }); it('asks an open controlled menu to close once when a focusable element outside is clicked', async () => { const onMenuToggle = jest.fn(); const { user } = makeHeader({ loggedIn: true, isMenuOpen: true, onMenuToggle, headerBottom: Outside link, }); getMenuToggle().focus(); await user.click(screen.getByRole('link', { name: 'Outside link' })); expect(onMenuToggle).toHaveBeenCalledTimes(1); }); it('reports one toggle when a focusable element outside is clicked', async () => { const onMenuToggle = jest.fn(); const { user } = makeHeader({ loggedIn: true, onMenuToggle, headerBottom: Outside link, }); await user.click(getMenuToggle()); onMenuToggle.mockClear(); await user.click(screen.getByRole('link', { name: 'Outside link' })); expectMenuToBeClosed(); expect(onMenuToggle).toHaveBeenCalledTimes(1); }); it('does not toggle a closed controlled menu when non-focusable content outside is clicked', async () => { const onMenuToggle = jest.fn(); const { user } = makeHeader({ loggedIn: true, isMenuOpen: false, onMenuToggle, headerBottom, }); await user.click(screen.getByText('Outside the menu')); expect(onMenuToggle).not.toHaveBeenCalled(); }); }); }); });