Skip to content

Commit dd626b1

Browse files
committed
Test coverage
main-menu/dropdown main-menu login menu "hinkyQAIssue" came from years ago; the solution wasn't legal. use-navigate-by-key adjusted Test coverage: menu-expander
1 parent 3e1ced8 commit dd626b1

File tree

5 files changed

+135
-27
lines changed

5 files changed

+135
-27
lines changed

src/app/layouts/default/header/menus/main-menu/dropdown/use-navigate-by-key.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import useDropdownContext from '../../dropdown-context';
22
import {isMobileDisplay} from '~/helpers/device';
3+
import { assertDefined } from '~/helpers/data';
34

45
function findNext(dropdownRef: React.MutableRefObject<HTMLDivElement | null>) {
56
const nextSib = document.activeElement?.nextElementSibling;
67

78
if (nextSib?.matches('a')) {
89
return nextSib as HTMLAnchorElement;
910
}
10-
const targets = Array.from(dropdownRef.current?.querySelectorAll('a') ?? []);
11+
const targets = Array.from(assertDefined(dropdownRef.current?.querySelectorAll('a')));
1112
const idx = targets.indexOf(document.activeElement as HTMLAnchorElement);
1213
const nextIdx = (idx + 1) % targets.length;
1314

1415
return targets[nextIdx];
1516
}
1617

17-
// eslint-disable-next-line complexity
1818
function findPrev(
1919
topRef: React.MutableRefObject<HTMLAnchorElement | null>,
2020
dropdownRef: React.MutableRefObject<HTMLDivElement | null>
@@ -24,7 +24,7 @@ function findPrev(
2424
if (prevSib?.matches('a')) {
2525
return prevSib as HTMLAnchorElement;
2626
}
27-
const targets = Array.from(dropdownRef.current?.querySelectorAll('a') ?? []);
27+
const targets = Array.from(assertDefined(dropdownRef.current?.querySelectorAll('a')));
2828
const idx = targets.indexOf(document.activeElement as HTMLAnchorElement);
2929

3030
if (idx === 0) {

src/app/layouts/default/header/menus/main-menu/login-menu/login-menu.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,12 @@ import linkHelper from '~/helpers/link';
77
function LoginLink() {
88
// It's not used directly, but loginLink changes when it does
99
useLocation();
10-
const addressHinkyQAIssue = React.useCallback(
11-
(e: React.MouseEvent<HTMLAnchorElement>) => {
12-
if (e.defaultPrevented) {
13-
e.defaultPrevented = false;
14-
}
15-
},
16-
[]
17-
);
1810

1911
return (
2012
<li className="login-menu nav-menu-item rightmost">
2113
<a
2214
href={linkHelper.loginLink()} className="pardotTrackClick"
23-
data-local="true" role="menuitem" onClick={addressHinkyQAIssue}
15+
data-local="true" role="menuitem"
2416
>
2517
Log in
2618
</a>

test/src/layouts/default/data/osmenu.json renamed to test/src/layouts/default/data/oxmenu.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@
3131
},
3232
{"label": "Webinars", "partial_url": "/webinars"}
3333
]
34+
},
35+
{
36+
"oops": "Not a valid menu"
3437
}
3538
]

test/src/layouts/default/default.test.tsx

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ import StickyNote from '~/layouts/default/header/sticky-note/sticky-note';
1313
import stickyData from './data/sticky.json';
1414
import fundraiserData from './data/fundraiser.json';
1515
import footerData from './data/footer.json';
16-
import oxmenuData from './data/osmenu.json';
16+
import oxmenuData from './data/oxmenu.json';
1717
import giveTodayData from './data/give-today.json';
1818
import giveBannerData from './data/givebanner.json';
1919
import * as CF from '~/helpers/cms-fetch';
20+
import MemoryRouter from '../../../helpers/future-memory-router';
21+
import {Link} from 'react-router-dom';
22+
import * as UUC from '~/contexts/user';
23+
import LoginMenu from '~/layouts/default/header/menus/main-menu/login-menu/login-menu-with-dropdown';
24+
import MenuExpander from '~/layouts/default/header/menus/menu-expander/menu-expander';
25+
import {DropdownContextProvider} from '~/layouts/default/header/menus/dropdown-context';
2026
import '@testing-library/jest-dom';
2127

2228
// Mock external dependencies
@@ -35,13 +41,6 @@ jest.mock('~/contexts/window', () => ({
3541
default: () => ({innerWidth: 1024})
3642
}));
3743

38-
// Having it defined inline caused location updates on every call
39-
let mockPathname = {pathname: '/test-path'};
40-
41-
jest.mock('react-router-dom', () => ({
42-
useLocation: () => mockPathname
43-
}));
44-
4544
const mockUseSharedDataContext = jest
4645
.fn()
4746
.mockReturnValue({stickyFooterState: [false, () => undefined]});
@@ -281,6 +280,19 @@ describe('default layout', () => {
281280
});
282281
}
283282

283+
const myOpenStaxUser = {
284+
contact: {
285+
firstName: 'Roy',
286+
lastName: 'Johnson'
287+
}
288+
};
289+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
290+
const loggedInUser = {userModel: {id: 16249}, myOpenStaxUser} as any;
291+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
292+
const loggedOutUser = {} as any;
293+
294+
const spyUseUserContext = jest.spyOn(UUC, 'default').mockReturnValue(loggedOutUser);
295+
284296
beforeAll(() => {
285297
global.fetch = jest
286298
.fn()
@@ -307,10 +319,11 @@ describe('default layout', () => {
307319
});
308320
});
309321
it('renders; menu opens and closes', async () => {
310-
render(<DefaultLayout />);
311-
expect(await screen.findAllByText('JIT Load Component')).toHaveLength(
312-
2
313-
);
322+
render(<MemoryRouter initialEntries={['/webinars']}>
323+
<DefaultLayout />
324+
<Link to="/kinetic">Change route</Link>
325+
</MemoryRouter>);
326+
expect(await screen.findAllByText('JIT Load Component')).toHaveLength(2);
314327
const toggle = screen.getByRole('button', {
315328
name: 'Toggle Meta Navigation Menu'
316329
});
@@ -328,6 +341,8 @@ describe('default layout', () => {
328341
await user.click(overlay as Element);
329342
expect(toggle.getAttribute('aria-expanded')).toBe('false');
330343

344+
spyUseUserContext.mockReturnValue(loggedInUser);
345+
331346
// close on Escape (but not on other keypress)
332347
await user.click(toggle);
333348
expect(toggle.getAttribute('aria-expanded')).toBe('true');
@@ -336,9 +351,66 @@ describe('default layout', () => {
336351
fireEvent.keyDown(menuContainer, {key: 'Escape'});
337352
expect(toggle.getAttribute('aria-expanded')).toBe('false');
338353

339-
// close on location change (the open is immediately reversed because the path has changed)
340-
mockPathname = {pathname: '/test-path'};
341-
await user.click(toggle);
354+
// close on location change
355+
fireEvent.click(screen.getByText('Change route'));
342356
expect(toggle.getAttribute('aria-expanded')).toBe('false');
357+
358+
const techMenu = screen.getAllByRole('button', {name: 'Technology'})[0];
359+
360+
await user.click(techMenu);
361+
expect(techMenu.getAttribute('aria-expanded')).toBe('false');
362+
await user.click(techMenu);
363+
expect(techMenu.getAttribute('aria-expanded')).toBe('true');
364+
365+
fireEvent.focus(techMenu);
366+
fireEvent.keyDown(techMenu, {key: 'ArrowDown'});
367+
expect(document.activeElement?.textContent).toBe('OpenStax Assignable');
368+
fireEvent.keyDown(techMenu, {key: 'ArrowUp'});
369+
expect(document.activeElement?.textContent).toBe('Technology arrow');
370+
fireEvent.keyDown(techMenu, {key: 'ArrowRight'});
371+
expect(document.activeElement?.textContent).toBe('What we do arrow');
372+
fireEvent.keyDown(techMenu, {key: 'ArrowLeft'});
373+
expect(document.activeElement?.textContent).toBe('What we do arrow');
374+
expect(techMenu.getAttribute('aria-expanded')).toBe('false');
375+
});
376+
it('renders login menu', async () => {
377+
render(<MemoryRouter initialEntries={['/webinars']}>
378+
<LoginMenu />
379+
</MemoryRouter>);
380+
await screen.findByText('Account Dashboard');
381+
});
382+
it('closes mobile menu on location change', () => {
383+
const toggleActive = jest.fn();
384+
385+
render(<DropdownContextProvider>
386+
<MemoryRouter initialEntries={['/webinars']}>
387+
<MenuExpander active={true} toggleActive={toggleActive} />
388+
<Link to="/kinetic">Change route</Link>
389+
</MemoryRouter>
390+
</DropdownContextProvider>);
391+
fireEvent.click(screen.getByText('Change route'));
392+
expect(toggleActive).toHaveBeenCalledWith(false);
393+
});
394+
it('renders login-menu options based on userModel', async () => {
395+
spyUseUserContext.mockReturnValue({
396+
myOpenStaxUser: {
397+
error: 'true'
398+
},
399+
// @ts-expect-error userModel missssing properties
400+
userModel: {
401+
instructorEligible: true,
402+
incompleteSignup: true,
403+
pendingInstructorAccess: true,
404+
emailUnverified: true
405+
}
406+
});
407+
render(<MemoryRouter initialEntries={['/webinars']}>
408+
<LoginMenu />
409+
</MemoryRouter>);
410+
screen.getByRole('link', {name: 'Account Profile'});
411+
screen.getByRole('link', {name: 'Request instructor access'});
412+
screen.getByRole('link', {name: 'Complete your profile'});
413+
screen.getByRole('link', {name: 'Pending instructor access'});
414+
screen.getByRole('link', {name: 'Verify your email address'});
343415
});
344416
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import {render, screen} from '@testing-library/preact';
3+
import ShellContextProvider from '../../../helpers/shell-context';
4+
import MainMenu from '~/layouts/default/header/menus/main-menu/main-menu';
5+
import MemoryRouter from '../../../helpers/future-memory-router';
6+
import * as ULC from '~/contexts/language';
7+
8+
jest.mock('~/models/give-today', () => jest.fn().mockReturnValue({}));
9+
10+
function Component({path = '/'}) {
11+
return <ShellContextProvider>
12+
<MemoryRouter initialEntries={[path]}>
13+
<MainMenu />
14+
</MemoryRouter>
15+
</ShellContextProvider>;
16+
}
17+
18+
describe('main-menu', () => {
19+
it('shows subjects menu when there are subjects', async () => {
20+
render(<Component />);
21+
22+
await screen.findByRole('link', {name: 'Math'});
23+
screen.getByRole('link', {name: 'Spanish'});
24+
screen.getByRole('link', {name: '🍎 For K12 Teachers'});
25+
});
26+
it('hides language options in /details/books paths', async () => {
27+
render(<Component path='/details/books/a-book' />);
28+
29+
await screen.findByRole('link', {name: 'Math'});
30+
expect(screen.queryByRole('link', {name: 'Spanish'})).toBeNull();
31+
});
32+
it('hides k12 item in Spanish', async () => {
33+
const spyLang = jest.spyOn(ULC, 'default').mockReturnValue({language: 'es', setLanguage: jest.fn()});
34+
35+
render(<Component />);
36+
37+
await screen.findByRole('link', {name: 'Math'});
38+
expect(screen.queryByRole('link', {name: '🍎 For K12 Teachers'})).toBeNull();
39+
spyLang.mockReset();
40+
});
41+
});

0 commit comments

Comments
 (0)