+
{blurbs.map((blurb) => (
-
@@ -43,11 +69,13 @@ export default function Partners({bookAbbreviation, model}) {
title={blurb.name}
href={blurb.url}
logoUrl={blurb.image}
- tags={[blurb.cost, blurb.type].filter((x) => x)}
+ tags={[blurb.cost, blurb.type].filter(
+ (x) => x
+ ) as string[]}
onClick={onClick}
badgeImage={badgeImage}
- verifiedFeatures={blurb.verifiedFeatures}
- analyticsContentType='Partner Profile'
+ verifiedFeatures={blurb.verifiedFeatures?.join(', ')}
+ analyticsContentType="Partner Profile"
/>
))}
diff --git a/src/app/pages/details/desktop-view/student-resource-tab/import-student-resource-tab.js b/src/app/pages/details/desktop-view/student-resource-tab/import-student-resource-tab.ts
similarity index 100%
rename from src/app/pages/details/desktop-view/student-resource-tab/import-student-resource-tab.js
rename to src/app/pages/details/desktop-view/student-resource-tab/import-student-resource-tab.ts
diff --git a/src/app/pages/details/desktop-view/videos-tab/videos-tab.js b/src/app/pages/details/desktop-view/videos-tab/videos-tab.js
deleted file mode 100644
index 1ed626c63..000000000
--- a/src/app/pages/details/desktop-view/videos-tab/videos-tab.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-import RawHTML from '~/components/jsx-helpers/raw-html';
-import './videos-tab.scss';
-
-export default function VideoTab({videos}) {
- return (
-
- {
- videos.map(({title, description, embed}) =>
-
-
{title}
-
-
-
- )
- }
-
- );
-}
diff --git a/src/app/pages/details/desktop-view/videos-tab/videos-tab.tsx b/src/app/pages/details/desktop-view/videos-tab/videos-tab.tsx
new file mode 100644
index 000000000..e7c17495e
--- /dev/null
+++ b/src/app/pages/details/desktop-view/videos-tab/videos-tab.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import RawHTML from '~/components/jsx-helpers/raw-html';
+import type {VideoContent} from '../../context';
+import './videos-tab.scss';
+
+export default function VideoTab({videos}: {videos: VideoContent[]}) {
+ return (
+
+ {videos.map(({title, description, embed}) => (
+
+
{title}
+
+
+
+ ))}
+
+ );
+}
diff --git a/src/app/pages/details/phone-view/details-pane/details-pane.js b/src/app/pages/details/phone-view/details-pane/details-pane.tsx
similarity index 70%
rename from src/app/pages/details/phone-view/details-pane/details-pane.js
rename to src/app/pages/details/phone-view/details-pane/details-pane.tsx
index 2f27e9634..f830e2ddc 100644
--- a/src/app/pages/details/phone-view/details-pane/details-pane.js
+++ b/src/app/pages/details/phone-view/details-pane/details-pane.tsx
@@ -3,9 +3,16 @@ import RawHTML from '~/components/jsx-helpers/raw-html';
import {Authors, PublicationInfo} from '../../common/common';
import CollapsingPane from '~/components/collapsing-pane/collapsing-pane';
import SavingsBlurb from '../../common/savings-blurb';
+import type {ContextValues} from '../../context';
import './details-pane.scss';
-export default function DetailsPane({polish, model}) {
+export default function DetailsPane({
+ polish,
+ model
+}: {
+ polish: boolean;
+ model: ContextValues;
+}) {
return (
@@ -17,8 +24,10 @@ export default function DetailsPane({polish, model}) {
diff --git a/src/app/pages/details/phone-view/instructor-resources-pane/instructor-resources-pane.js b/src/app/pages/details/phone-view/instructor-resources-pane/instructor-resources-pane.js
deleted file mode 100644
index 01b48174b..000000000
--- a/src/app/pages/details/phone-view/instructor-resources-pane/instructor-resources-pane.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import React from 'react';
-import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
-import {faSignOutAlt} from '@fortawesome/free-solid-svg-icons/faSignOutAlt';
-import {useNavigate} from 'react-router-dom';
-import {resourceBoxModel, useResources} from '../../common/resource-box/resource-box-utils';
-import FeaturedResourcesSection from '../../common/featured-resources/featured-resources';
-import ResourceBoxes from '../../common/resource-box/resource-boxes';
-import VideoResourceBoxes from '../../common/resource-box/video-resource-box';
-import useUserContext from '~/contexts/user';
-import useWindowContext, {WindowContextProvider} from '~/contexts/window';
-import './instructor-resources-pane.scss';
-
-export function InstructorResourcesPane({model, userStatus}) {
- const {
- bookVideoFacultyResources,
- bookFacultyResources
- } = useResources(model.slug);
- const bookId = model.id;
-
- for (const r of bookFacultyResources) {
- r.resource.description = '';
- r.resource.comingSoonText = '';
- }
- const featuredResources = bookFacultyResources.filter((r) => r.featured);
- const featuredModels = featuredResources
- .map((res) => resourceBoxModel(res, userStatus, bookId));
- const referenceModels = bookFacultyResources
- .filter((r) => r.videoReferenceNumber !== null)
- .map((res) => resourceBoxModel(res, userStatus, bookId));
- const otherModels = bookFacultyResources
- .filter((r) =>
- !r.featured && r.videoReferenceNumber === null &&
- r.linkText !== 'View resources'
- )
- .map((res) => resourceBoxModel(res, userStatus, bookId));
- const navigate = useNavigate();
-
- function goToPartners(event) {
- event.preventDefault();
- navigate('/partners', {
- book: model.salesforceAbbreviation,
- redirect: true
- });
- }
-
- return (
-
- {
- featuredModels.length > 0 &&
-
- }
-
- OpenStax Partners{' '}
-
-
-
-
-
-
-
- );
-}
-
-function StubUnlessDisplayed({model, userStatus}) {
- const ref = React.useRef();
- const [y, setY] = React.useState(null);
- const {innerWidth, scrollY} = useWindowContext();
-
- React.useEffect(
- () => setY(y || ref.current?.getBoundingClientRect().y),
- [innerWidth, scrollY, y]
- );
-
- return (
-
- {
- y ?
- :
- null
- }
-
- );
-}
-
-export default function LoadUserStatusThenInstructorPane({model}) {
- const {userStatus} = useUserContext();
-
- if (!userStatus) {
- return null;
- }
- return (
-
-
-
- );
-}
diff --git a/src/app/pages/details/phone-view/instructor-resources-pane/instructor-resources-pane.tsx b/src/app/pages/details/phone-view/instructor-resources-pane/instructor-resources-pane.tsx
new file mode 100644
index 000000000..a80e9e540
--- /dev/null
+++ b/src/app/pages/details/phone-view/instructor-resources-pane/instructor-resources-pane.tsx
@@ -0,0 +1,121 @@
+import React from 'react';
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
+import {faSignOutAlt} from '@fortawesome/free-solid-svg-icons/faSignOutAlt';
+import {NavigateOptions, useNavigate} from 'react-router-dom';
+import {resourceBoxModel, useResources} from '../../common/resource-box/resource-box-utils';
+import FeaturedResourcesSection from '../../common/featured-resources/featured-resources';
+import ResourceBoxes from '../../common/resource-box/resource-boxes';
+import VideoResourceBoxes from '../../common/resource-box/video-resource-box';
+import useUserContext, {type UserStatus} from '~/contexts/user';
+import useWindowContext, {WindowContextProvider} from '~/contexts/window';
+import type {ContextValues} from '../../context';
+import './instructor-resources-pane.scss';
+import {assertNotNull} from '~/helpers/data';
+
+export function InstructorResourcesPane({
+ model,
+ userStatus
+}: {
+ model: ContextValues;
+ userStatus: UserStatus;
+}) {
+ const {bookVideoFacultyResources, bookFacultyResources} = useResources(
+ model.slug
+ );
+
+ for (const r of bookFacultyResources) {
+ if (r.resource) {
+ r.resource.description = '';
+ r.resource.comingSoonText = '';
+ }
+ }
+ const featuredResources = bookFacultyResources.filter((r) => r.featured);
+ const featuredModels = featuredResources.map((res) =>
+ resourceBoxModel(res, userStatus, model)
+ );
+ const referenceModels = bookFacultyResources
+ .filter((r) => r.videoReferenceNumber !== null)
+ .map((res) => resourceBoxModel(res, userStatus, model));
+ const otherModels = bookFacultyResources
+ .filter(
+ (r) =>
+ !r.featured &&
+ r.videoReferenceNumber === null &&
+ r.linkText !== 'View resources'
+ )
+ .map((res) => resourceBoxModel(res, userStatus, model));
+ const navigate = useNavigate();
+
+ function goToPartners(event: React.MouseEvent) {
+ event.preventDefault();
+ navigate('/partners', {
+ book: model.salesforceAbbreviation,
+ redirect: true
+ } as NavigateOptions);
+ }
+
+ return (
+
+ {featuredModels.length > 0 && (
+
+ )}
+
+ OpenStax Partners
+
+
+
+
+
+
+ );
+}
+
+function StubUnlessDisplayed({
+ model,
+ userStatus
+}: {
+ model: ContextValues;
+ userStatus: UserStatus;
+}) {
+ const ref = React.useRef
(null);
+ const [y, setY] = React.useState(null);
+ const {innerWidth, scrollY} = useWindowContext();
+
+ React.useEffect(
+ () => setY(y || assertNotNull(ref.current).getBoundingClientRect().y),
+ [innerWidth, scrollY, y]
+ );
+
+ return (
+
+ {y ? (
+
+ ) : null}
+
+ );
+}
+
+export default function LoadUserStatusThenInstructorPane({
+ model
+}: {
+ model: ContextValues;
+}) {
+ const {userStatus} = useUserContext();
+
+ return (
+
+
+
+ );
+}
diff --git a/src/app/pages/details/phone-view/phone-view.js b/src/app/pages/details/phone-view/phone-view.js
deleted file mode 100644
index e5472b659..000000000
--- a/src/app/pages/details/phone-view/phone-view.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import React from 'react';
-import LetUsKnow from '../common/let-us-know/let-us-know';
-import GetThisTitle from '../common/get-this-title';
-import AccordionGroup from '~/components/accordion-group/accordion-group';
-import useDetailsContext from '../context';
-import $ from '~/helpers/$';
-import JITLoad from '~/helpers/jit-load';
-import {findSelectedTab} from '../common/tab-utils';
-import RawHTML from '~/components/jsx-helpers/raw-html';
-import {useTableOfContents} from '../common/hooks';
-import {ErrataContents, GiveLink} from '../common/common';
-import './phone-view.scss';
-
-const importDetailsPane = () => import('./details-pane/details-pane.js');
-const importInstructorPane = () => import('./instructor-resources-pane/instructor-resources-pane.js');
-const importStudentPane = () => import('./student-resources-pane/student-resources-pane.js');
-
-function TocPane({model}) {
- const tocHtml = useTableOfContents(model);
-
- return (
-
-
-
- );
-}
-
-function ErrataPane({model, polish}) {
- return (
-
-
-
- );
-}
-
-function items(model) {
- const polish = $.isPolish(model.title);
- const result = polish ?
- [
- {
- title: 'Szczegóły książki',
- contentComponent:
- }
- ] :
- [
- {
- title: 'Book details',
- contentComponent:
- },
- {
- title: 'Instructor resources',
- titleTag: 'updated',
- contentComponent:
- },
- {
- title: 'Student resources',
- openTitle: `Student resources (${model.bookStudentResources.length})`,
- contentComponent:
- }
- ];
-
- const includeTOC = ['live', 'new_edition_available'].includes(model.bookState);
-
- if (includeTOC) {
- result.splice(1, 0, {
- title: polish ? 'Spis treści' : 'Table of contents',
- contentComponent:
- });
- result.push({
- title: polish ? 'Zgłoś erratę' : 'Report errata',
- contentComponent:
- });
- }
-
- result.push({
- inline:
- });
-
- return result;
-}
-
-export default function PhoneView() {
- const model = useDetailsContext();
- const accordionItems = items(model);
- const selectedTab = findSelectedTab(accordionItems.map((i) => i.title));
-
- return (
-
- );
-}
diff --git a/src/app/pages/details/phone-view/phone-view.tsx b/src/app/pages/details/phone-view/phone-view.tsx
new file mode 100644
index 000000000..5dfbbf76a
--- /dev/null
+++ b/src/app/pages/details/phone-view/phone-view.tsx
@@ -0,0 +1,136 @@
+import React from 'react';
+import LetUsKnow from '../common/let-us-know/let-us-know';
+import GetThisTitle from '../common/get-this-title';
+import AccordionGroup from '~/components/accordion-group/accordion-group';
+import useDetailsContext, {type ContextValues} from '../context';
+import $ from '~/helpers/$';
+import JITLoad from '~/helpers/jit-load';
+import {findSelectedTab} from '../common/tab-utils';
+import RawHTML from '~/components/jsx-helpers/raw-html';
+import {useTableOfContents} from '../common/hooks';
+import {ErrataContents, GiveLink} from '../common/common';
+import './phone-view.scss';
+
+const importDetailsPane = () => import('./details-pane/details-pane');
+const importInstructorPane = () =>
+ import('./instructor-resources-pane/instructor-resources-pane');
+const importStudentPane = () =>
+ import('./student-resources-pane/student-resources-pane');
+
+function TocPane() {
+ const tocHtml = useTableOfContents();
+
+ return (
+
+
+
+ );
+}
+
+function ErrataPane() {
+ return (
+
+
+
+ );
+}
+
+type AccordionItem = {
+ title: string;
+ titleTag?: string;
+ openTitle?: string;
+ contentComponent: React.ReactNode;
+} | {
+ inline: React.ReactNode;
+};
+
+function items(model: ContextValues) {
+ const polish = $.isPolish(model.title);
+ const result: AccordionItem[] = polish
+ ? [
+ {
+ title: 'Szczegóły książki',
+ contentComponent: (
+
+ )
+ }
+ ]
+ : [
+ {
+ title: 'Book details',
+ contentComponent: (
+
+ )
+ },
+ {
+ title: 'Instructor resources',
+ titleTag: 'updated',
+ contentComponent: (
+
+ )
+ },
+ {
+ title: 'Student resources',
+ openTitle: `Student resources (${model.bookStudentResources.length})`,
+ contentComponent: (
+
+ )
+ }
+ ];
+
+ const includeTOC = ['live', 'new_edition_available'].includes(
+ model.bookState
+ );
+
+ if (includeTOC) {
+ result.splice(1, 0, {
+ title: polish ? 'Spis treści' : 'Table of contents',
+ contentComponent:
+ });
+ result.push({
+ title: polish ? 'Zgłoś erratę' : 'Report errata',
+ contentComponent:
+ });
+ }
+
+ result.push({
+ inline:
+ });
+
+ return result;
+}
+
+export default function PhoneView() {
+ const model = useDetailsContext();
+ const accordionItems = items(model);
+ const selectedTab = findSelectedTab(
+ accordionItems.filter((i) => 'title' in i)
+ .map((i) => i.title)
+ );
+
+ return (
+
+ );
+}
diff --git a/src/app/pages/details/phone-view/student-resources-pane/student-resources-pane.js b/src/app/pages/details/phone-view/student-resources-pane/student-resources-pane.js
deleted file mode 100644
index bda5f3c04..000000000
--- a/src/app/pages/details/phone-view/student-resources-pane/student-resources-pane.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import {studentResourceBoxPermissions} from '../../common/resource-box/resource-box-utils';
-import ResourceBoxes from '../../common/resource-box/resource-boxes';
-import useUserContext from '~/contexts/user';
-import './student-resources-pane.scss';
-
-function resourceBoxModel(resourceData, userStatus) {
- return Object.assign({
- heading: resourceData.resourceHeading,
- description: '',
- comingSoon: Boolean(resourceData.comingSoonText),
- comingSoonText: '',
- printLink: resourceData.printLink
- }, studentResourceBoxPermissions(resourceData, userStatus, 'Student resources'));
-}
-
-function StudentResourcesPane({model, userStatus}) {
- return (
-
- resourceBoxModel(res, userStatus))} />
-
- );
-}
-
-export default function LoadUserStatusThenStudentPane({model}) {
- const {userStatus} = useUserContext();
-
- if (!userStatus) {
- return null;
- }
- return (
-
- );
-}
diff --git a/src/app/pages/details/phone-view/student-resources-pane/student-resources-pane.tsx b/src/app/pages/details/phone-view/student-resources-pane/student-resources-pane.tsx
new file mode 100644
index 000000000..b1837b1d9
--- /dev/null
+++ b/src/app/pages/details/phone-view/student-resources-pane/student-resources-pane.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import {
+ studentResourceBoxPermissions,
+ type ResourceData
+} from '../../common/resource-box/resource-box-utils';
+import ResourceBoxes, {type ResourceModel} from '../../common/resource-box/resource-boxes';
+import useUserContext, {type UserStatus} from '~/contexts/user';
+import type {ContextValues} from '../../context';
+import './student-resources-pane.scss';
+
+function resourceBoxModel(
+ resourceData: ResourceData,
+ userStatus: UserStatus
+): ResourceModel {
+ return Object.assign(
+ {
+ heading: resourceData.resourceHeading,
+ description: '',
+ comingSoon: Boolean(resourceData.comingSoonText),
+ comingSoonText: '',
+ printLink: resourceData.printLink
+ },
+ studentResourceBoxPermissions(
+ resourceData,
+ userStatus
+ )
+ ) as ResourceModel;
+}
+
+function StudentResourcesPane({
+ model,
+ userStatus
+}: {
+ model: ContextValues;
+ userStatus: UserStatus;
+}) {
+ return (
+
+
+ resourceBoxModel(res, userStatus)
+ )}
+ />
+
+ );
+}
+
+export default function LoadUserStatusThenStudentPane({
+ model
+}: {
+ model: ContextValues;
+}) {
+ const {userStatus} = useUserContext();
+
+ return ;
+}
diff --git a/test/src/data/details-college-algebra.js b/test/src/data/details-college-algebra.js
index 8bfa44107..dbfbb8aa3 100644
--- a/test/src/data/details-college-algebra.js
+++ b/test/src/data/details-college-algebra.js
@@ -1313,7 +1313,19 @@ const details = {
},
"partner_list_label": "Technology Partners",
"partner_page_link_text": "See all partners",
- "videos": [],
+ "videos": [
+ {
+ "type": "video",
+ "value": [
+ {
+ "title": "Interface",
+ "description": "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.
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.
",
+ "embed": ""
+ }
+ ],
+ "id": "28d0ee63-04e0-4128-ba2b-9239b7da5e4e"
+ }
+ ],
"promote_image": null,
"last_updated_pdf": null,
"featured_resources_header": null
diff --git a/test/src/pages/details/details-tab.test.tsx b/test/src/pages/details/details-tab.test.tsx
new file mode 100644
index 000000000..54ab6a0f5
--- /dev/null
+++ b/test/src/pages/details/details-tab.test.tsx
@@ -0,0 +1,36 @@
+// details-tab is dynamically loaded, so needs to be tested separately
+import React from 'react';
+import {render, screen} from '@testing-library/preact';
+import DetailsTab from '~/pages/details/desktop-view/details-tab/details-tab';
+import {LanguageContextProvider} from '~/contexts/language';
+
+type Props = Parameters[0];
+
+jest.mock('~/pages/details/common/get-this-title');
+
+const model = {
+ id: 2,
+ title: 'model-title',
+ description: 'model-description',
+ adoptions: 69,
+ slug: 'whatever'
+} as Props['model'];
+
+function Component(props: Props) {
+ return (
+
+
+
+ );
+}
+
+describe('details-tab', () => {
+ it('renders (English)', () => {
+ render();
+ screen.getByRole('heading', {level: 3, name: 'Summary'});
+ });
+ it('renders (Polish)', () => {
+ render();
+ screen.getByRole('heading', {level: 3, name: 'Przejdź do książki'});
+ });
+});
diff --git a/test/src/pages/details/details.test.tsx b/test/src/pages/details/details.test.tsx
index 88b79b341..1267f4a81 100644
--- a/test/src/pages/details/details.test.tsx
+++ b/test/src/pages/details/details.test.tsx
@@ -8,6 +8,7 @@ import ShellContextProvider from '~/../../test/helpers/shell-context';
import * as DH from '~/helpers/use-document-head';
import $ from '~/helpers/$';
import * as WC from '~/contexts/window';
+import * as RBU from '~/pages/details/common/resource-box/resource-box-utils';
// Tamp down meaningless errors
jest.mock('~/models/rex-release', () =>
@@ -23,14 +24,15 @@ jest.mock('~/models/table-of-contents-html', () =>
jest.fn().mockReturnValue(Promise.resolve({}))
);
+jest.spyOn(window, 'scrollBy').mockImplementation(() => null);
jest.spyOn(DH, 'setPageTitleAndDescriptionFromBookData').mockReturnValue();
const spyIsPolish = jest.spyOn($, 'isPolish');
const spyWindowContext = jest.spyOn(WC, 'default');
-function Component() {
+function Component({path='/details/books/college-algebra'}) {
return (
-
+
{
+ const user = userEvent.setup();
+ const saveWarn = console.warn;
+
beforeEach(() => {
document.head.innerHTML = '';
const el = document.createElement('meta');
@@ -65,20 +70,64 @@ describe('Details page', () => {
el.setAttribute('name', 'description');
document.head.appendChild(el);
});
+ console.debug = jest.fn();
- it('renders book', async () => {
+ it('renders book with video data', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
spyWindowContext.mockReturnValue({innerWidth: 1280} as any);
render();
await finishedRendering();
expect(lengthOfView('phone')).toBeUndefined();
- expect(lengthOfView('bigger')).toBe(98);
+ expect(lengthOfView('bigger')).toBe(794);
const jsonLdScript = document.head.querySelector('script');
expect(jsonLdScript?.textContent).toEqual(
expect.stringContaining('mainEntity')
);
+ const tabs = screen.getAllByRole('tab');
+
+ expect(tabs).toHaveLength(4);
+ // These do not seem to update the tab state as expected, though they
+ // do exercise some code.
+ await user.click(tabs[1]);
+ });
+ it('renders with Student tab selected', async () => {
+ const mockLocation = jest.spyOn(window, 'location', 'get').mockReturnValue({
+ ...window.location,
+ search: '?Student%20resources'
+ });
+
+ render();
+ const tabs = await screen.findAllByRole('tab');
+
+ expect(tabs[2].getAttribute('aria-selected')).toBe('true');
+ await user.click(tabs[1]);
+ mockLocation.mockRestore();
+ });
+ it('renders with Instructor tab selected', async () => {
+ jest.spyOn(RBU, 'useResources').mockReturnValue({
+ bookVideoFacultyResources: [],
+ bookFacultyResources: []
+ });
+ const mockLocation = jest.spyOn(window, 'location', 'get').mockReturnValue({
+ ...window.location,
+ search: '?Instructor%20resources'
+ });
+
+ render();
+ await finishedRendering();
+ const tabs = screen.getAllByRole('tab');
+
+ expect(tabs[1].getAttribute('aria-selected')).toBe('true');
+ await user.click(tabs[2]);
+
+ screen.getByRole('heading', {name: 'Technology Partners'});
+ console.warn = jest.fn();
+ await user.click(screen.getByRole('link', {name: 'MagicBox E-Reader'}));
+ expect(console.warn).toHaveBeenCalled();
+ console.warn = saveWarn;
+ mockLocation.mockRestore();
});
it('renders Polish book', async () => {
spyIsPolish.mockReturnValue(true);
@@ -104,11 +153,46 @@ describe('Details page', () => {
);
});
it('renders only phone-view at phone width', async () => {
+ jest.spyOn(RBU, 'useResources').mockReturnValue({
+ bookVideoFacultyResources: [],
+ bookFacultyResources: [
+ {
+ featured: true,
+ linkText: 'Link text',
+ comingSoonText: '',
+ printLink: 'print-link',
+ videoReferenceNumber: 13
+ },
+ {
+ featured: false,
+ linkText: 'Link text2',
+ comingSoonText: '',
+ printLink: 'print-link2',
+ videoReferenceNumber: null,
+ resource: {
+ id: 1,
+ heading: 'resource-heading',
+ resourceCategory: 'any',
+ resourceUnlocked: true,
+ description: 'resource-description'
+ }
+ }
+ ]
+ });
spyWindowContext.mockReturnValue({innerWidth: 480} as any); // eslint-disable-line
render();
await finishedRendering();
expect(lengthOfView('phone')).toBe(346);
expect(lengthOfView('bigger')).toBeUndefined();
+
+ jest.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({
+ y: 100
+ } as any); // eslint-disable-line
+ await user.click(screen.getByRole('button', {name: 'Instructor resources updated'}));
+ console.warn = jest.fn();
+ await user.click(await screen.findByRole('link', {name: 'OpenStax Partners'}));
+ expect(console.warn).toHaveBeenCalled();
+ console.warn = saveWarn;
});
it('toggles authors at phone width', async () => {
spyWindowContext.mockReturnValue({innerWidth: 480} as any); // eslint-disable-line
@@ -116,7 +200,6 @@ describe('Details page', () => {
await finishedRendering();
const authorToggle = await screen.findByText('Authors');
const detailsEl = authorToggle.closest('details');
- const user = userEvent.setup();
await user.click(authorToggle);
expect(detailsEl?.open).toBe(true);
diff --git a/test/src/pages/details/left-content.test.tsx b/test/src/pages/details/left-content.test.tsx
index 57865e92b..383e6e89a 100644
--- a/test/src/pages/details/left-content.test.tsx
+++ b/test/src/pages/details/left-content.test.tsx
@@ -19,8 +19,11 @@ describe('left-content', () => {
comingSoon: false,
iconType: 'lock',
heading: 'heading',
- double: false
- } as unknown as ModelType; // incomplete, but it's enough for testing
+ double: false,
+ bookModel: {
+ id: 1
+ }
+ } as ModelType; // incomplete, but it's enough for testing
const link = {url: '#good-url', text: 'button-label'};
// Setup option prevents await click from hanging when using faketimers
const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime});