From 023ce217d99490e5f1c54beddb5f50e065a02e1d Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Tue, 30 Sep 2025 15:01:40 -0500 Subject: [PATCH 01/10] fixed portal/adoption --- .../shell/router-helpers/page-routes.tsx | 28 +++++++++--------- .../router-helpers/portal-page-routes.js | 29 +++++++++++-------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/app/components/shell/router-helpers/page-routes.tsx b/src/app/components/shell/router-helpers/page-routes.tsx index bc00c1746..d578c760b 100644 --- a/src/app/components/shell/router-helpers/page-routes.tsx +++ b/src/app/components/shell/router-helpers/page-routes.tsx @@ -79,25 +79,27 @@ export function OtherPageRoutes() { } // Some pages have no page data in the CMS! - if ( - [ - 'adopters', - 'adoption', - 'blog', - 'campaign', - 'confirmation', - 'institutional-partnership-application', - 'interest', - 'renewal-form', - 'separatemap' - ].includes(dir) - ) { + if (isNoDataPage(dir)) { return ; } return ; } +export function isNoDataPage(dir: string) { + return [ + 'adopters', + 'adoption', + 'blog', + 'campaign', + 'confirmation', + 'institutional-partnership-application', + 'interest', + 'renewal-form', + 'separatemap' + ].includes(dir); +} + // There are a couple of pages whose names in the CMS don't match their osweb urls const mismatch: Record = { press: 'news', diff --git a/src/app/components/shell/router-helpers/portal-page-routes.js b/src/app/components/shell/router-helpers/portal-page-routes.js index 39d933e61..46edca0ca 100644 --- a/src/app/components/shell/router-helpers/portal-page-routes.js +++ b/src/app/components/shell/router-helpers/portal-page-routes.js @@ -11,7 +11,8 @@ import { usePageDataFromRoute, FlexPageUsingItsOwnLayout, NonFlexPageUsingDefaultLayout, - DetailsRoutes + DetailsRoutes, + isNoDataPage } from './page-routes'; import {assertDefined} from '~/helpers/data'; import {ImportedPage} from './page-loaders'; @@ -40,6 +41,13 @@ export function RouteAsPortalOrNot() { if (!other) { return ; } + if (isNoDataPage(name)) { + return ( + + + + ); + } return ( @@ -61,8 +69,8 @@ export function RouteAsPortalOrNot() { return ; } -// eslint-disable-next-line complexity -export function PortalSubRoute() { + +function PortalSubRoute() { const {name, data, hasError} = usePageDataFromRoute(); if (!data) { @@ -73,17 +81,14 @@ export function PortalSubRoute() { return ; } - const isFlex = !hasError && isFlexPage(data); - - if (isFlex) { + if (isFlexPage(data)) { return ; } - const isGeneral = Boolean(data?.body); - return isGeneral ? ( - - ) : ( - - ); + if (data?.body) { + return ; + } + + return ; } From 607b56d80f7282774e5cf7574c883b7810cac7c7 Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Wed, 1 Oct 2025 09:38:11 -0500 Subject: [PATCH 02/10] Fix coverage on vertical-list --- test/src/components/role-selector.test.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/src/components/role-selector.test.tsx b/test/src/components/role-selector.test.tsx index 7c4a4b1c6..5f5b4ae32 100644 --- a/test/src/components/role-selector.test.tsx +++ b/test/src/components/role-selector.test.tsx @@ -47,11 +47,20 @@ describe('role-selector', () => { expect(options[0].getAttribute('aria-selected')).toBe('false'); await user.keyboard('{Enter}'); expect(options[0].getAttribute('aria-selected')).toBe('true'); + listbox.focus(); await user.keyboard('{Escape}'); // Select by space + listbox.focus(); await user.keyboard('{ArrowDown} '); - const i = options.findIndex((o) => o.getAttribute('aria-selected') === 'true'); + const i = options.findIndex( + (o) => o.getAttribute('aria-selected') === 'true' + ); expect(i).toBe(1); + // Select by mouse + listbox.focus(); + // fireEvent.mouseEnter(options[1]); + await user.click(options[1]); + expect(options[1].getAttribute('aria-selected')).toBe('true'); }); }); From 65ab00a0ed2037fcb19e680b226981fa0285e8c2 Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Wed, 1 Oct 2025 13:49:51 -0500 Subject: [PATCH 03/10] Port portal-page-routes; code coverage for rex-portal --- package.json | 1 + ...-page-routes.js => portal-page-routes.tsx} | 7 +++--- src/app/helpers/rex-portal.ts | 2 ++ test/src/pages/details/common/hooks.test.tsx | 23 +++++++++++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) rename src/app/components/shell/router-helpers/{portal-page-routes.js => portal-page-routes.tsx} (93%) diff --git a/package.json b/package.json index 2895fc2ba..2d9e24ead 100644 --- a/package.json +++ b/package.json @@ -133,6 +133,7 @@ "setupFiles": [ "/test/setupFile.js" ], + "maxWorkers": "60%", "modulePathIgnorePatterns": [ "package.json" ], diff --git a/src/app/components/shell/router-helpers/portal-page-routes.js b/src/app/components/shell/router-helpers/portal-page-routes.tsx similarity index 93% rename from src/app/components/shell/router-helpers/portal-page-routes.js rename to src/app/components/shell/router-helpers/portal-page-routes.tsx index 46edca0ca..08f8719c0 100644 --- a/src/app/components/shell/router-helpers/portal-page-routes.js +++ b/src/app/components/shell/router-helpers/portal-page-routes.tsx @@ -31,7 +31,7 @@ export function RouteAsPortalOrNot() { } const isFlex = !hasError && isFlexPage(data); - const isPortal = isFlex && data.layout[0].type === 'landing'; + const isPortal = isFlex && data.layout[0]?.type === 'landing'; if (isPortal) { if (portalPrefix !== `/${name}`) { @@ -41,7 +41,7 @@ export function RouteAsPortalOrNot() { if (!other) { return ; } - if (isNoDataPage(name)) { + if (isNoDataPage(assertDefined(name))) { return ( @@ -69,7 +69,7 @@ export function RouteAsPortalOrNot() { return ; } - +// eslint-disable-next-line complexity function PortalSubRoute() { const {name, data, hasError} = usePageDataFromRoute(); @@ -91,4 +91,3 @@ function PortalSubRoute() { return ; } - diff --git a/src/app/helpers/rex-portal.ts b/src/app/helpers/rex-portal.ts index dd32b7683..8351a907d 100644 --- a/src/app/helpers/rex-portal.ts +++ b/src/app/helpers/rex-portal.ts @@ -1,5 +1,7 @@ import usePortalContext from '~/contexts/portal'; +// This is about making TOC links to Rex work with a portal there. +// Will probably need to be revisited. export function useRexPortalLinkOrNot(link: string) { const {portalPrefix} = usePortalContext(); diff --git a/test/src/pages/details/common/hooks.test.tsx b/test/src/pages/details/common/hooks.test.tsx index 95c925030..c5d87d8ff 100644 --- a/test/src/pages/details/common/hooks.test.tsx +++ b/test/src/pages/details/common/hooks.test.tsx @@ -5,6 +5,7 @@ import { usePartnerFeatures } from '~/pages/details/common/hooks'; import * as UDC from '~/pages/details/context'; +import * as PC from '~/contexts/portal'; const mockTocHtml = jest.fn(); @@ -49,6 +50,28 @@ describe('details/common/hooks', () => { render(); expect(document.body.textContent).toBe(''); }); + it('(useTableOfContents) is ok in a portal', async () => { + // This is about making TOC links to Rex work with a portal there. + // Will probably need to be revisited. + const setPortal = jest.fn(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + jest.spyOn(UDC, 'default').mockReturnValue({webviewRexLink: ''} as any); + jest.spyOn(PC, 'default').mockReturnValue({ + portalPrefix: '/landing-page', + setPortal, + rewriteLinks: jest.fn() + }); + + function Component() { + const tocHtml = useTableOfContents(); + + return
; + } + + render(); + expect(document.body.textContent).toBe(''); + }); it('(usePartnerFeatures) sets its values', async () => { function Component() { const [blurbs, includePartners] = usePartnerFeatures('Economics'); From c3a932ad2883b171999df26b1959626074010e24 Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Wed, 1 Oct 2025 15:02:20 -0500 Subject: [PATCH 04/10] Coverage for contexts/portal --- test/src/layouts/layouts.test.tsx | 114 +++++++++++++++++------------- 1 file changed, 66 insertions(+), 48 deletions(-) diff --git a/test/src/layouts/layouts.test.tsx b/test/src/layouts/layouts.test.tsx index 4936ee2aa..42301cd19 100644 --- a/test/src/layouts/layouts.test.tsx +++ b/test/src/layouts/layouts.test.tsx @@ -1,24 +1,23 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; -import {describe, it} from '@jest/globals'; -import { MemoryRouter } from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import LandingLayout from '~/layouts/landing/landing'; - -// @ts-expect-error does not exist on -const {routerFuture} = global; +import usePortalContext, {PortalContextProvider} from '~/contexts/portal'; +import '@testing-library/jest-dom'; type Data = Parameters[0]['data']; type Layout = Exclude['layout']; describe('layouts/landing', () => { - function Component({layout}: {layout: Layout}) { - const data: Parameters[0]['data'] = { + function dataForLayout(layout: Layout) { + return { title: 'the-title', layout }; - + } + function Component({data}: Parameters[0]) { return ( - +
child contents
@@ -27,47 +26,47 @@ describe('layouts/landing', () => { } it('renders without data object', () => { - render( - -
child contents
-
-
); + render(); expect(screen.getAllByRole('img')).toHaveLength(2); expect(screen.getAllByRole('link')).toHaveLength(1); }); it('renders without layout values', () => { - render(); + render(); expect(screen.getAllByRole('img')).toHaveLength(2); expect(screen.getAllByRole('link')).toHaveLength(1); }); it('suppresses the Give link when specified', () => { - const layout = [{ - value: { - navLinks: [], - showGiveNowButton: false + const layout = [ + { + value: { + navLinks: [], + showGiveNowButton: false + } } - }]; + ]; - render(); + render(); expect(screen.getAllByRole('img')).toHaveLength(2); expect(screen.queryAllByRole('link')).toHaveLength(0); }); it('renders with layout values', () => { - const layout = [{ - value: { - navLinks: [ - { - text: 'link-name', - target: { - type: 'link-type', - value: 'link-value' + const layout = [ + { + value: { + navLinks: [ + { + text: 'link-name', + target: { + type: 'link-type', + value: 'link-value' + } } - } - ] + ] + } } - }]; + ]; - render(); + render(); expect(screen.getAllByRole('link')).toHaveLength(2); }); it('renders the default footer for non-flex pages', async () => { @@ -77,13 +76,7 @@ describe('layouts/landing', () => { const meta = {type}; const data = {title, layout, meta} as const; - render( - - -
child contents
-
-
- ); + render(); // Find social links by title expect(await screen.findAllByTitle(/^OpenStax on .+$/)).toHaveLength(5); @@ -98,18 +91,43 @@ describe('layouts/landing', () => { const data = {title, layout, meta} as const; render( - - -
child contents
-
-
+ + + ); // Flex footer does not have social links - await expect(screen.findAllByTitle(/^OpenStax on .+$/)) - .rejects - .toThrow(/Unable to find an element/); + await expect(screen.findAllByTitle(/^OpenStax on .+$/)).rejects.toThrow( + /Unable to find an element/ + ); // Default footer has 4 links + 1 link in layout = 5 links expect(await screen.findAllByRole('link')).toHaveLength(5); }); + it('rewrites footer links in portal', async () => { + const title = 'some-title'; + const layout = [{value: {navLinks: []}}]; + const type = 'pages.FlexPage'; + const meta = {type}; + const ComponentInPortal = ({data}: Parameters[0]) => { + const {setPortal} = usePortalContext(); + + setPortal('/a-portal'); + return ; + }; + + render( + + + + ); + + // Flex footer does not have social links + await expect(screen.findAllByTitle(/^OpenStax on .+$/)).rejects.toThrow( + /Unable to find an element/ + ); + // Default footer has 4 links + 1 link in layout = 5 links + const links = await screen.findAllByRole('link'); + + expect(links[1]).toHaveAttribute('href', '//a-portal/tos'); + }); }); From 7439359d8df2484114a0faf26c5fc5065c3d4a6f Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Mon, 29 Sep 2025 14:21:21 -0500 Subject: [PATCH 05/10] test coverage for page-routes and portal-page-routes --- .../shell/router-helpers/page-routes.tsx | 1 - .../router-helpers/portal-page-routes.tsx | 14 +- test/src/components/shell.test.tsx | 257 +++++++++--------- test/src/pages/interest.test.tsx | 1 - 4 files changed, 137 insertions(+), 136 deletions(-) diff --git a/src/app/components/shell/router-helpers/page-routes.tsx b/src/app/components/shell/router-helpers/page-routes.tsx index d578c760b..e5c080395 100644 --- a/src/app/components/shell/router-helpers/page-routes.tsx +++ b/src/app/components/shell/router-helpers/page-routes.tsx @@ -54,7 +54,6 @@ export function DetailsRoutes() { ); } -// eslint-disable-next-line complexity export function OtherPageRoutes() { const dir = assertDefined(useParams().dir); const {'*': path} = useParams(); diff --git a/src/app/components/shell/router-helpers/portal-page-routes.tsx b/src/app/components/shell/router-helpers/portal-page-routes.tsx index 08f8719c0..bc1520f64 100644 --- a/src/app/components/shell/router-helpers/portal-page-routes.tsx +++ b/src/app/components/shell/router-helpers/portal-page-routes.tsx @@ -38,16 +38,19 @@ export function RouteAsPortalOrNot() { setPortal(assertDefined(name)); return null; } + if (!other) { return ; } - if (isNoDataPage(assertDefined(name))) { + + if (isNoDataPage(other)) { return ( - + ); } + return ( @@ -69,20 +72,15 @@ export function RouteAsPortalOrNot() { return ; } -// eslint-disable-next-line complexity function PortalSubRoute() { const {name, data, hasError} = usePageDataFromRoute(); - if (!data) { - return null; - } - if (hasError) { return ; } if (isFlexPage(data)) { - return ; + return ; } if (data?.body) { diff --git a/test/src/components/shell.test.tsx b/test/src/components/shell.test.tsx index f69822166..1c67ab31a 100644 --- a/test/src/components/shell.test.tsx +++ b/test/src/components/shell.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {render, screen, waitFor} from '@testing-library/preact'; import userEvent from '@testing-library/user-event'; +import '@testing-library/jest-dom'; import AppElement from '~/components/shell/shell'; import * as RRD from 'react-router-dom'; import MR from '~/../../test/helpers/future-memory-router'; @@ -18,6 +19,7 @@ import * as UC from '~/contexts/user'; import {camelCaseKeys, transformData} from '~/helpers/page-data-utils'; import landingPage from '../data/landing-page'; import contactPage from '../data/contact-page'; +import formHeadings from '../data/form-headings'; import homePage from '../data/home-page'; import subjectPage from '../data/new-subjects'; import flexPage from '../data/flex-page'; @@ -26,9 +28,17 @@ import ChildrenContainer from '~/../../test/helpers/mock-children-container'; const {useLocation} = RRD; const BrowserRouter = jest.spyOn(RRD, 'BrowserRouter').mockImplementation(({children}) => ( - {children} +
{children}
)); +function mockBrowserInitialEntries(entries: string[]) { + BrowserRouter.mockImplementationOnce(({children}) => ( + + {children} + + )); +} + jest.mock('react-modal', () => ({ setAppElement: jest.fn() })); @@ -42,13 +52,24 @@ describe('shell', () => { return
-{loc.pathname}-
; } + function mockBrowserInitialEntriesWithLocation(entries: string[]) { + BrowserRouter.mockImplementationOnce(({children}) => ( + + {children} + + + )); + } + // eslint-disable-next-line complexity const spyUpd = jest.spyOn(UPD, 'default').mockImplementation((path) => { switch (path) { + case 'pages/form-headings?locale=en': + return camelCaseKeys(formHeadings); case 'pages/landing-page': return camelCaseKeys(landingPage); case 'pages/flex-page': - // @ts-expect-error some format thing + // @ts-expect-error flexPage type return camelCaseKeys(flexPage); case 'pages/contact': return camelCaseKeys(transformData(contactPage)); @@ -75,6 +96,7 @@ describe('shell', () => { }); const setPortal = jest.fn(); const spyGP = jest.spyOn(GP, 'GeneralPageFromSlug'); + const saveWarn = console.warn; function setPortalPrefix(portalPrefix: string) { jest.spyOn(PC, 'default').mockReturnValue({ @@ -92,6 +114,7 @@ describe('shell', () => { jest.spyOn(WC, 'default').mockReturnValue(null); jest.spyOn(TD, 'default').mockReturnValue(null); jest.spyOn(LSN, 'default').mockReturnValue(null); + jest.spyOn(DH, 'default').mockReturnValue(undefined); jest.spyOn(DH, 'setPageDescription').mockReturnValue(undefined); jest.spyOn(DH, 'setPageTitleAndDescriptionFromBookData').mockReturnValue(undefined); jest.spyOn(UC, 'UserContextProvider').mockImplementation(ChildrenContainer); @@ -103,9 +126,7 @@ describe('shell', () => { }); it('Delivers embedded contact page', async () => { - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/embedded/contact']); spyUpd.mockReturnValueOnce(null); render(AppElement); @@ -120,10 +141,7 @@ describe('shell', () => { const piTracker = jest.fn(); w.piTracker = (path: string) => piTracker(path); - - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/']); render(AppElement); @@ -132,9 +150,7 @@ describe('shell', () => { }); it('(skip to main content link) works', async () => { window.scrollBy = jest.fn(); - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/']); render(AppElement); const skipLink = await screen.findByRole('link', {name: 'skip to main content'}); @@ -142,174 +158,163 @@ describe('shell', () => { await waitFor(() => expect(window.scrollBy).toHaveBeenCalled()); }); it('routes "home/anything" to top-level', async () => { - BrowserRouter.mockImplementationOnce(({children}) => ( - - {children} - - - )); + mockBrowserInitialEntriesWithLocation(['/home/anything']); render(AppElement); await screen.findByText('-/-'); }); it('routes "general/anything" to "/anything"', async () => { - BrowserRouter.mockImplementationOnce(({children}) => ( - - {children} - - - )); + mockBrowserInitialEntriesWithLocation(['/general/anything']); render(AppElement); await screen.findByText('-/anything-'); }); it('routes adoption (no CMS page data) page', async () => { - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/adoption']); + + render(AppElement); + await screen.findByRole('combobox'); + await screen.findByText('Let us know you\'re using OpenStax'); + }); + it('sets portal before routing to page in a portal', async () => { + setPortalPrefix(''); + mockBrowserInitialEntries(['/landing-page/adoption']); + render(AppElement); + + await waitFor(() => expect(setPortal).toHaveBeenCalledWith('landing-page')); + }); + + it('routes adoption (no CMS page data) page when in portal', async () => { + console.warn = jest.fn(); + setPortalPrefix('/landing-page'); + mockBrowserInitialEntries(['/landing-page/adoption']); render(AppElement); - await screen.findByText('Adoption Form', {exact: false}); - screen.getByRole('combobox'); + + await screen.findByRole('combobox'); + await screen.findByText('Let us know you\'re using OpenStax'); + await waitFor(() => expect(console.warn).toHaveBeenCalled()); + console.warn = saveWarn; }); it('routes "errata" paths', async () => { - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/errata']); render(AppElement); await screen.findByText('No book or errata ID selected'); }); it('routes "details" paths (top level routes to Subjects)', async () => { - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/details']); render(AppElement); await screen.findByRole('heading', {level: 1, name: 'Browse our subjects'}); }); - it('routes "books" to details', async () => { - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + it('routes "books/*" to details', async () => { + mockBrowserInitialEntries(['/books/some-book']); + + render(AppElement); + await waitFor(() => expect(document.querySelector('main.details-page')).toBeTruthy); + }); + it('routes "books" to 404', async () => { + mockBrowserInitialEntries(['/books']); render(AppElement); await waitFor(() => expect(document.querySelector('main.details-page')).toBeTruthy); }); it('returns 404 for unknown path', async () => { - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/invalid']); render(AppElement); await screen.findByText('Uh-oh', {exact: false}); }); + it('routes general page to fetch spike/slug', async () => { + mockBrowserInitialEntries(['/general-page']); + render(AppElement); + await screen.findByText('Loaded page ""'); + }); it('sets portal when handling a portal page', async () => { setPortalPrefix(''); - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/landing-page']); render(AppElement); await waitFor(() => expect(setPortal).toHaveBeenCalledWith('landing-page')); }); - it('routes books routes to portal/books', async () => { - setPortalPrefix('portal'); - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); - render(AppElement); - // Nothing can really be checked; code coverage in rex-portal - }); it('renders nothing when data is null', async () => { setPortalPrefix('/landing-page'); spyUpd.mockReturnValueOnce(null); - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/landing-page']); render(AppElement); await expect(screen.findByRole('link', {name: 'K12 resources'})).rejects.toThrow(); }); - it('renders as a portal route', async () => { + it('renders page within a portal route', async () => { setPortalPrefix('/'); - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + mockBrowserInitialEntries(['/landing-page/contact']); render(AppElement); await waitFor(() => expect(setPortal).toHaveBeenCalledWith('landing-page')); }); - it('routes general page properly', async () => { - setPortalPrefix(''); - BrowserRouter.mockImplementationOnce(({children}) => ( - {children} - )); + // -- Warnings are generated from failed reads + it('renders as a portal route with nothing beyond the portal', async () => { + console.warn = jest.fn(); + setPortalPrefix('/landing-page'); + mockBrowserInitialEntries(['/landing-page/']); + render(AppElement); + await screen.findByText('Loaded page ""'); + await waitFor(() => expect(console.warn).toHaveBeenCalled()); + console.warn = saveWarn; + }); + it('renders page within a portal route', async () => { + setPortalPrefix('/'); + + mockBrowserInitialEntries(['/landing-page/contact']); + render(AppElement); + await waitFor(() => expect(setPortal).toHaveBeenCalledWith('landing-page')); + }); + it('renders ordinary page through portal', async () => { + console.warn = jest.fn(); + setPortalPrefix('/landing-page'); + + mockBrowserInitialEntries(['/landing-page/contact']); + render(AppElement); + expect(await screen.findByText('What is your question about?')).toBeInTheDocument(); + await waitFor(() => expect(console.warn).toHaveBeenCalled()); + console.warn = saveWarn; + }); + it('returns 404 for unknown portal path', async () => { + console.warn = jest.fn(); + setPortalPrefix('/landing-page'); + + mockBrowserInitialEntries(['/landing-page/invalid']); + render(AppElement); + await screen.findByText('Uh-oh, no page here'); + await waitFor(() => expect(console.warn).toHaveBeenCalled()); + console.warn = saveWarn; + }); + it('loads flex page within a portal route', async () => { + console.warn = jest.fn(); + setPortalPrefix('/landing-page'); + mockBrowserInitialEntries(['/landing-page/flex-page']); + render(AppElement); + await screen.findByRole('heading', {level: 2, name: 'Apply today to be an OpenStax Partner'}); + await waitFor(() => expect(console.warn).toHaveBeenCalled()); + console.warn = saveWarn; + }); + it('reroutes flex pages with extra path components to the page', async () => { + setPortalPrefix(''); + mockBrowserInitialEntries(['/flex-page/extra/junk']); + render(AppElement); + await screen.findByRole('heading', {level: 2, name: 'Apply today to be an OpenStax Partner'}); + }); + it('loads general page within a portal route', async () => { + console.warn = jest.fn(); + setPortalPrefix('/landing-page'); + mockBrowserInitialEntries(['/landing-page/general-page']); render(AppElement); await waitFor(() => expect(spyGP).toHaveBeenCalled()); spyGP.mockClear(); + await waitFor(() => expect(console.warn).toHaveBeenCalled()); + console.warn = saveWarn; }); - /* Below: tests that fail in strange ways */ - // it('renders as FlexPage when portal is properly set', async () => { - // setPortalPrefix('/landing-page'); - - // BrowserRouter.mockImplementationOnce(({children}) => ( - // {children} - // )); - // render(AppElement); - - // await screen.findByRole('link', {name: 'K12 resources'}); - // }); - // it('renders nothing when portal route data is null', async () => { - // setPortalPrefix('/landing-page'); - - // BrowserRouter.mockImplementationOnce(({children}) => ( - // {children} - // )); - // render(AppElement); - // await expect(screen.findByRole('form')).rejects.toThrow(); - // }); - // it('returns 404 for unknown portal path', async () => { - // BrowserRouter.mockImplementationOnce(({children}) => ( - // {children} - // )); - - // render(AppElement); - // await screen.findByText('Uh-oh', {exact: false}); - // }); - // it('loads flex pages that are not landing pages', async () => { - // setPortalPrefix(''); - // BrowserRouter.mockImplementationOnce(({children}) => ( - // {children} - // )); - // render(AppElement); - // await screen.findByRole('heading', {level: 2, name: 'Apply today to be an OpenStax Partner'}); - // }); - // it('loads flex page within a portal route', async () => { - // setPortalPrefix('/landing-page'); - // BrowserRouter.mockImplementationOnce(({children}) => ( - // {children} - // )); - // render(AppElement); - // await screen.findByRole('heading', {level: 2, name: 'Apply today to be an OpenStax Partner'}); - // }); - // it('reroutes flex pages with extra path components to the page', async () => { - // setPortalPrefix(''); - // BrowserRouter.mockImplementationOnce(({children}) => ( - // {children} - // )); - // render(AppElement); - // await screen.findByRole('heading', {level: 2, name: 'Apply today to be an OpenStax Partner'}); - // }); - // it('loads general page within a portal route', async () => { - // setPortalPrefix('/landing-page'); - // BrowserRouter.mockImplementationOnce(({children}) => ( - // {children} - // )); - // render(AppElement); - // await waitFor(() => expect(spyGP).toHaveBeenCalled()); - // spyGP.mockClear(); - // }); }); diff --git a/test/src/pages/interest.test.tsx b/test/src/pages/interest.test.tsx index e23473939..da1378112 100644 --- a/test/src/pages/interest.test.tsx +++ b/test/src/pages/interest.test.tsx @@ -36,7 +36,6 @@ describe('interest form', () => { render(); const roleSelector = await screen.findByRole('combobox'); - console.info('*** RS:', roleSelector.outerHTML); await user.click(roleSelector); const options = screen.getAllByRole('option'); From 0b56cd3998c0881c2c57b661fa464b31da2a5548 Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Thu, 2 Oct 2025 17:29:57 -0500 Subject: [PATCH 06/10] Quiet Memory Router and a couple lint issues --- test/src/components/book-selector.test.tsx | 2 +- test/src/components/loader-page.test.tsx | 2 +- test/src/components/search-bar.test.tsx | 2 +- test/src/components/shell/microsurvey-popup.test.tsx | 2 +- test/src/components/shell/use-link-handler.test.tsx | 3 ++- test/src/contexts/layout.test.tsx | 2 +- test/src/helpers/use-document-head.test.tsx | 2 +- test/src/pages/about.test.tsx | 2 +- test/src/pages/adopters.test.tsx | 2 +- test/src/pages/blog/gated-content-dialog.test.tsx | 2 +- test/src/pages/blog/search-results.test.tsx | 4 ++-- test/src/pages/confirmation.test.tsx | 5 ++++- test/src/pages/details/details.test.tsx | 3 ++- .../pages/details/get-this-title/give-before-other.test.tsx | 2 +- .../pages/details/get-this-title/give-before-pdf.test.tsx | 2 +- test/src/pages/details/left-content.test.tsx | 2 +- test/src/pages/details/resource-boxes.test.tsx | 2 +- test/src/pages/footer-page.test.tsx | 2 +- test/src/pages/partners/partner-details.test.tsx | 2 ++ test/src/pages/renewal-form.test.tsx | 4 ++-- test/src/pages/subjects/book-viewer.test.tsx | 2 +- test/src/pages/subjects/navigator.test.tsx | 2 +- test/src/pages/webinars/explore-page.test.tsx | 3 ++- test/src/pages/webinars/view-webinars-page.test.tsx | 2 +- test/src/pages/webinars/webinar-context.test.tsx | 2 +- test/src/pages/webinars/webinars.test.tsx | 2 +- test/test-utils.js | 2 +- 27 files changed, 36 insertions(+), 28 deletions(-) diff --git a/test/src/components/book-selector.test.tsx b/test/src/components/book-selector.test.tsx index 2c7e1f5cc..09cb6f156 100644 --- a/test/src/components/book-selector.test.tsx +++ b/test/src/components/book-selector.test.tsx @@ -5,7 +5,7 @@ import BookSelector, { useSelectedBooks } from '~/components/book-selector/book-selector'; import {useAfterSubmit} from '~/components/book-selector/after-form-submit'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import {describe, it, expect} from '@jest/globals'; import {LanguageContextProvider} from '~/contexts/language'; diff --git a/test/src/components/loader-page.test.tsx b/test/src/components/loader-page.test.tsx index 0cfc5d088..946b7e467 100644 --- a/test/src/components/loader-page.test.tsx +++ b/test/src/components/loader-page.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; import { LoadedPage } from '~/components/jsx-helpers/loader-page'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; describe('loader-page', () => { // The rest of the code is exercised in other tests. diff --git a/test/src/components/search-bar.test.tsx b/test/src/components/search-bar.test.tsx index 7af2d8a0d..ecff6fdaa 100644 --- a/test/src/components/search-bar.test.tsx +++ b/test/src/components/search-bar.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {describe, it, expect} from '@jest/globals'; import {render, screen, fireEvent} from '@testing-library/preact'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import SearchBar from '~/components/search-bar/search-bar'; describe('search-bar', () => { diff --git a/test/src/components/shell/microsurvey-popup.test.tsx b/test/src/components/shell/microsurvey-popup.test.tsx index 8c39ce264..38e8804f8 100644 --- a/test/src/components/shell/microsurvey-popup.test.tsx +++ b/test/src/components/shell/microsurvey-popup.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {describe, expect, it} from '@jest/globals'; import {render} from '@testing-library/preact'; import ShellContextProvider from '../../../helpers/shell-context'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import MicroSurvey from '~/layouts/default/microsurvey-popup/microsurvey-popup'; import useMSQueue from '~/layouts/default/microsurvey-popup/queue'; import useSharedDataContext from '~/contexts/shared-data'; diff --git a/test/src/components/shell/use-link-handler.test.tsx b/test/src/components/shell/use-link-handler.test.tsx index 88c201392..2b7c22f7a 100644 --- a/test/src/components/shell/use-link-handler.test.tsx +++ b/test/src/components/shell/use-link-handler.test.tsx @@ -6,7 +6,8 @@ import useLinkHandler, { TrackedMouseEvent } from '~/components/shell/router-helpers/use-link-handler'; import linkHelper from '~/helpers/link'; -import {useNavigate, MemoryRouter} from 'react-router-dom'; +import {useNavigate} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), diff --git a/test/src/contexts/layout.test.tsx b/test/src/contexts/layout.test.tsx index 3248dcd33..c11f339f7 100644 --- a/test/src/contexts/layout.test.tsx +++ b/test/src/contexts/layout.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; import useLayoutContext, { LayoutContextProvider } from '~/contexts/layout'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; function Component() { const {Layout, setLayoutParameters} = useLayoutContext(); diff --git a/test/src/helpers/use-document-head.test.tsx b/test/src/helpers/use-document-head.test.tsx index b3937a0cf..f805877c8 100644 --- a/test/src/helpers/use-document-head.test.tsx +++ b/test/src/helpers/use-document-head.test.tsx @@ -6,7 +6,7 @@ import useDocumentHead, { getPageDescription, useCanonicalLink } from '~/helpers/use-document-head'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; describe('use-document-head', () => { console.warn = jest.fn(); diff --git a/test/src/pages/about.test.tsx b/test/src/pages/about.test.tsx index adba6ac35..a9d28372f 100644 --- a/test/src/pages/about.test.tsx +++ b/test/src/pages/about.test.tsx @@ -3,7 +3,7 @@ import {render, screen} from '@testing-library/preact'; import {describe, it} from '@jest/globals'; import AboutPage from '~/pages/about/about'; import aboutData from '../../src/data/about'; -import { MemoryRouter } from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; const anImage = { id: 482, diff --git a/test/src/pages/adopters.test.tsx b/test/src/pages/adopters.test.tsx index a7a8faa24..9df4525f1 100644 --- a/test/src/pages/adopters.test.tsx +++ b/test/src/pages/adopters.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; import AdoptersPage from '~/pages/adopters/adopters'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; const pageData = [ { diff --git a/test/src/pages/blog/gated-content-dialog.test.tsx b/test/src/pages/blog/gated-content-dialog.test.tsx index 5aefe0661..c98f487e4 100644 --- a/test/src/pages/blog/gated-content-dialog.test.tsx +++ b/test/src/pages/blog/gated-content-dialog.test.tsx @@ -4,7 +4,7 @@ import {describe, it, expect} from '@jest/globals'; import ShellContextProvider from '~/../../test/helpers/shell-context'; import {MainClassContextProvider} from '~/contexts/main-class'; import {BlogContextProvider} from '~/pages/blog/blog-context'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import GatedContentDialog from '~/pages/blog/gated-content-dialog/gated-content-dialog'; import type {ArticleData} from '~/pages/blog/article/article'; import userEvent from '@testing-library/user-event'; diff --git a/test/src/pages/blog/search-results.test.tsx b/test/src/pages/blog/search-results.test.tsx index 6763ceaad..a58854eee 100644 --- a/test/src/pages/blog/search-results.test.tsx +++ b/test/src/pages/blog/search-results.test.tsx @@ -3,7 +3,7 @@ import {describe, expect, it} from '@jest/globals'; import {render, screen, waitFor} from '@testing-library/preact'; import PinnedArticle from '~/pages/blog/pinned-article/pinned-article'; import {LatestBlurbs} from '~/pages/blog/more-stories/more-stories'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import SearchResults from '~/pages/blog/search-results/search-results'; import * as PDU from '~/helpers/page-data-utils'; import * as AS from '~/pages/blog/article-summary/article-summary'; @@ -31,7 +31,7 @@ describe('search-results', () => { jest.clearAllMocks(); }); it('renders with paginator context when there are articles', async () => { - /* eslint-disable camelcase, max-len */ + /* eslint-disable camelcase */ jest.spyOn(PDU, 'fetchFromCMS').mockResolvedValueOnce( [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((id) => ({ id, diff --git a/test/src/pages/confirmation.test.tsx b/test/src/pages/confirmation.test.tsx index c18b86f2c..b0944bd87 100644 --- a/test/src/pages/confirmation.test.tsx +++ b/test/src/pages/confirmation.test.tsx @@ -1,7 +1,10 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; import Confirmation from '~/pages/confirmation/confirmation'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; +import * as DH from '~/helpers/use-document-head'; + +jest.spyOn(DH, 'default').mockReturnValue(undefined); describe('confirmation', () => { it('does a contact thank you', () => { diff --git a/test/src/pages/details/details.test.tsx b/test/src/pages/details/details.test.tsx index 6c789b5f7..88b79b341 100644 --- a/test/src/pages/details/details.test.tsx +++ b/test/src/pages/details/details.test.tsx @@ -2,7 +2,8 @@ import React from 'react'; import {render, screen, waitFor} from '@testing-library/preact'; import userEvent from '@testing-library/user-event'; import BookDetailsLoader from '~/pages/details/details'; -import {MemoryRouter, Routes, Route} from 'react-router-dom'; +import {Routes, Route} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import ShellContextProvider from '~/../../test/helpers/shell-context'; import * as DH from '~/helpers/use-document-head'; import $ from '~/helpers/$'; diff --git a/test/src/pages/details/get-this-title/give-before-other.test.tsx b/test/src/pages/details/get-this-title/give-before-other.test.tsx index 83dca4561..8c95518af 100644 --- a/test/src/pages/details/get-this-title/give-before-other.test.tsx +++ b/test/src/pages/details/get-this-title/give-before-other.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; import userEvent from '@testing-library/user-event'; import GiveBeforeOther from '~/pages/details/common/get-this-title-files/give-before-pdf/give-before-other'; -import { MemoryRouter } from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import {LanguageContextProvider} from '~/contexts/language'; import * as TY from '~/pages/details/common/get-this-title-files/give-before-pdf/thank-you-form'; diff --git a/test/src/pages/details/get-this-title/give-before-pdf.test.tsx b/test/src/pages/details/get-this-title/give-before-pdf.test.tsx index e80a34d07..fc21dbb88 100644 --- a/test/src/pages/details/get-this-title/give-before-pdf.test.tsx +++ b/test/src/pages/details/get-this-title/give-before-pdf.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {fireEvent, render, screen} from '@testing-library/preact'; import userEvent from '@testing-library/user-event'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import {LanguageContextProvider} from '~/contexts/language'; import GiveBeforePdf from '~/pages/details/common/get-this-title-files/give-before-pdf/give-before-pdf'; // eslint-disable-next-line max-len diff --git a/test/src/pages/details/left-content.test.tsx b/test/src/pages/details/left-content.test.tsx index e0bd836b2..57865e92b 100644 --- a/test/src/pages/details/left-content.test.tsx +++ b/test/src/pages/details/left-content.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; import userEvent from '@testing-library/user-event'; import {LanguageContextProvider} from '~/contexts/language'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import LeftContent from '~/pages/details/common/resource-box/left-content'; const mockUseUserContext = jest.fn(); diff --git a/test/src/pages/details/resource-boxes.test.tsx b/test/src/pages/details/resource-boxes.test.tsx index 827656b45..5721940cf 100644 --- a/test/src/pages/details/resource-boxes.test.tsx +++ b/test/src/pages/details/resource-boxes.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; import ShellContextProvider from '../../../helpers/shell-context'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import * as detailCtx from '~/pages/details/context'; import ResourceBoxes, {ResourceModel} from '~/pages/details/common/resource-box/resource-boxes'; diff --git a/test/src/pages/footer-page.test.tsx b/test/src/pages/footer-page.test.tsx index 82e92aec7..3a913958b 100644 --- a/test/src/pages/footer-page.test.tsx +++ b/test/src/pages/footer-page.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import * as UPD from '~/helpers/use-page-data'; import FooterPage from '~/pages/footer-page/footer-page'; diff --git a/test/src/pages/partners/partner-details.test.tsx b/test/src/pages/partners/partner-details.test.tsx index 1ba4c1b48..b2aac9949 100644 --- a/test/src/pages/partners/partner-details.test.tsx +++ b/test/src/pages/partners/partner-details.test.tsx @@ -21,6 +21,8 @@ describe('partner-details', () => { infoLinkText: 'info text' }; + console.warn = jest.fn(); + it('shows no synopsis if there is no partnerName', () => { const data = {...partnerData, ...linkTexts, title: ''}; // title is partnerName const setTitle = jest.fn(); diff --git a/test/src/pages/renewal-form.test.tsx b/test/src/pages/renewal-form.test.tsx index 6e6eda77e..e0f690801 100644 --- a/test/src/pages/renewal-form.test.tsx +++ b/test/src/pages/renewal-form.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {fireEvent, render, screen} from '@testing-library/preact'; import userEvent from '@testing-library/user-event'; import ShellContextProvider from '../../helpers/shell-context'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import RenewalForm from '~/pages/renewal-form/renewal-form'; import * as UUC from '~/contexts/user'; import * as UA from '~/models/renewals'; @@ -13,7 +13,7 @@ import * as SFBC from '~/components/multiselect/book-tags/sf-book-context'; // @ts-expect-error does not exist on const {routerFuture} = global; -/* eslint-enable camelcase */ + const mockNavigate = jest.fn(); // option necessary when using fake timers const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime}); diff --git a/test/src/pages/subjects/book-viewer.test.tsx b/test/src/pages/subjects/book-viewer.test.tsx index 00c116dee..060df5648 100644 --- a/test/src/pages/subjects/book-viewer.test.tsx +++ b/test/src/pages/subjects/book-viewer.test.tsx @@ -5,7 +5,7 @@ import BookViewer from '~/pages/subjects/new/specific/book-viewer'; import ShellContextProvider from '~/../../test/helpers/shell-context'; import {SpecificSubjectContextProvider} from '~/pages/subjects/new/specific/context'; import businessBooksData from '~/../../test/src/data/business-books'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; const mockUsePageData = jest.fn(); diff --git a/test/src/pages/subjects/navigator.test.tsx b/test/src/pages/subjects/navigator.test.tsx index 734716659..abdd634f1 100644 --- a/test/src/pages/subjects/navigator.test.tsx +++ b/test/src/pages/subjects/navigator.test.tsx @@ -5,7 +5,7 @@ import Navigator from '~/pages/subjects/new/specific/navigator'; import ShellContextProvider from '~/../../test/helpers/shell-context'; import {SpecificSubjectContextProvider} from '~/pages/subjects/new/specific/context'; import businessBooksData from '~/../../test/src/data/business-books'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import {NavigatorContextProvider} from '~/pages/subjects/new/specific/navigator-context'; const mockUsePageData = jest.fn(); diff --git a/test/src/pages/webinars/explore-page.test.tsx b/test/src/pages/webinars/explore-page.test.tsx index 623a938ed..901ef2c1b 100644 --- a/test/src/pages/webinars/explore-page.test.tsx +++ b/test/src/pages/webinars/explore-page.test.tsx @@ -2,11 +2,12 @@ import React from 'react'; import {describe, it} from '@jest/globals'; import {render, screen} from '@testing-library/preact'; import userEvent from '@testing-library/user-event'; -import {MemoryRouter, Routes, Route} from 'react-router-dom'; +import {Routes, Route} from 'react-router-dom'; import ExplorePage from '~/pages/webinars/explore-page/explore-page'; import useWebinarContext from '~/pages/webinars/webinar-context'; import {pastWebinar} from '../../data/webinars'; import type {Webinar} from '~/pages/webinars/types'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; // @ts-expect-error does not exist on const {routerFuture} = global; diff --git a/test/src/pages/webinars/view-webinars-page.test.tsx b/test/src/pages/webinars/view-webinars-page.test.tsx index 61218069d..f86b4f427 100644 --- a/test/src/pages/webinars/view-webinars-page.test.tsx +++ b/test/src/pages/webinars/view-webinars-page.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {describe, expect, it} from '@jest/globals'; import {render, screen} from '@testing-library/preact'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import {RouterContextProvider} from '~/components/shell/router-context'; import {upcomingWebinar} from '../../data/webinars'; import ViewWebinarsPage from '~/pages/webinars/view-webinars-page/view-webinars-page'; diff --git a/test/src/pages/webinars/webinar-context.test.tsx b/test/src/pages/webinars/webinar-context.test.tsx index 78a597aeb..b0d0f40b5 100644 --- a/test/src/pages/webinars/webinar-context.test.tsx +++ b/test/src/pages/webinars/webinar-context.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {describe, it} from '@jest/globals'; import {render} from '@testing-library/preact'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import useWebinarContext, {WebinarContextProvider} from '~/pages/webinars/webinar-context'; import useData from '~/helpers/use-data'; import {upcomingWebinar} from '../../data/webinars'; diff --git a/test/src/pages/webinars/webinars.test.tsx b/test/src/pages/webinars/webinars.test.tsx index e4d02cb94..2fb114370 100644 --- a/test/src/pages/webinars/webinars.test.tsx +++ b/test/src/pages/webinars/webinars.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {describe, it} from '@jest/globals'; import {render} from '@testing-library/preact'; -import {MemoryRouter} from 'react-router-dom'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; import WebinarsLoader from '~/pages/webinars/webinars'; function Component({path}: {path: string}) { diff --git a/test/test-utils.js b/test/test-utils.js index 9a730cd27..c8161e97f 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -16,7 +16,7 @@ const keyCodes = { function keyEvent(eventName, el, key) { el.dispatchEvent( new KeyboardEvent(eventName, { - key: key, + key, keyCode: keyCodes[key], bubbles: true, cancelable: true From 9554df4ad25532da06b8f2fda8fe20c643a84014 Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Thu, 2 Oct 2025 17:30:16 -0500 Subject: [PATCH 07/10] Coverage for use-link-handler --- .../components/shell/use-link-handler.test.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/src/components/shell/use-link-handler.test.tsx b/test/src/components/shell/use-link-handler.test.tsx index 2b7c22f7a..22e1d7aa1 100644 --- a/test/src/components/shell/use-link-handler.test.tsx +++ b/test/src/components/shell/use-link-handler.test.tsx @@ -8,6 +8,7 @@ import useLinkHandler, { import linkHelper from '~/helpers/link'; import {useNavigate} from 'react-router-dom'; import MemoryRouter from '~/../../test/helpers/future-memory-router'; +import * as PC from '~/contexts/portal'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -15,6 +16,16 @@ jest.mock('react-router-dom', () => ({ useNavigate: jest.fn() })); +const setPortal = jest.fn(); + +function setPortalPrefix(portalPrefix: string) { + jest.spyOn(PC, 'default').mockReturnValue({ + portalPrefix, + setPortal, + rewriteLinks: jest.fn() + }); +} + describe('use-link-handler', () => { const user = userEvent.setup(); const notPrevented = jest.fn(); @@ -69,6 +80,7 @@ describe('use-link-handler', () => { expect(notPrevented).toBeCalledWith(true); }); it('goes on when left click on valid URL', async () => { + setPortalPrefix('/portal'); const navigate = jest.fn(); const el = document.createElement('a'); @@ -84,6 +96,7 @@ describe('use-link-handler', () => { expect(navigate).toBeCalledWith('whatever', {x: 0, y: 0}); }); it('calls piTracker if available', async () => { + setPortalPrefix('/portal'); type WindowWithPiTracker = (typeof window) & { piTracker: (path: string) => void; } @@ -110,6 +123,7 @@ describe('use-link-handler', () => { expect(piTracker).toBeCalledWith('clickHref'); }); it('handles external URL opening local', async () => { + setPortalPrefix('/portal'); const navigate = jest.fn(); jest.spyOn(linkHelper, 'validUrlClick').mockReturnValue({ @@ -130,6 +144,7 @@ describe('use-link-handler', () => { expect(navigate).not.toBeCalled(); }); it('handles external URL opening in current tab when new window fails', async () => { + setPortalPrefix('/portal'); const navigate = jest.fn(); jest.spyOn(linkHelper, 'validUrlClick').mockReturnValue({ @@ -151,6 +166,7 @@ describe('use-link-handler', () => { expect(navigate).not.toBeCalled(); }); it('does the tracking info fetch', async () => { + setPortalPrefix('/portal'); const navigate = jest.fn(); jest.spyOn(linkHelper, 'validUrlClick').mockReturnValue({ @@ -171,6 +187,7 @@ describe('use-link-handler', () => { await user.click(screen.getByRole('link')); }); it('catches tracking fetch failure', async () => { + setPortalPrefix('/'); const navigate = jest.fn(); jest.spyOn(linkHelper, 'validUrlClick').mockReturnValue({ From a3897b796bf0fdcab9254fba9839156e069fbd61 Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Mon, 6 Oct 2025 15:59:32 -0500 Subject: [PATCH 08/10] Page-loaders --- .../shell/router-helpers/page-loaders.js | 78 ------------------- .../shell/router-helpers/page-loaders.tsx | 46 +++++++++++ .../router-helpers/portal-page-routes.tsx | 2 +- 3 files changed, 47 insertions(+), 79 deletions(-) delete mode 100644 src/app/components/shell/router-helpers/page-loaders.js create mode 100644 src/app/components/shell/router-helpers/page-loaders.tsx diff --git a/src/app/components/shell/router-helpers/page-loaders.js b/src/app/components/shell/router-helpers/page-loaders.js deleted file mode 100644 index 0f976f1fb..000000000 --- a/src/app/components/shell/router-helpers/page-loaders.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, {useEffect} from 'react'; -import { - Navigate, - useLocation -} from 'react-router-dom'; -import loadable from 'react-loadable'; -import LoadingPlaceholder from '~/components/loading-placeholder/loading-placeholder'; -import useLayoutContext from '~/contexts/layout'; - -function useAnalyticsPageView() { - const location = useLocation(); - const isRedirect = location.state?.redirect; - - useEffect(() => { - window.scrollTo(0, 0); - }, [isRedirect]); -} - -function useLoading(name) { - const {pathname} = useLocation(); - - return React.useCallback( - ({error, pastDelay, retry}) => { - if (error) { - if (error.code === 'MODULE_NOT_FOUND') { - return pathname.endsWith('/') - ?

{name} did not load (module not found)

// I don't think we ever get here - : ; - } - return
Error!
; - } - if (pastDelay) { - return ; - } - return null; - }, - [name, pathname] - ); -} - -function usePage(name) { - const loading = useLoading(name); - - return React.useMemo( - () => loadable({ - loader: () => import(`~/pages/${name}/${name}`), - loading, - render(loaded, props) { - const Component = loaded.default; - - return ; - } - }), - [name, loading] - ); -} - -export function ImportedPage({name}) { - const {pathname} = useLocation(); - const Page = usePage(name); - const {layoutParameters, setLayoutParameters} = useLayoutContext(); - - if (layoutParameters.name === null) { - setLayoutParameters(); - } - - useAnalyticsPageView(); - - // Scroll to the top when the pathname changes - // (Avoids scrolling when going to a new tab) - useEffect( - () => window.scrollTo(0, 0), - [name, pathname] - ); - - return ; -} - diff --git a/src/app/components/shell/router-helpers/page-loaders.tsx b/src/app/components/shell/router-helpers/page-loaders.tsx new file mode 100644 index 000000000..29b416283 --- /dev/null +++ b/src/app/components/shell/router-helpers/page-loaders.tsx @@ -0,0 +1,46 @@ +import React, {useEffect} from 'react'; +import {useLocation} from 'react-router-dom'; +import loadable from 'react-loadable'; +import LoadingPlaceholder from '~/components/loading-placeholder/loading-placeholder'; +import useLayoutContext from '~/contexts/layout'; + +function useAnalyticsPageView() { + const location = useLocation(); + const isRedirect = location.state?.redirect; + + useEffect(() => { + window.scrollTo(0, 0); + }, [isRedirect]); +} + +function usePage(name: string) { + return React.useMemo(() => { + return loadable({ + loader: () => import(`~/pages/${name}/${name}`), + loading: LoadingPlaceholder, + render(loaded, props: object) { + const Component = loaded.default; + + return ; + } + }); + }, [name]); +} + +export function ImportedPage({name}: {name: string}) { + const {pathname} = useLocation(); + const Page = usePage(name); + const {layoutParameters, setLayoutParameters} = useLayoutContext(); + + if (layoutParameters.name === null) { + setLayoutParameters(); + } + + useAnalyticsPageView(); + + // Scroll to the top when the pathname changes + // (Avoids scrolling when going to a new tab) + useEffect(() => window.scrollTo(0, 0), [name, pathname]); + + return ; +} diff --git a/src/app/components/shell/router-helpers/portal-page-routes.tsx b/src/app/components/shell/router-helpers/portal-page-routes.tsx index bc1520f64..c42fe1c0d 100644 --- a/src/app/components/shell/router-helpers/portal-page-routes.tsx +++ b/src/app/components/shell/router-helpers/portal-page-routes.tsx @@ -87,5 +87,5 @@ function PortalSubRoute() { return ; } - return ; + return ; } From 61d1880a2f5b28dfa1eecb5dfe853f9d4d004793 Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Mon, 6 Oct 2025 16:43:02 -0500 Subject: [PATCH 09/10] Remove piTracker test from shell --- test/src/components/shell.test.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/src/components/shell.test.tsx b/test/src/components/shell.test.tsx index 1c67ab31a..004eb9bff 100644 --- a/test/src/components/shell.test.tsx +++ b/test/src/components/shell.test.tsx @@ -133,21 +133,6 @@ describe('shell', () => { await screen.findByText('What is your question about?'); expect(screen.queryAllByRole('navigation')).toHaveLength(0); }); - it('handles piTracker ', async () => { - type WindowWithPiTracker = (typeof window) & { - piTracker?: (path: string) => void; - } - const w = window as WindowWithPiTracker; - const piTracker = jest.fn(); - - w.piTracker = (path: string) => piTracker(path); - mockBrowserInitialEntries(['/']); - - render(AppElement); - - await waitFor(() => expect(piTracker).toHaveBeenCalled()); - delete w.piTracker; - }); it('(skip to main content link) works', async () => { window.scrollBy = jest.fn(); mockBrowserInitialEntries(['/']); From f9b528831ec3d0e222ccffc6d1d0ae196f1e1665 Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Mon, 6 Oct 2025 18:51:39 -0500 Subject: [PATCH 10/10] Reorder tests to avoid flaky failure --- test/src/components/shell.test.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/src/components/shell.test.tsx b/test/src/components/shell.test.tsx index 004eb9bff..6e07178f1 100644 --- a/test/src/components/shell.test.tsx +++ b/test/src/components/shell.test.tsx @@ -125,14 +125,6 @@ describe('shell', () => { setPortal.mockClear(); }); - it('Delivers embedded contact page', async () => { - mockBrowserInitialEntries(['/embedded/contact']); - spyUpd.mockReturnValueOnce(null); - - render(AppElement); - await screen.findByText('What is your question about?'); - expect(screen.queryAllByRole('navigation')).toHaveLength(0); - }); it('(skip to main content link) works', async () => { window.scrollBy = jest.fn(); mockBrowserInitialEntries(['/']); @@ -142,6 +134,14 @@ describe('shell', () => { await user.click(skipLink); await waitFor(() => expect(window.scrollBy).toHaveBeenCalled()); }); + it('Delivers embedded contact page', async () => { + mockBrowserInitialEntries(['/embedded/contact']); + spyUpd.mockReturnValueOnce(null); + + render(AppElement); + await screen.findByText('What is your question about?'); + expect(screen.queryAllByRole('navigation')).toHaveLength(0); + }); it('routes "home/anything" to top-level', async () => { mockBrowserInitialEntriesWithLocation(['/home/anything']);