+            
                 
                     {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}
-                        
-                        
-                    
-                )
-            }
-        
+            {videos.map(({title, description, embed}) => (
+                
+                    
{title}
+                    
+                    
+                
+            ))}
+        
             
@@ -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});