diff --git a/src/app/pages/foundation/foundation.js b/src/app/pages/foundation/foundation.tsx similarity index 52% rename from src/app/pages/foundation/foundation.js rename to src/app/pages/foundation/foundation.tsx index 01e2f8873..599fda2b0 100644 --- a/src/app/pages/foundation/foundation.js +++ b/src/app/pages/foundation/foundation.tsx @@ -4,25 +4,61 @@ import LoaderPage from '~/components/jsx-helpers/loader-page'; import ClippedImage from '~/components/clipped-image/clipped-image'; import './foundation.scss'; +type FunderData = { + funderName: string; + url?: string; +}; + +type ImageData = { + file: string; +}; + +type FoundationGroupData = { + groupTitle: string; + description: string; + funders: FunderData[]; + image?: ImageData; +}; + +type FoundationPageData = { + bannerImage: { + meta: { + downloadUrl: string; + }; + }; + bannerHeading: string; + bannerDescription: string; + funderGroups: FoundationGroupData[]; + disclaimer: string; +}; + const slug = 'pages/supporters'; -function Funder({data}) { - return ( - data.url ? - {data.funderName} : - {data.funderName} +function Funder({data}: {data: FunderData}) { + return data.url ? ( + {data.funderName} + ) : ( + {data.funderName} ); } -function Funders({data}) { +function Funders({data}: {data: FunderData[]}) { return (
- {data.map((f, i) => )} + {data.map((f, i) => ( + + ))}
); } -function FundersWithImage({data, image}) { +function FundersWithImage({ + data, + image +}: { + data: FunderData[]; + image: ImageData; +}) { return (
@@ -31,21 +67,21 @@ function FundersWithImage({data, image}) { ); } -function FoundationGroup({data}) { +function FoundationGroup({data}: {data: FoundationGroupData}) { return (

{data.groupTitle}

{data.description}
- { - data.image?.file ? - : - - } + {data.image?.file ? ( + + ) : ( + + )}
); } -function FoundationPage({data: model}) { +function FoundationPage({data: model}: {data: FoundationPageData}) { return (
@@ -57,7 +93,9 @@ function FoundationPage({data: model}) {
- {model.funderGroups.map((g, i) => )} + {model.funderGroups.map((g, i) => ( + + ))}
{model.disclaimer}
); diff --git a/src/app/pages/institutional-partnership-application/institutional-partnership-application.js b/src/app/pages/institutional-partnership-application/institutional-partnership-application.tsx similarity index 52% rename from src/app/pages/institutional-partnership-application/institutional-partnership-application.js rename to src/app/pages/institutional-partnership-application/institutional-partnership-application.tsx index 3460c9e50..7b21d8c6b 100644 --- a/src/app/pages/institutional-partnership-application/institutional-partnership-application.js +++ b/src/app/pages/institutional-partnership-application/institutional-partnership-application.tsx @@ -4,8 +4,37 @@ import LoaderPage from '~/components/jsx-helpers/loader-page'; import sample from 'lodash/sample'; import './institutional-partnership-application.scss'; -function TestimonialBlock({data}) { - const testimonials = [ +type Testimonial = { + block: string; + name: string; + address1: string; + address2: string; +}; + +type TestimonialData = { + quote: string; + quoteAuthor: string; + quoteTitle: string; + quoteSchool: string; + applicationQuote: string; + applicationQuoteAuthor: string; + applicationQuoteTitle: string; + applicationQuoteSchool: string; +}; + +type ProgramSection = { + heading: string; + description: string; +}; + +type ApplicationPageData = { + headingYear: string; + heading: string; + programTabContent: [ProgramSection[]]; +} & TestimonialData; + +function TestimonialBlock({data}: {data: TestimonialData}) { + const testimonials: Testimonial[] = [ { block: data.quote, name: data.quoteAuthor, @@ -25,12 +54,14 @@ function TestimonialBlock({data}) {
-

{testimonial.block}

+

{testimonial?.block}

- -{testimonial.name}
- {testimonial.address1}
- {testimonial.address2} + -{testimonial?.name} +
+ {testimonial?.address1} +
+ {testimonial?.address2}

@@ -39,18 +70,20 @@ function TestimonialBlock({data}) { ); } -function ProgramDetails({model}) { +function ProgramDetails({model}: {model: [ProgramSection[]]}) { return ( - model[0].map((section) => -
-

{section.heading}

- -
- ) + <> + {model[0].map((section) => ( +
+

{section.heading}

+ +
+ ))} + ); } -function ApplicationPage({data}) { +function ApplicationPage({data}: {data: ApplicationPageData}) { return (
@@ -62,7 +95,7 @@ function ApplicationPage({data}) {
- + ); } @@ -71,7 +104,8 @@ export default function ApplicationLoader() { return (
diff --git a/test/src/pages/foundation.test.tsx b/test/src/pages/foundation.test.tsx new file mode 100644 index 000000000..0cdc8915c --- /dev/null +++ b/test/src/pages/foundation.test.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import {render, screen} from '@testing-library/preact'; +import {describe, it} from '@jest/globals'; +import FoundationPage from '~/pages/foundation/foundation'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; + +const mockFoundationData = { + bannerImage: { + meta: { + downloadUrl: 'https://example.com/banner.jpg' + } + }, + bannerHeading: 'Our Supporters', + bannerDescription: + '

Thanks to generous funders who support our mission.

', + funderGroups: [ + { + groupTitle: 'Major Funders', + description: 'Organizations that provide significant support', + funders: [ + { + funderName: 'Test Foundation', + url: 'https://example.com' + }, + { + funderName: 'Another Supporter' + } + ] + }, + { + groupTitle: 'Secondary Funders', + description: 'Organizations that provide support', + image: {file: '/path/to/image'}, + funders: [] + } + ], + disclaimer: 'All funders listed have provided support to OpenStax.' +}; + +global.fetch = jest.fn().mockImplementation((args: [string]) => { + const payload = args.includes('pages/supporters') ? mockFoundationData : {}; + + return Promise.resolve({ + ok: true, + json() { + return Promise.resolve(payload); + } + }); +}); + +describe('foundation page', () => { + it('displays funder groups', async () => { + render( + + + + ); + await screen.findByText('Our Supporters'); + await screen.findByText('Major Funders'); + await screen.findByText( + 'Organizations that provide significant support' + ); + }); + + it('displays funders with and without links', async () => { + render( + + + + ); + const linkedFunder = await screen.findByRole('link', { + name: 'Test Foundation' + }); + + expect(linkedFunder.getAttribute('href')).toBe('https://example.com'); + + await screen.findByText('Another Supporter'); + }); + + it('displays disclaimer', async () => { + render( + + + + ); + await screen.findByText( + 'All funders listed have provided support to OpenStax.' + ); + }); +}); diff --git a/test/src/pages/institutional-partnership-application.test.tsx b/test/src/pages/institutional-partnership-application.test.tsx new file mode 100644 index 000000000..72e5054ec --- /dev/null +++ b/test/src/pages/institutional-partnership-application.test.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import {render, screen} from '@testing-library/preact'; +import '@testing-library/jest-dom'; +import {describe, it} from '@jest/globals'; +import ApplicationPage from '~/pages/institutional-partnership-application/institutional-partnership-application'; +import MemoryRouter from '~/../../test/helpers/future-memory-router'; + +const mockApplicationData = { + headingYear: '2024', + heading: 'Institutional Partnership Application', + programTabContent: [ + [ + { + heading: 'Program Overview', + description: + '

Learn about our institutional partnership program.

' + }, + { + heading: 'Benefits', + description: + '

Discover the benefits of partnering with us.

' + } + ] + ], + quote: 'OpenStax has transformed our teaching approach.', + quoteAuthor: 'Dr. Jane Smith', + quoteTitle: 'Professor of Biology', + quoteSchool: 'State University', + applicationQuote: 'The application process was straightforward.', + applicationQuoteAuthor: 'Prof. John Doe', + applicationQuoteTitle: 'Department Chair', + applicationQuoteSchool: 'Community College' +}; + +global.fetch = jest.fn().mockImplementation((args: [string]) => { + const payload = args.includes('pages/institutional-partnership') + ? mockApplicationData + : {}; + + return Promise.resolve({ + ok: true, + json() { + return Promise.resolve(payload); + } + }); +}); + +function Component() { + return ( + + + + ); +} + +describe('institutional partnership application page', () => { + it('displays program sections', async () => { + render(); + await screen.findByText('Program Overview'); + await screen.findByText('Benefits'); + }); + + it('displays testimonial content', async () => { + render(); + + // Since lodash sample() picks randomly, we need to check for either testimonial + // We'll check for elements that should always be present + const testimonialSection = await screen.findByRole('main'); + + expect(testimonialSection).toBeInTheDocument(); + + // The testimonial block should contain one of our test quotes + const testimonialContainer = await screen.findByText( + (content, element) => { + return element?.className === 'testimonial-box' || false; + } + ); + + expect(testimonialContainer).toBeInTheDocument(); + }); + + it('has proper main container class', async () => { + render(); + const mainElement = await screen.findByRole('main'); + + expect(mainElement).toHaveClass('institutional-page', 'page'); + }); +}); diff --git a/test/src/pages/institutional-partnership/institutional-partnership.test.tsx b/test/src/pages/institutional-partnership.test.tsx similarity index 100% rename from test/src/pages/institutional-partnership/institutional-partnership.test.tsx rename to test/src/pages/institutional-partnership.test.tsx