Skip to content

Commit 2e6d867

Browse files
committed
Test coverage
1 parent 0fb7473 commit 2e6d867

File tree

8 files changed

+159
-31
lines changed

8 files changed

+159
-31
lines changed

src/app/pages/details/common/resource-box/resource-box-utils.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ function encodeLocation(search: string) {
1818
}
1919

2020
export type ResourceData = {
21-
linkExternal: string;
22-
linkDocumentUrl: string;
21+
linkExternal?: string;
22+
linkDocumentUrl?: string;
2323
linkDocument?: {
2424
file: string;
2525
};
@@ -29,18 +29,18 @@ export type ResourceData = {
2929
heading: string;
3030
resourceCategory: string;
3131
resourceUnlocked: boolean;
32-
creatorFestResource: boolean;
32+
creatorFestResource?: boolean;
3333
description: string;
3434
comingSoonText?: string;
3535
};
3636
comingSoonText: string | null;
3737
videoReferenceNumber?: number | null;
3838
k12?: boolean;
3939
printLink: string | null;
40-
resourceUnlocked: boolean;
40+
resourceUnlocked?: boolean;
4141
lockedText?: string;
42-
resourceHeading: string;
43-
resourceDescription: string;
42+
resourceHeading?: string;
43+
resourceDescription?: string;
4444
featured?: boolean;
4545
};
4646

src/app/pages/details/desktop-view/instructor-resource-tab/partners/partners.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
2-
import {useNavigate} from 'react-router-dom';
2+
import {NavigateOptions, useNavigate} from 'react-router-dom';
33
import PartnerCard from '~/components/partner-card/partner-card';
4+
import {assertNotNull} from '~/helpers/data';
45
import './partners.scss';
56

67
type PartnerBlurb = {
@@ -32,12 +33,10 @@ export default function Partners({
3233
(event: React.MouseEvent<HTMLAnchorElement>) => {
3334
const destUrl = event.currentTarget.getAttribute('href');
3435

35-
if (destUrl) {
36-
navigate(destUrl, {
37-
book: bookAbbreviation,
38-
redirect: true
39-
} as never);
40-
}
36+
navigate(assertNotNull(destUrl), {
37+
book: bookAbbreviation,
38+
redirect: true
39+
} as NavigateOptions);
4140
event.preventDefault();
4241
},
4342
[navigate, bookAbbreviation]

src/app/pages/details/phone-view/instructor-resources-pane/instructor-resources-pane.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
33
import {faSignOutAlt} from '@fortawesome/free-solid-svg-icons/faSignOutAlt';
4-
import {useNavigate} from 'react-router-dom';
4+
import {NavigateOptions, useNavigate} from 'react-router-dom';
55
import {resourceBoxModel, useResources} from '../../common/resource-box/resource-box-utils';
66
import FeaturedResourcesSection from '../../common/featured-resources/featured-resources';
77
import ResourceBoxes from '../../common/resource-box/resource-boxes';
@@ -10,6 +10,7 @@ import useUserContext, {type UserStatus} from '~/contexts/user';
1010
import useWindowContext, {WindowContextProvider} from '~/contexts/window';
1111
import type {ContextValues} from '../../context';
1212
import './instructor-resources-pane.scss';
13+
import {assertNotNull} from '~/helpers/data';
1314

1415
export function InstructorResourcesPane({
1516
model,
@@ -50,7 +51,7 @@ export function InstructorResourcesPane({
5051
navigate('/partners', {
5152
book: model.salesforceAbbreviation,
5253
redirect: true
53-
} as never);
54+
} as NavigateOptions);
5455
}
5556

5657
return (
@@ -62,7 +63,7 @@ export function InstructorResourcesPane({
6263
models={featuredModels}
6364
/>
6465
)}
65-
<a className="card filter-for-book" onClick={goToPartners}>
66+
<a className="card filter-for-book" href="/partners" onClick={goToPartners}>
6667
OpenStax Partners <FontAwesomeIcon icon={faSignOutAlt} />
6768
</a>
6869
<div
@@ -92,7 +93,7 @@ function StubUnlessDisplayed({
9293
const {innerWidth, scrollY} = useWindowContext();
9394

9495
React.useEffect(
95-
() => setY((y || ref.current?.getBoundingClientRect().y) ?? null),
96+
() => setY(y || assertNotNull(ref.current).getBoundingClientRect().y),
9697
[innerWidth, scrollY, y]
9798
);
9899

@@ -112,9 +113,6 @@ export default function LoadUserStatusThenInstructorPane({
112113
}) {
113114
const {userStatus} = useUserContext();
114115

115-
if (!userStatus) {
116-
return null;
117-
}
118116
return (
119117
<WindowContextProvider>
120118
<StubUnlessDisplayed model={model} userStatus={userStatus} />

src/app/pages/details/phone-view/student-resources-pane/student-resources-pane.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,5 @@ export default function LoadUserStatusThenStudentPane({
5252
}) {
5353
const {userStatus} = useUserContext();
5454

55-
if (!userStatus) {
56-
return null;
57-
}
5855
return <StudentResourcesPane model={model} userStatus={userStatus} />;
5956
}

test/src/data/details-college-algebra.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1313,7 +1313,19 @@ const details = {
13131313
},
13141314
"partner_list_label": "Technology Partners",
13151315
"partner_page_link_text": "See all partners",
1316-
"videos": [],
1316+
"videos": [
1317+
{
1318+
"type": "video",
1319+
"value": [
1320+
{
1321+
"title": "Interface",
1322+
"description": "<p data-block-key=\"ftocg\">As the world’s largest producer of carpet tile and other flooring materials, Interface is everywhere. Their modular products foster a great degree of creativity and innovation in interior design and function, and allow their clients to have a positive impact on the world around them. Interface’s sustainability principles and initiatives -- Mission Zero and Climate Take Back -- have led to powerful community engagement and revolutionary technological enhancements.<br/><br/>This video is part of the OpenStax Business series, which showcases entrepreneurs and businesses with a focus on purpose, principles, and best business practices. It can be used with any OpenStax business textbook.</p>",
1323+
"embed": "<script src=\"https://fast.wistia.com/embed/medias/hyb2pj6nrg.jsonp\" async></script><script src=\"https://fast.wistia.com/assets/external/E-v1.js\" async></script><div class=\"wistia_embed wistia_async_hyb2pj6nrg\" style=\"height:360px;position:relative;width:640px\"><div class=\"wistia_swatch\" style=\"height:100%;left:0;opacity:0;overflow:hidden;position:absolute;top:0;transition:opacity 200ms;width:100%;\"><img src=\"https://fast.wistia.com/embed/medias/hyb2pj6nrg/swatch\" style=\"filter:blur(5px);height:100%;object-fit:contain;width:100%;\" alt=\"\" aria-hidden=\"true\" onload=\"this.parentNode.style.opacity=1;\" /></div></div>"
1324+
}
1325+
],
1326+
"id": "28d0ee63-04e0-4128-ba2b-9239b7da5e4e"
1327+
}
1328+
],
13171329
"promote_image": null,
13181330
"last_updated_pdf": null,
13191331
"featured_resources_header": null
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// details-tab is dynamically loaded, so needs to be tested separately
2+
import React from 'react';
3+
import {render, screen} from '@testing-library/preact';
4+
import DetailsTab from '~/pages/details/desktop-view/details-tab/details-tab';
5+
import {LanguageContextProvider} from '~/contexts/language';
6+
7+
type Props = Parameters<typeof DetailsTab>[0];
8+
9+
jest.mock('~/pages/details/common/get-this-title');
10+
11+
const model = {
12+
id: 2,
13+
title: 'model-title',
14+
description: 'model-description',
15+
adoptions: 69,
16+
slug: 'whatever'
17+
} as Props['model'];
18+
19+
function Component(props: Props) {
20+
return (
21+
<LanguageContextProvider>
22+
<DetailsTab {...props} />
23+
</LanguageContextProvider>
24+
);
25+
}
26+
27+
describe('details-tab', () => {
28+
it('renders (English)', () => {
29+
render(<Component model={model} polish={false} />);
30+
screen.getByRole('heading', {level: 3, name: 'Summary'});
31+
});
32+
it('renders (Polish)', () => {
33+
render(<Component model={model} polish={true} />);
34+
screen.getByRole('heading', {level: 3, name: 'Przejdź do książki'});
35+
});
36+
});

test/src/pages/details/details.test.tsx

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import ShellContextProvider from '~/../../test/helpers/shell-context';
88
import * as DH from '~/helpers/use-document-head';
99
import $ from '~/helpers/$';
1010
import * as WC from '~/contexts/window';
11+
import * as RBU from '~/pages/details/common/resource-box/resource-box-utils';
1112

1213
// Tamp down meaningless errors
1314
jest.mock('~/models/rex-release', () =>
@@ -23,14 +24,15 @@ jest.mock('~/models/table-of-contents-html', () =>
2324
jest.fn().mockReturnValue(Promise.resolve({}))
2425
);
2526

27+
jest.spyOn(window, 'scrollBy').mockImplementation(() => null);
2628
jest.spyOn(DH, 'setPageTitleAndDescriptionFromBookData').mockReturnValue();
2729
const spyIsPolish = jest.spyOn($, 'isPolish');
2830
const spyWindowContext = jest.spyOn(WC, 'default');
2931

30-
function Component() {
32+
function Component({path='/details/books/college-algebra'}) {
3133
return (
3234
<ShellContextProvider>
33-
<MemoryRouter initialEntries={['/details/books/college-algebra']}>
35+
<MemoryRouter initialEntries={[path]}>
3436
<Routes>
3537
<Route
3638
path="/details/books/:title"
@@ -58,27 +60,74 @@ function lengthOfView(phoneOrBigger: string) {
5860
}
5961

6062
describe('Details page', () => {
63+
const user = userEvent.setup();
64+
const saveWarn = console.warn;
65+
6166
beforeEach(() => {
6267
document.head.innerHTML = '';
6368
const el = document.createElement('meta');
6469

6570
el.setAttribute('name', 'description');
6671
document.head.appendChild(el);
6772
});
73+
console.debug = jest.fn();
6874

69-
it('renders book', async () => {
75+
it('renders book with video data', async () => {
7076
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7177
spyWindowContext.mockReturnValue({innerWidth: 1280} as any);
7278
render(<Component />);
7379
await finishedRendering();
7480
expect(lengthOfView('phone')).toBeUndefined();
75-
expect(lengthOfView('bigger')).toBe(98);
81+
expect(lengthOfView('bigger')).toBe(794);
7682

7783
const jsonLdScript = document.head.querySelector('script');
7884

7985
expect(jsonLdScript?.textContent).toEqual(
8086
expect.stringContaining('mainEntity')
8187
);
88+
const tabs = screen.getAllByRole('tab');
89+
90+
expect(tabs).toHaveLength(4);
91+
// These do not seem to update the tab state as expected, though they
92+
// do exercise some code.
93+
await user.click(tabs[1]);
94+
});
95+
it('renders with Student tab selected', async () => {
96+
const mockLocation = jest.spyOn(window, 'location', 'get').mockReturnValue({
97+
...window.location,
98+
search: '?Student%20resources'
99+
});
100+
101+
render(<Component path='/details/books/biology-2e' />);
102+
const tabs = await screen.findAllByRole('tab');
103+
104+
expect(tabs[2].getAttribute('aria-selected')).toBe('true');
105+
await user.click(tabs[1]);
106+
mockLocation.mockRestore();
107+
});
108+
it('renders with Instructor tab selected', async () => {
109+
jest.spyOn(RBU, 'useResources').mockReturnValue({
110+
bookVideoFacultyResources: [],
111+
bookFacultyResources: []
112+
});
113+
const mockLocation = jest.spyOn(window, 'location', 'get').mockReturnValue({
114+
...window.location,
115+
search: '?Instructor%20resources'
116+
});
117+
118+
render(<Component />);
119+
await finishedRendering();
120+
const tabs = screen.getAllByRole('tab');
121+
122+
expect(tabs[1].getAttribute('aria-selected')).toBe('true');
123+
await user.click(tabs[2]);
124+
125+
screen.getByRole('heading', {name: 'Technology Partners'});
126+
console.warn = jest.fn();
127+
await user.click(screen.getByRole('link', {name: 'MagicBox E-Reader'}));
128+
expect(console.warn).toHaveBeenCalled();
129+
console.warn = saveWarn;
130+
mockLocation.mockRestore();
82131
});
83132
it('renders Polish book', async () => {
84133
spyIsPolish.mockReturnValue(true);
@@ -104,19 +153,53 @@ describe('Details page', () => {
104153
);
105154
});
106155
it('renders only phone-view at phone width', async () => {
156+
jest.spyOn(RBU, 'useResources').mockReturnValue({
157+
bookVideoFacultyResources: [],
158+
bookFacultyResources: [
159+
{
160+
featured: true,
161+
linkText: 'Link text',
162+
comingSoonText: '',
163+
printLink: 'print-link',
164+
videoReferenceNumber: 13
165+
},
166+
{
167+
featured: false,
168+
linkText: 'Link text2',
169+
comingSoonText: '',
170+
printLink: 'print-link2',
171+
videoReferenceNumber: null,
172+
resource: {
173+
id: 1,
174+
heading: 'resource-heading',
175+
resourceCategory: 'any',
176+
resourceUnlocked: true,
177+
description: 'resource-description'
178+
}
179+
}
180+
]
181+
});
107182
spyWindowContext.mockReturnValue({innerWidth: 480} as any); // eslint-disable-line
108183
render(<Component />);
109184
await finishedRendering();
110185
expect(lengthOfView('phone')).toBe(346);
111186
expect(lengthOfView('bigger')).toBeUndefined();
187+
188+
jest.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({
189+
y: 100
190+
} as any); // eslint-disable-line
191+
await user.click(screen.getByRole('button', {name: 'Instructor resources updated'}));
192+
console.warn = jest.fn();
193+
await user.click(await screen.findByRole('link', {name: 'OpenStax Partners'}));
194+
expect(console.warn).toHaveBeenCalled();
195+
console.warn = saveWarn;
112196
});
113197
it('toggles authors at phone width', async () => {
114198
spyWindowContext.mockReturnValue({innerWidth: 480} as any); // eslint-disable-line
115199
render(<Component />);
116200
await finishedRendering();
117201
const authorToggle = await screen.findByText('Authors');
118202
const detailsEl = authorToggle.closest('details');
119-
const user = userEvent.setup();
120203

121204
await user.click(authorToggle);
122205
expect(detailsEl?.open).toBe(true);

test/src/pages/details/left-content.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ describe('left-content', () => {
1919
comingSoon: false,
2020
iconType: 'lock',
2121
heading: 'heading',
22-
double: false
23-
} as unknown as ModelType; // incomplete, but it's enough for testing
22+
double: false,
23+
bookModel: {
24+
id: 1
25+
}
26+
} as ModelType; // incomplete, but it's enough for testing
2427
const link = {url: '#good-url', text: 'button-label'};
2528
// Setup option prevents await click from hanging when using faketimers
2629
const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime});

0 commit comments

Comments
 (0)