diff --git a/src/app/contexts/layout.tsx b/src/app/contexts/layout.tsx index 8d8a61f1b..ce5af9305 100644 --- a/src/app/contexts/layout.tsx +++ b/src/app/contexts/layout.tsx @@ -18,17 +18,19 @@ type LayoutParameters = { const defaultLayoutParameters: LayoutParameters = {name: 'default'}; function useContextValue() { - const [layoutParameters, setLayoutParameters] = React.useReducer( - (state: LayoutParameters, newState: LayoutParameters) => { - if (newState === undefined) { - return defaultLayoutParameters; + const [layoutParameters, setLayoutParameters] = React.useState(defaultLayoutParameters); + const updateIfNotEqual = React.useCallback( + (newValue?: LayoutParameters) => { + if (newValue === undefined) { + setLayoutParameters(defaultLayoutParameters); + return; } - if (deepEqual(state, newState)) { - return state; + if (deepEqual(layoutParameters, newValue)) { + return; } - return newState; + setLayoutParameters(newValue); }, - defaultLayoutParameters + [layoutParameters] ); const LoadableLayout = React.useMemo( () => @@ -45,7 +47,7 @@ function useContextValue() { [LoadableLayout, layoutParameters.data] ); - return {Layout, setLayoutParameters, layoutParameters}; + return {Layout, setLayoutParameters: updateIfNotEqual, layoutParameters}; } const {useContext, ContextProvider} = buildContext({useContextValue}); diff --git a/src/app/pages/about/about.js b/src/app/pages/about/about.tsx similarity index 57% rename from src/app/pages/about/about.js rename to src/app/pages/about/about.tsx index ef5d13779..a70a54b07 100644 --- a/src/app/pages/about/about.js +++ b/src/app/pages/about/about.tsx @@ -7,9 +7,22 @@ import './about.scss'; const slug = 'pages/about'; -function translateCard(c) { - const imgEntry = c.find((v) => v.type === 'image'); - const textEntry = c.find((v) => v.type === 'paragraph'); +type ImageCard = { + type: 'image'; + value: { + image: string; + link: string; + }; +}; +type TextCard = { + type: 'paragraph'; + value: string; +}; +type RawCard = [ImageCard, TextCard]; + +function translateCard(c: RawCard) { + const imgEntry = c.find((v) => v.type === 'image') as ImageCard; + const textEntry = c.find((v) => v.type === 'paragraph') as TextCard; return { image: imgEntry.value.image, @@ -18,29 +31,61 @@ function translateCard(c) { }; } -function Card({link, image, text}) { +function Card({ + link, + image, + text +}: { + link: string; + image: string; + text: string; +}) { const optimizedImage = useOptimizedImage(image); return ( -
- {text} -
+
{text}
); } -function About({data: { - whoHeading, whoParagraph, whoImageUrl, - whatHeading, whatParagraph, whatCards, - whereHeading, whereParagraph, whereMapUrl: map, whereMapAlt -}}) { +type AboutData = { + whoHeading: string; + whoParagraph: string; + whoImageUrl: string; + whatHeading: string; + whatParagraph: string; + whatCards: RawCard[]; + whereHeading: string; + whereParagraph: string; + whereMapUrl: string; + whereMapAlt: string; +}; + +function About({ + data: { + whoHeading, + whoParagraph, + whoImageUrl, + whatHeading, + whatParagraph, + whatCards, + whereHeading, + whereParagraph, + whereMapUrl: map, + whereMapAlt + } +}: { + data: AboutData; +}) { const cards = React.useMemo( - () => (whatCards || []).map(translateCard), + () => whatCards.map(translateCard), [whatCards] ); - const mapAlt = whereMapAlt || 'animated map suggesting where our books are being adopted'; + const mapAlt = + whereMapAlt || + 'animated map suggesting where our books are being adopted'; const maxDim = window.innerWidth < 1920 ? 1015 : null; const optimizedWhoImage = useOptimizedImage(whoImageUrl, maxDim); @@ -63,13 +108,14 @@ function About({data: {
- {cards.map( - ({link, image, text}) => - - )} + {cards.map(({link, image, text}) => ( + + ))}
@@ -92,7 +138,5 @@ function About({data: { } export default function AboutLoader() { - return ( - - ); + return ; } diff --git a/src/app/pages/adopters/adopters.js b/src/app/pages/adopters/adopters.js deleted file mode 100644 index 5ad1ab557..000000000 --- a/src/app/pages/adopters/adopters.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import usePageData from '~/helpers/use-page-data'; -import './adopters.scss'; - -export default function Adopters() { - const pageData = usePageData('adopters'); - - if (!pageData) { - return null; - } - if (pageData.error) { - return ( -
-

Data missing in the CMS

-
- ); - } - - return ( -
-

Complete list of institutions that have adopted OpenStax

-
    - { - pageData.map((adopter) => -
  • {adopter.name}
  • - ) - } -
-
- ); -} diff --git a/src/app/pages/adopters/adopters.tsx b/src/app/pages/adopters/adopters.tsx new file mode 100644 index 000000000..f86ecf134 --- /dev/null +++ b/src/app/pages/adopters/adopters.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import LoaderPage from '~/components/jsx-helpers/loader-page'; +import './adopters.scss'; + +const slug = 'adopters'; + +type Data = { + name: string; + description: string; + website: string; +}; + +function Adopters({data}: {data: Data[]}) { + return ( +
+

Complete list of institutions that have adopted OpenStax

+
    + {data.map((adopter) => ( +
  • + {adopter.name} +
  • + ))} +
+
+ ); +} + +export default function AdoptersLoader() { + return ; +} diff --git a/test/helpers/mock-lazyload.tsx b/test/helpers/mock-lazyload.tsx new file mode 100644 index 000000000..8f843e3c9 --- /dev/null +++ b/test/helpers/mock-lazyload.tsx @@ -0,0 +1,8 @@ +import React from 'react'; + +jest.mock('react-lazyload', () => ({ + __esModule: true, + default: ({children}: React.PropsWithChildren) => { + return children; + } +})); diff --git a/test/setupFile.js b/test/setupFile.js index 124c06272..7ed9faad3 100644 --- a/test/setupFile.js +++ b/test/setupFile.js @@ -1,5 +1,6 @@ import 'settings'; import './helpers/fetch-mocker'; +import './helpers/mock-lazyload'; import {LocalStorage} from 'node-localstorage'; import ReactModal from 'react-modal'; diff --git a/test/src/contexts/layout.test.tsx b/test/src/contexts/layout.test.tsx index e7d8a22e9..3248dcd33 100644 --- a/test/src/contexts/layout.test.tsx +++ b/test/src/contexts/layout.test.tsx @@ -1,24 +1,50 @@ import React from 'react'; import {render, screen} from '@testing-library/preact'; import useLayoutContext, { LayoutContextProvider } from '~/contexts/layout'; +import {MemoryRouter} from 'react-router-dom'; function Component() { const {Layout, setLayoutParameters} = useLayoutContext(); React.useEffect( - () => setLayoutParameters({name: 'landing', data: {title: 'title', layout: [ - { - value: { - navLinks: [{ - text: 'Landing page link', - target: { - type: 'href', - value: 'whatever' + () => { + setLayoutParameters({ + name: 'landing', + data: { + title: 'title', layout: [ + { + value: { + navLinks: [{ + text: 'Landing page link', + target: { + type: 'href', + value: 'whatever' + } + }] } }] } - } - ]}}), + }); + // Exercise the code that tests for equal setting + setTimeout(() => setLayoutParameters({ + name: 'landing', + data: { + title: 'title', layout: [ + { + value: { + navLinks: [{ + text: 'Landing page link', + target: { + type: 'href', + value: 'whatever' + } + }] + } + }] + } + }), 5); + setTimeout(() => setLayoutParameters(undefined), 10); + }, [setLayoutParameters] ); @@ -29,13 +55,24 @@ function Component() { ); } +jest.useFakeTimers(); +jest.mock('~/layouts/default/microsurvey-popup/microsurvey-popup', () => jest.fn()); +jest.mock('~/layouts/default/header/header', () => jest.fn()); +jest.mock('~/layouts/default/lower-sticky-note/lower-sticky-note', () => jest.fn()); +jest.mock('~/layouts/default/welcome/welcome', () => jest.fn()); +jest.mock('~/layouts/default/footer/footer', () => jest.fn()); +jest.mock('~/layouts/default/takeover-dialog/takeover-dialog', () => jest.fn()); + describe('layout-context', () => { it('renders a landing page', async () => { render( - - - + + + + + ); - await screen.findByText('Landing page link'); + jest.runAllTimers(); + await screen.findByText('No menus'); }); }); diff --git a/test/src/data/about.js b/test/src/data/about.js new file mode 100644 index 000000000..b8d8b70cc --- /dev/null +++ b/test/src/data/about.js @@ -0,0 +1,199 @@ +/* eslint camelcase: 0 */ +export default { + id: 248, + meta: { + slug: 'about', + seo_title: 'Learn more about OpenStax', + search_description: `Interested in learning more about OpenStax? We're an OER publisher on a mission to + improve access to education by publishing free textbooks and developing low-cost + learning technology.`, + type: 'pages.AboutUsPage', + detail_url: 'https://dev.openstax.org/apps/cms/api/v2/pages/248/', + html_url: 'https://dev.openstax.org/about/', + show_in_menus: false, + first_published_at: '2018-08-13T23:31:08.771162-05:00', + alias_of: null, + parent: { + id: 29, + meta: { + type: 'pages.HomePage', + detail_url: + 'https://dev.openstax.org/apps/cms/api/v2/pages/29/', + html_url: 'https://dev.openstax.org/' + }, + title: 'Openstax Homepage' + }, + locale: 'en' + }, + title: 'About us', + who_heading: 'Who we are', + who_paragraph: `OpenStax is part of Rice University, which is a 501(c)(3) nonprofit charitable corporation. + As an educational initiative, it's our mission to transform learning so that education works for every student. + Through our partnerships with philanthropic foundations and our + alliance with other educational resource companies, we're breaking + down the most common barriers to learning. + Because we believe that everyone should and can have access to knowledge. +
+
+ Looking for more information about OpenStax? Visit our FAQ page.`, + who_image: { + id: 734, + meta: { + width: 1590, + height: 1000, + type: 'wagtailimages.Image', + detail_url: 'https://dev.openstax.org/apps/cms/api/v2/images/734/', + download_url: + 'https://assets.openstax.org/oscms-dev/media/original_images/students2x.original.original.webp' + }, + title: 'students2x.webp' + }, + who_image_url: + 'https://assets.openstax.org/oscms-dev/media/original_images/students2x.original.original.webp', + what_heading: 'What we do', + what_paragraph: `We publish high-quality, peer-reviewed, openly licensed college textbooks + that are absolutely free online and low cost in print. Seriously. We've also + developed a low-cost, research-based courseware that gives students the tools they need + to complete their course the first time around, and a new online math homework system with + step-level feedback. Check out our current + library of textbooks and explore OpenStax + Tutor Beta.`, + what_cards: [ + { + type: 'card', + value: [ + { + type: 'image', + value: { + image: 482, + alt_text: 'OpenStax textbooks', + link: 'https://openstax.org/subjects', + alignment: 'left', + identifier: '' + }, + id: '47ba4699-e2b8-4581-8e5e-60d42350d14a' + }, + { + type: 'paragraph', + value: `Our first textbook was published in 2012 and our library has since grown to + 42 books for college and high school courses.`, + id: 'f56929e7-3a34-4822-b763-e303badb10f7' + } + ], + id: 'c045f0e5-3819-42de-beb2-bee86d5073d4' + }, + { + type: 'card', + value: [ + { + type: 'image', + value: { + image: 484, + alt_text: 'OpenStax research team', + link: 'https://openstax.org/research', + alignment: 'left', + identifier: '' + }, + id: '9ac91575-a439-49d2-bbb9-a65e432d374a' + }, + { + type: 'paragraph', + value: `We have a world-class research team dedicated to improving learning + outcomes for all students.`, + id: 'df9a28d6-aed5-4a2d-8523-3cf9636a17f2' + } + ], + id: '2ea9bd47-d118-4ba4-a5b0-d565410e13d9' + }, + { + type: 'card', + value: [ + { + type: 'image', + value: { + image: 633, + alt_text: 'Institutional Partnership', + link: 'https://openstax.org/institutional-partnership', + alignment: 'left', + identifier: '' + }, + id: '0dfd65cf-2a33-4a5b-8872-2adebc1cdeb4' + }, + { + type: 'paragraph', + value: `Our Institutional Partner Program provides free coaching and + strategic planning to institutions expanding their open educational + resources programs.`, + id: '768e13d7-ae7d-442f-961f-919c860f8b99' + } + ], + id: '4ab54534-5f5c-4bb6-a741-4cae37b33ac2' + }, + { + type: 'card', + value: [ + { + type: 'image', + value: { + image: 485, + alt_text: 'OpenStax partners', + link: 'https://openstax.org/partners', + alignment: 'left', + identifier: '' + }, + id: 'ecef09c6-c7cb-4e2b-bbe0-6a402bd09698' + }, + { + type: 'paragraph', + value: `We've partnered with companies to provide low-cost technology products + that are integrated with our books.`, + id: '985b2761-5509-4f4f-bc90-e0f7d60b6707' + } + ], + id: '922bddb6-8ffe-47d8-8133-4519e7b4066e' + }, + { + type: 'card', + value: [ + { + type: 'image', + value: { + image: 483, + alt_text: 'OpenStax Tutor Beta', + link: 'https://openstax.org/openstax-tutor', + alignment: 'left', + identifier: '' + }, + id: '674f897f-7dda-4938-b683-771e295e761a' + }, + { + type: 'paragraph', + value: `In 2017, we launched OpenStax Tutor Beta, our easy-to-use, research-backed + online courseware.`, + id: '16347f60-6868-4bbb-9a77-454e31f9122f' + } + ], + id: '92f4b75d-871d-4c4a-a6e5-1db8b14040c7' + } + ], + where_heading: "Where we're going", + where_paragraph: `Our textbooks are being used in 56% of college and universities in the U.S. and over 100 countries, but there’s still room to grow. Alongside our efforts to expand our library, we have been taking steps toward improving student learning and advancing research in learning science. Access is just the beginning – we want to make educational tools better for a new generation of learners. + + Explore our map to see where we're headed.`, + where_map: { + id: 488, + meta: { + width: 2400, + height: 1400, + type: 'wagtailimages.Image', + detail_url: 'https://dev.openstax.org/apps/cms/api/v2/images/488/', + download_url: + 'https://assets.openstax.org/oscms-dev/media/original_images/about-map-animate2x.gif' + }, + title: 'about-map-animate@2x.gif' + }, + where_map_alt: null, + where_map_url: + 'https://assets.openstax.org/oscms-dev/media/original_images/about-map-animate2x.gif', + promote_image: null +}; diff --git a/test/src/pages/about.test.tsx b/test/src/pages/about.test.tsx new file mode 100644 index 000000000..adba6ac35 --- /dev/null +++ b/test/src/pages/about.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +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'; + +const anImage = { + id: 482, + file: 'https://assets.openstax.org/oscms-dev/media/original_images/About_Us_Icon.png', + title: 'About Us Icon.png', + height: 308, + width: 540, + /* eslint camelcase: 0 */ + created_at: '2018-08-13T23:26:44.689948-05:00' +}; + +global.fetch = jest.fn().mockImplementation( + (args: [string]) => { + const payload = (args.includes('pages/about')) ? aboutData : anImage; + + return Promise.resolve({ + ok: true, + json() { + return Promise.resolve(payload); + } + }); + } +); + +describe('about page', () => { + it('renders', async () => { + render(); + await screen.findByText('Who we are'); + }); + it('sets maxdim based on window.innerwidth', async () => { + window.innerWidth = 2000; + render(); + await screen.findByText('Who we are'); + }); +}); diff --git a/test/src/pages/adopters.test.tsx b/test/src/pages/adopters.test.tsx new file mode 100644 index 000000000..e2bec7e99 --- /dev/null +++ b/test/src/pages/adopters.test.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import {render, screen} from '@testing-library/preact'; +import {describe, it} from '@jest/globals'; +import AdoptersPage from '~/pages/adopters/adopters'; +import {MemoryRouter} from 'react-router-dom'; + +const pageData = [ + { + name: 'First one', + description: 'First description', + website: 'http://first.one' + }, + { + name: 'Second one', + description: 'Second description', + website: 'http://second.one' + } +]; + +global.fetch = jest.fn().mockReturnValue( + Promise.resolve({ + ok: true, + json() { + return Promise.resolve(pageData); + } + }) +); + +describe('Adopters page', () => { + it('renders', async () => { + render( + + + + ); + await screen.findByText('Complete list', {exact: false}); + expect(screen.getAllByRole('listitem')).toHaveLength(pageData.length); + }); +}); diff --git a/test/src/pages/institutional-partnership/institutional-partnership.test.js b/test/src/pages/institutional-partnership/institutional-partnership.test.js index 0efdacf88..a0ca24c51 100644 --- a/test/src/pages/institutional-partnership/institutional-partnership.test.js +++ b/test/src/pages/institutional-partnership/institutional-partnership.test.js @@ -13,6 +13,6 @@ describe('InstitutionalPartnership', () => { ); await screen.findByText('About the program'); - expect(screen.queryAllByRole('link')).toHaveLength(2); + expect(screen.queryAllByRole('link')).toHaveLength(8); }); });