Skip to content

Commit b4bec6f

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/tar-6.2.1
2 parents 7e08894 + 7fee251 commit b4bec6f

11 files changed

+160
-7
lines changed

src/app/content/components/Content.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import ErrorBoundary from '../../errors/components/ErrorBoundary';
77
import Notifications from '../../notifications/components/Notifications';
88
import theme from '../../theme';
99
import { AppState } from '../../types';
10+
import { Book } from '../types';
1011
import HighlightsPopUp from '../highlights/components/HighlightsPopUp';
1112
import KeyboardShortcutsPopup from '../keyboardShortcuts/components/KeyboardShortcutsPopup';
1213
import PracticeQuestionsPopup from '../practiceQuestions/components/PracticeQuestionsPopup';
1314
import { mobileToolbarOpen } from '../search/selectors';
15+
import { book as bookSelector } from '../selectors';
1416
import StudyguidesPopUp from '../studyGuides/components/StudyGuidesPopUp';
1517
import Footer from './../../components/Footer';
1618
import Attribution from './Attribution';
@@ -25,6 +27,7 @@ import {
2527
topbarMobileHeight
2628
} from './constants';
2729
import ContentPane from './ContentPane';
30+
import ContentWarning from './ContentWarning';
2831
import LabsCTA from './LabsCall';
2932
import NudgeStudyTools from './NudgeStudyTools';
3033
import Page from './Page';
@@ -69,7 +72,7 @@ const OuterWrapper = styled.div`
6972
`;
7073

7174
// tslint:disable-next-line:variable-name
72-
const Content = ({mobileExpanded}: {mobileExpanded: boolean}) => <Layout>
75+
const Content = ({mobileExpanded, book}: {mobileExpanded: boolean; book: Book}) => <Layout>
7376
<ScrollOffset
7477
desktopOffset={
7578
bookBannerDesktopMiniHeight
@@ -96,6 +99,7 @@ const Content = ({mobileExpanded}: {mobileExpanded: boolean}) => <Layout>
9699
<Navigation />
97100
<ContentPane>
98101
<ContentNotifications mobileExpanded={mobileExpanded} />
102+
<ContentWarning book={book} />
99103
<Page>
100104
<PrevNextBar />
101105
<LabsCTA />
@@ -113,5 +117,6 @@ const Content = ({mobileExpanded}: {mobileExpanded: boolean}) => <Layout>
113117
export default connect(
114118
(state: AppState) => ({
115119
mobileExpanded: mobileToolbarOpen(state),
120+
book: bookSelector(state),
116121
})
117122
)(Content);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { renderToDom } from '../../../test/reactutils';
3+
import ContentWarning from './ContentWarning';
4+
import { useServices } from '../../context/Services';
5+
import { OSWebBook } from '../../../gateways/createOSWebLoader';
6+
import { BookWithOSWebData } from '../types';
7+
import { act } from 'react-dom/test-utils';
8+
import TestContainer from '../../../test/TestContainer';
9+
import ReactTestUtils from 'react-dom/test-utils';
10+
11+
const dummyBook = ({
12+
id: 'dummy-id',
13+
} as unknown) as BookWithOSWebData;
14+
15+
const dummyBookInfo = ({
16+
content_warning_text: 'some warning text',
17+
id: 72,
18+
} as unknown) as OSWebBook;
19+
20+
const services = {
21+
osWebLoader: {
22+
getBookFromId: jest.fn(() => Promise.resolve(dummyBookInfo)),
23+
},
24+
};
25+
26+
jest.mock('../../context/Services', () => ({
27+
...jest.requireActual('../../context/Services'),
28+
useServices: jest.fn(),
29+
}));
30+
31+
(useServices as jest.Mock).mockReturnValue(services);
32+
33+
describe('ContentWarning', () => {
34+
it('renders warning modal', async() => {
35+
renderToDom(<TestContainer><ContentWarning book={dummyBook} /></TestContainer>);
36+
37+
expect(services.osWebLoader.getBookFromId).toBeCalledWith(dummyBook.id);
38+
39+
await act(() => new Promise((resolve) => setTimeout(resolve, 1)));
40+
41+
const root = document?.body;
42+
const b = root?.querySelector('button');
43+
44+
expect(b).toBeTruthy();
45+
act(() => ReactTestUtils.Simulate.click(b!));
46+
expect(root?.querySelector('button')).toBeFalsy();
47+
});
48+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React from 'react';
2+
import { useServices } from '../../context/Services';
3+
import { OSWebBook } from '../../../gateways/createOSWebLoader';
4+
import { Book } from '../types';
5+
import { HTMLDivElement } from '@openstax/types/lib.dom';
6+
import styled from 'styled-components/macro';
7+
import Button from '../../components/Button';
8+
import Modal from './Modal';
9+
import theme from '../../theme';
10+
import Cookies from 'js-cookie';
11+
import { useTrapTabNavigation } from '../../reactUtils';
12+
13+
// tslint:disable-next-line
14+
const WarningDiv = styled.div`
15+
display: flex;
16+
flex-direction: column;
17+
gap: 4rem;
18+
justify-content: center;
19+
align-items: center;
20+
font-size: 1.8rem;
21+
padding: 2rem;
22+
min-height: 50vh;
23+
top: 25vh;
24+
z-index: 4;
25+
26+
> div {
27+
max-width: 80rem;
28+
}
29+
`;
30+
31+
function WarningDivWithTrap({
32+
text,
33+
dismiss,
34+
}: {
35+
text: string;
36+
dismiss: () => void;
37+
}) {
38+
const ref = React.useRef<HTMLDivElement>(null);
39+
40+
React.useEffect(() => ref.current?.focus(), []);
41+
42+
useTrapTabNavigation(ref);
43+
44+
return (
45+
<WarningDiv tabIndex='-1' ref={ref}>
46+
<div>{text}</div>
47+
<Button type='button' onClick={dismiss}>
48+
Ok
49+
</Button>
50+
</WarningDiv>
51+
);
52+
}
53+
54+
export default function ContentWarning({ book }: { book: Book }) {
55+
const services = useServices();
56+
const [bookInfo, setBookInfo] = React.useState<OSWebBook | undefined>();
57+
const cookieKey = `content-warning-${bookInfo?.id}`;
58+
const dismiss = React.useCallback(() => {
59+
// This is only called when bookInfo is populated
60+
Cookies.set(cookieKey, 'true', { expires: 28 });
61+
setBookInfo(undefined);
62+
}, [cookieKey]);
63+
64+
React.useEffect(() => {
65+
services.osWebLoader.getBookFromId(book.id).then(setBookInfo);
66+
}, [book, services]);
67+
68+
if (!bookInfo?.content_warning_text || Cookies.get(cookieKey)) {
69+
return null;
70+
}
71+
72+
return (
73+
<Modal
74+
ariaLabel='Content warning'
75+
tabIndex='-1'
76+
scrollLockProps={{
77+
mediumScreensOnly: false,
78+
overlay: true,
79+
zIndex: theme.zIndex.highlightSummaryPopup,
80+
}}
81+
>
82+
<WarningDivWithTrap
83+
text={bookInfo.content_warning_text}
84+
dismiss={dismiss}
85+
/>
86+
</Modal>
87+
);
88+
}

src/app/content/hooks/locationChange.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ describe('contentRouteHookBody', () => {
214214
polish_site_link: '',
215215
promote_image: null,
216216
publish_date: '2012-06-21',
217+
content_warning_text: '',
218+
id: 72,
217219
};
218220

219221
beforeEach(() => {

src/app/content/utils/seoUtils.spec.data.ts

+2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ export const mockOsWebBook: OSWebBook = {
7575
cnx_id: '',
7676
amazon_link: '',
7777
polish_site_link: '',
78+
content_warning_text: '',
79+
id: 72,
7880
};
7981

8082
export const emptyPage = {

src/app/reactUtils.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import theme from './theme';
77
import { assertDefined, assertDocument, assertWindow } from './utils';
88

99
export const useDrawFocus = <E extends HTMLElement = HTMLElement>() => {
10-
const ref = React.useRef<E | null>(null);
10+
const ref = React.useRef<E>(null);
1111

1212
React.useEffect(() => {
1313
if (ref && ref.current) {
@@ -99,10 +99,11 @@ export function createTrapTab(...elements: HTMLElement[]) {
9999
return;
100100
}
101101
const trapTab = createTrapTab(el);
102+
const document = assertDocument();
102103

103-
el.addEventListener('keydown', trapTab, true);
104+
document.body.addEventListener('keydown', trapTab, true);
104105

105-
return () => el.removeEventListener('keydown', trapTab, true);
106+
return () => document.body.removeEventListener('keydown', trapTab, true);
106107
},
107108
[ref, otherDep]
108109
);

src/gateways/createOSWebLoader.ts

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export interface OSWebBook {
2121
cnx_id: string;
2222
amazon_link: string;
2323
polish_site_link: string;
24+
content_warning_text: string | null;
25+
id: number;
2426
}
2527

2628
interface OSWebResponse {
@@ -41,6 +43,8 @@ export const fields = [
4143
'promote_image',
4244
'book_subjects',
4345
'book_categories',
46+
'content_warning_text',
47+
'id',
4448
].join(',');
4549

4650
interface Options {

src/setupProxy.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* this file is shared between webpack-dev-server and the pre-renderer
33
*/
44
const url = require('url');
5-
const util = require('util');
65
const fs = require('fs');
76
const path = require('path');
87
const proxy = require('http-proxy-middleware');
@@ -32,7 +31,7 @@ const { default: createOSWebLoader } = require('./gateways/createOSWebLoader');
3231
const archiveLoader = createArchiveLoader({
3332
archivePrefix: ARCHIVE_URL
3433
});
35-
const osWebLoader = createOSWebLoader(`${ARCHIVE_URL}${REACT_APP_OS_WEB_API_URL}`);
34+
const osWebLoader = createOSWebLoader(`${OS_WEB_URL}${REACT_APP_OS_WEB_API_URL}`);
3635

3736
const archivePaths = [
3837
'/apps/archive',
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"cover_color": "blue",
1616
"authors": [{"value": {"name": "Bam Bammerson"}}],
1717
"publish_date": "2012-06-21",
18-
"book_state": "live"
18+
"book_state": "live",
19+
"content_warning_text": "sensitive material is covered",
20+
"id": 72
1921
}
2022
]
2123
}

src/test/mocks/osWebLoader.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export const mockCmsBook: OSWebBook = {
1414
},
1515
promote_image: null,
1616
publish_date: '2012-06-21',
17+
content_warning_text: '',
18+
id: 72,
1719
};
1820

1921
export default () => ({

0 commit comments

Comments
 (0)