Skip to content

Commit 29b7556

Browse files
authored
Core 801 set document title for footer pages (#2715)
* Rename footer-pages -> .tsx * Set title and description; test coverage Also removed a couple of unused imports from latest-blog-posts * Add lint to the build steps * Fix Contact page, too With associated test updates
1 parent 80c090a commit 29b7556

File tree

7 files changed

+115
-30
lines changed

7 files changed

+115
-30
lines changed

.github/workflows/build.yml

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ jobs:
1515
-
1616
name: "Setup environment"
1717
run: ./script/setup
18+
-
19+
name: "Lint osweb"
20+
run: yarn lint
1821
-
1922
name: "Build osweb"
2023
run: ./script/build production

src/app/pages/contact/contact.tsx

+21-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
2-
import usePageData from '~/helpers/use-page-data';
2+
import LoaderPage from '~/components/jsx-helpers/loader-page';
3+
import useDocumentHead from '~/helpers/use-document-head';
34
import RawHTML from '~/components/jsx-helpers/raw-html';
45
import Form from './form';
56
import './contact.scss';
@@ -10,22 +11,26 @@ type PageData = {
1011
mailingHeader: string;
1112
mailingAddress: string;
1213
customerService: string;
14+
meta?: {
15+
searchDescription: string;
16+
}
1317
}
1418

15-
export default function ContactPage() {
16-
const pageData = usePageData<PageData>('pages/contact');
17-
18-
if (!pageData) {
19-
return null;
20-
}
19+
function ContactPage({data: pageData}: {data: PageData}) {
2120
const {
2221
title,
2322
tagline,
2423
mailingHeader,
2524
mailingAddress,
26-
customerService
25+
customerService,
26+
meta
2727
} = pageData;
2828

29+
useDocumentHead({
30+
title,
31+
description: meta?.searchDescription
32+
});
33+
2934
return (
3035
<main id="maincontent" className="contact-page page">
3136
<div className="hero">
@@ -48,3 +53,11 @@ export default function ContactPage() {
4853
</main>
4954
);
5055
}
56+
57+
export default function ContactPageLoader() {
58+
return (
59+
<main id="maincontent" className="contact-page page">
60+
<LoaderPage slug="pages/contact" Child={ContactPage} />
61+
</main>
62+
);
63+
}

src/app/pages/footer-page/footer-page.js renamed to src/app/pages/footer-page/footer-page.tsx

+36-16
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,31 @@ import React from 'react';
22
import RawHTML from '~/components/jsx-helpers/raw-html';
33
import usePageData from '~/helpers/use-page-data';
44
import {useLocation} from 'react-router-dom';
5+
import useDocumentHead from '~/helpers/use-document-head';
56
import './footer-page.scss';
67

7-
const specialSlugFromPath = {
8+
const specialSlugFromPath: Record<string, string> = {
89
'/privacy': '/privacy-policy'
910
};
1011

11-
export default function FooterPage() {
12-
const {pathname} = useLocation();
13-
const slugEnd = specialSlugFromPath[pathname] || pathname;
14-
const slug = `pages${slugEnd}`;
15-
const data = usePageData(slug);
16-
17-
React.useLayoutEffect(
18-
() => window.scrollTo(0, 0),
19-
[pathname]
20-
);
12+
type PageData = {
13+
introHeading: string;
14+
title: string;
15+
meta: {
16+
searchDescription: string;
17+
};
18+
} & {
19+
[contentFieldName: string]: string;
20+
}
2121

22-
if (!data) {
23-
return null;
24-
}
22+
function FooterPage({data}: {data: PageData}) {
23+
useDocumentHead({
24+
title: data.title,
25+
description: data.meta.searchDescription
26+
});
2527

26-
const contentFieldName = Reflect.ownKeys(data)
27-
.find((k) => k.match(/Content$/));
28+
const contentFieldName = Object.keys(data)
29+
.find((k) => k.match(/Content$/)) as string;
2830
const {introHeading: heading, [contentFieldName]: content} = data;
2931

3032
return (
@@ -37,3 +39,21 @@ export default function FooterPage() {
3739
</div>
3840
);
3941
}
42+
43+
export default function LoadFooterPage() {
44+
const {pathname} = useLocation();
45+
const slugEnd = specialSlugFromPath[pathname] ?? pathname;
46+
const slug = `pages${slugEnd}`;
47+
const data = usePageData<PageData>(slug);
48+
49+
React.useLayoutEffect(
50+
() => window.scrollTo(0, 0),
51+
[pathname]
52+
);
53+
54+
if (!data) {
55+
return null;
56+
}
57+
58+
return <FooterPage data={data} />;
59+
}

test/src/components/shell.test.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import AppElement from '~/components/shell/shell';
55
import {BrowserRouter, MemoryRouter as MR} from 'react-router-dom';
66
import ReactModal from 'react-modal';
77

8+
// @ts-expect-error does not exist on
9+
const {routerFuture} = global;
10+
811
jest.mock('react-router-dom', () => {
912
const actualRouterDom = jest.requireActual('react-router-dom');
1013

@@ -32,8 +35,10 @@ jest.mock('react-modal', () => ({
3235

3336
describe('shell', () => {
3437
it('Delivers embedded contact page', async () => {
38+
console.warn = jest.fn();
39+
console.debug = jest.fn();
3540
(BrowserRouter as jest.Mock).mockImplementationOnce(({children}) => (
36-
<MR initialEntries={['/embedded/contact']}>{children}</MR>
41+
<MR initialEntries={['/embedded/contact']} future={routerFuture}>{children}</MR>
3742
));
3843

3944
render(AppElement);
@@ -42,7 +47,7 @@ describe('shell', () => {
4247
});
4348
it('Delivers normal contact page', async () => {
4449
(BrowserRouter as jest.Mock).mockImplementationOnce(({children}) => (
45-
<MR initialEntries={['/contact']}>{children}</MR>
50+
<MR initialEntries={['/contact']} future={routerFuture}>{children}</MR>
4651
));
4752

4853
render(AppElement);

test/src/pages/blog/latest-blog-posts.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import {render, screen} from '@testing-library/preact';
33
import {describe, it, expect} from '@jest/globals';
4-
import {BrowserRouter, MemoryRouter, Routes, Route} from 'react-router-dom';
4+
import {BrowserRouter} from 'react-router-dom';
55
import {BlogContextProvider} from '~/pages/blog/blog-context';
66

77
import LatestBlogPosts from '~/pages/blog/latest-blog-posts/latest-blog-posts';

test/src/pages/contact.test.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ jest.spyOn(SFF, 'default').mockImplementation(MockSfForm);
6262
describe('contact page', () => {
6363
const user = userEvent.setup();
6464

65-
it('returns null until data', () => {
65+
it('returns loader page until data', () => {
6666
spyUsePageData.mockReturnValue(undefined);
67-
const {container} = render(<Component />);
67+
render(<Component />);
6868

69-
expect(container.innerHTML).toBe('');
69+
expect(document.body.textContent).toBe('');
7070
});
7171
it('displays the form', async () => {
7272
spyUsePageData.mockReturnValue(pageData);

test/src/pages/footer-page.test.tsx

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import {render, screen} from '@testing-library/preact';
3+
import {MemoryRouter} from 'react-router-dom';
4+
import * as UPD from '~/helpers/use-page-data';
5+
import FooterPage from '~/pages/footer-page/footer-page';
6+
7+
const mockUsePageData = jest.spyOn(UPD, 'default');
8+
9+
// @ts-expect-error does not exist on
10+
const {routerFuture} = global;
11+
12+
window.scrollTo = jest.fn();
13+
14+
describe('footer-page', () => {
15+
const saveWarn = console.warn;
16+
17+
it('renders', () => {
18+
mockUsePageData.mockReturnValue({
19+
introHeading: 'heading',
20+
title: 'docTitle',
21+
meta: {
22+
searchDescription: 'description in head'
23+
},
24+
pageContent: 'the html for the page'
25+
});
26+
console.warn = jest.fn();
27+
28+
render(
29+
<MemoryRouter initialEntries={['/aslug']} future={routerFuture}>
30+
<FooterPage />
31+
</MemoryRouter>);
32+
expect((screen.getByRole('heading', {level: 1})).textContent).toBe('heading');
33+
expect(document.head.querySelector('title')?.textContent).toBe('docTitle - OpenStax');
34+
console.warn = saveWarn;
35+
});
36+
it('renders nothing until page data is received', async () => {
37+
mockUsePageData.mockReturnValue(null);
38+
render(
39+
<MemoryRouter initialEntries={['/aslug']} future={routerFuture}>
40+
<FooterPage />
41+
</MemoryRouter>);
42+
expect(document.body.textContent).toBe('');
43+
});
44+
});

0 commit comments

Comments
 (0)