chore: update to paragon 17.0.0

- Drop our custom breakpoints (identical to paragon's)
- Drop our custom useWindowSize (and adapt to paragon's version
  not providing a size initially at component mount)
- Drop our dependency on react-responsive
- Drop our dependency on react-break
This commit is contained in:
Michael Terry
2022-02-18 13:55:19 -05:00
committed by Michael Terry
parent c25ec8f1ae
commit cc8ee33dcd
21 changed files with 138 additions and 257 deletions

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { useDispatch } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { breakpoints, useWindowSize } from '@edx/paragon';
import { AlertList } from '../../generic/user-messages';
@@ -14,7 +15,6 @@ import CourseBreadcrumbs from './CourseBreadcrumbs';
import NotificationTrigger from './NotificationTrigger';
import { useModel } from '../../generic/model-store';
import useWindowSize, { responsiveBreakpoints } from '../../generic/tabs/useWindowSize';
import { getLocalStorage, setLocalStorage } from '../../data/localStorage';
import { getSessionStorage, setSessionStorage } from '../../data/sessionStorage';
@@ -28,6 +28,7 @@ function Course({
nextSequenceHandler,
previousSequenceHandler,
unitNavigationHandler,
windowWidth,
}) {
const course = useModel('coursewareMeta', courseId);
const sequence = useModel('sequences', sequenceId);
@@ -59,8 +60,8 @@ function Course({
const daysPerWeek = courseGoals?.selectedGoal?.daysPerWeek;
// Responsive breakpoints for showing the notification button/tray
const shouldDisplayNotificationTriggerInCourse = useWindowSize().width >= responsiveBreakpoints.small.minWidth;
const shouldDisplayNotificationTrayOpenOnLoad = useWindowSize().width > responsiveBreakpoints.medium.minWidth;
const shouldDisplayNotificationTriggerInCourse = windowWidth >= breakpoints.small.minWidth;
const shouldDisplayNotificationTrayOpenOnLoad = windowWidth > breakpoints.medium.minWidth;
// Course specific notification tray open/closed persistance by browser session
if (!getSessionStorage(`notificationTrayStatus.${courseId}`)) {
@@ -177,6 +178,7 @@ Course.propTypes = {
nextSequenceHandler: PropTypes.func.isRequired,
previousSequenceHandler: PropTypes.func.isRequired,
unitNavigationHandler: PropTypes.func.isRequired,
windowWidth: PropTypes.number.isRequired,
};
Course.defaultProps = {
@@ -185,4 +187,18 @@ Course.defaultProps = {
unitId: null,
};
export default Course;
function CourseWrapper(props) {
// useWindowSize initially returns an undefined width intentionally at first.
// See https://www.joshwcomeau.com/react/the-perils-of-rehydration/ for why.
// But <Course> has some tricky window-size-dependent, session-storage-setting logic and React would yell at us if
// we exited that component early, before hitting all the useState() calls.
// So just skip all that until we have a window size available.
const windowWidth = useWindowSize().width;
if (windowWidth === undefined) {
return null;
}
return <Course {...props} windowWidth={windowWidth} />;
}
export default CourseWrapper;

View File

@@ -1,17 +1,15 @@
import React from 'react';
import { Factory } from 'rosie';
import { breakpoints } from '@edx/paragon';
import {
loadUnit, render, screen, waitFor, getByRole, initializeTestStore, fireEvent,
} from '../../setupTest';
import Course from './Course';
import { handleNextSectionCelebration } from './celebration';
import * as celebrationUtils from './celebration/utils';
import useWindowSize from '../../generic/tabs/useWindowSize';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('./NotificationTray', () => () => <div data-testid="NotificationTray" />);
jest.mock('../../generic/tabs/useWindowSize');
useWindowSize.mockReturnValue({ width: 1200 });
const recordFirstSectionCelebration = jest.fn();
celebrationUtils.recordFirstSectionCelebration = recordFirstSectionCelebration;
@@ -37,6 +35,7 @@ describe('Course', () => {
});
getItemSpy = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'getItem');
setItemSpy = jest.spyOn(Object.getPrototypeOf(window.sessionStorage), 'setItem');
global.innerWidth = breakpoints.extraLarge.minWidth;
});
afterAll(() => {
@@ -104,7 +103,6 @@ describe('Course', () => {
});
it('displays notification trigger and toggles active class on click', async () => {
useWindowSize.mockReturnValue({ width: 1200 });
render(<Course {...mockData} />);
const notificationTrigger = screen.getByRole('button', { name: /Show notification tray/i });

View File

@@ -3,12 +3,16 @@ import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Icon, IconButton } from '@edx/paragon';
import {
breakpoints,
Icon,
IconButton,
useWindowSize,
} from '@edx/paragon';
import { ArrowBackIos, Close } from '@edx/paragon/icons';
import messages from './messages';
import { useModel } from '../../generic/model-store';
import useWindowSize, { responsiveBreakpoints } from '../../generic/tabs/useWindowSize';
import UpgradeNotification from '../../generic/upgrade-notification/UpgradeNotification';
function NotificationTray({
@@ -30,7 +34,7 @@ function NotificationTray({
verifiedMode,
} = course;
const shouldDisplayFullScreen = useWindowSize().width < responsiveBreakpoints.large.minWidth;
const shouldDisplayFullScreen = useWindowSize().width < breakpoints.large.minWidth;
// After three seconds, update notificationSeen (to hide red dot)
useEffect(() => { setTimeout(onNotificationSeen, 3000); }, []);

View File

@@ -3,6 +3,7 @@ import { Factory } from 'rosie';
import MockAdapter from 'axios-mock-adapter';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { breakpoints } from '@edx/paragon';
import { fetchCourse } from '../data';
import {
@@ -11,10 +12,8 @@ import {
import initializeStore from '../../store';
import { appendBrowserTimezoneToUrl, executeThunk } from '../../utils';
import NotificationTray from './NotificationTray';
import useWindowSize from '../../generic/tabs/useWindowSize';
initializeMockApp();
jest.mock('../../generic/tabs/useWindowSize');
jest.mock('@edx/frontend-platform/analytics');
describe('NotificationTray', () => {
@@ -37,6 +36,7 @@ describe('NotificationTray', () => {
}
beforeEach(async () => {
global.innerWidth = breakpoints.large.minWidth;
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(courseMetadataUrl).reply(200, defaultMetadata);
@@ -46,7 +46,7 @@ describe('NotificationTray', () => {
});
it('renders notification tray and close tray button', async () => {
useWindowSize.mockReturnValue({ width: 1200 });
global.innerWidth = breakpoints.extraLarge.minWidth;
const toggleNotificationTray = jest.fn();
const testData = {
...mockData,
@@ -81,7 +81,7 @@ describe('NotificationTray', () => {
});
it('renders notification tray with full screen "Back to course" at responsive view', async () => {
useWindowSize.mockReturnValue({ width: 991 });
global.innerWidth = breakpoints.medium.maxWidth;
const toggleNotificationTray = jest.fn();
const testData = {
...mockData,

View File

@@ -1,8 +1,13 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { ActionRow, Button, StandardModal } from '@edx/paragon';
import { layoutGenerator } from 'react-break';
import {
ActionRow,
breakpoints,
Button,
StandardModal,
useWindowSize,
} from '@edx/paragon';
import ClapsMobile from './assets/claps_280x201.gif';
import ClapsTablet from './assets/claps_456x328.gif';
@@ -15,14 +20,7 @@ function CelebrationModal({
courseId, intl, isOpen, onClose, ...rest
}) {
const { org } = useModel('coursewareMeta', courseId);
const layout = layoutGenerator({
mobile: 0,
tablet: 400,
});
const OnMobile = layout.is('mobile');
const OnAtLeastTablet = layout.isAtLeast('tablet');
const wideScreen = useWindowSize().width >= breakpoints.small.minWidth;
useEffect(() => {
if (isOpen) {
@@ -47,12 +45,8 @@ function CelebrationModal({
>
<>
<p className="text-center">{intl.formatMessage(messages.completed)}</p>
<OnMobile>
<img src={ClapsMobile} alt="" className="img-fluid" />
</OnMobile>
<OnAtLeastTablet>
<img src={ClapsTablet} alt="" className="img-fluid w-100" />
</OnAtLeastTablet>
{!wideScreen && <img src={ClapsMobile} alt="" className="img-fluid" />}
{wideScreen && <img src={ClapsTablet} alt="" className="img-fluid w-100" />}
<p className="mt-3 text-center">
<strong>{intl.formatMessage(messages.earned)}</strong> {intl.formatMessage(messages.share)}
</p>

View File

@@ -5,10 +5,15 @@ import { faLinkedinIn } from '@fortawesome/free-brands-svg-icons';
import {
FormattedDate, FormattedMessage, injectIntl, intlShape,
} from '@edx/frontend-platform/i18n';
import { layoutGenerator } from 'react-break';
import { Helmet } from 'react-helmet';
import { useDispatch, useSelector } from 'react-redux';
import { Alert, Button, Hyperlink } from '@edx/paragon';
import {
Alert,
breakpoints,
Button,
Hyperlink,
useWindowSize,
} from '@edx/paragon';
import { CheckCircle } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
@@ -32,14 +37,7 @@ import CourseRecommendations from './CourseRecommendations';
const LINKEDIN_BLUE = '#2867B2';
function CourseCelebration({ intl }) {
const layout = layoutGenerator({
mobile: 0,
tablet: 768,
});
const OnMobile = layout.is('mobile');
const OnAtLeastTablet = layout.isAtLeast('tablet');
const wideScreen = useWindowSize().width >= breakpoints.medium.minWidth;
const { courseId } = useSelector(state => state.courseware);
const dispatch = useDispatch();
const {
@@ -273,21 +271,21 @@ function CourseCelebration({ intl }) {
/>
</div>
<div className="col-12 mt-3 mb-4 px-0 px-md-5 text-center">
<OnMobile>
{!wideScreen && (
<img
src={CelebrationMobile}
alt={`${intl.formatMessage(messages.congratulationsImage)}`}
className="img-fluid"
/>
</OnMobile>
<OnAtLeastTablet>
)}
{wideScreen && (
<img
src={CelebrationDesktop}
alt={`${intl.formatMessage(messages.congratulationsImage)}`}
className="img-fluid"
style={{ width: '36rem' }}
/>
</OnAtLeastTablet>
)}
</div>
<div className="col-12 px-0 px-md-5">
{certHeader && (

View File

@@ -12,10 +12,10 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';
import { history } from '@edx/frontend-platform';
import SequenceExamWrapper from '@edx/frontend-lib-special-exams';
import { breakpoints, useWindowSize } from '@edx/paragon';
import PageLoading from '../../../generic/PageLoading';
import { UserMessagesContext, ALERT_TYPES } from '../../../generic/user-messages';
import useWindowSize, { responsiveBreakpoints } from '../../../generic/tabs/useWindowSize';
import { useModel } from '../../../generic/model-store';
import CourseLicense from '../course-license';
@@ -53,7 +53,7 @@ function Sequence({
const unit = useModel('units', unitId);
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
const sequenceMightBeUnit = useSelector(state => state.courseware.sequenceMightBeUnit);
const shouldDisplayNotificationTriggerInSequence = useWindowSize().width < responsiveBreakpoints.small.minWidth;
const shouldDisplayNotificationTriggerInSequence = useWindowSize().width < breakpoints.small.minWidth;
const handleNext = () => {
const nextIndex = sequence.unitIds.indexOf(unitId) + 1;

View File

@@ -1,16 +1,14 @@
import React from 'react';
import { Factory } from 'rosie';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { breakpoints } from '@edx/paragon';
import {
loadUnit, render, screen, fireEvent, waitFor, initializeTestStore,
} from '../../../setupTest';
import Sequence from './Sequence';
import { fetchSequenceFailure } from '../../data/slice';
import useWindowSize from '../../../generic/tabs/useWindowSize';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('../../../generic/tabs/useWindowSize');
useWindowSize.mockReturnValue({ width: 1200 });
describe('Sequence', () => {
let mockData;
@@ -36,6 +34,10 @@ describe('Sequence', () => {
};
});
beforeEach(() => {
global.innerWidth = breakpoints.extraLarge.minWidth;
});
it('renders correctly without data', async () => {
const testStore = await initializeTestStore({ excludeFetchCourse: true, excludeFetchSequence: true }, false);
render(<Sequence {...mockData} {...{ unitId: undefined, sequenceId: undefined }} />, { store: testStore });
@@ -406,7 +408,7 @@ describe('Sequence', () => {
});
it('does not render notification tray in sequence by default if in responsive view', async () => {
useWindowSize.mockReturnValue({ width: 991 });
global.innerWidth = breakpoints.medium.maxWidth;
const { container } = render(<Sequence {...mockData} />);
// unable to test the absence of 'Notifications' by finding it by text, using the class of the tray instead:
expect(container).not.toHaveClass('notification-tray-container');

View File

@@ -3,12 +3,11 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import { Alert, breakpoints, useWindowSize } from '@edx/paragon';
import { Locked } from '@edx/paragon/icons';
import messages from './messages';
import certificateLocked from '../../../../generic/assets/edX_locked_certificate.png';
import { useModel } from '../../../../generic/model-store';
import useWindowSize, { responsiveBreakpoints } from '../../../../generic/tabs/useWindowSize';
import { UpgradeButton } from '../../../../generic/upgrade-button';
import {
VerifiedCertBullet,
@@ -31,15 +30,14 @@ function LockPaywall({
// the following variables are set and used for resposive layout to work with
// whether the NotificationTray is open or not and if there's an offer with longer text
const shouldDisplayBulletPointsBelowCertificate = useWindowSize().width
<= responsiveBreakpoints.large.minWidth;
const shouldDisplayGatedContentOneColumn = useWindowSize().width <= responsiveBreakpoints.extraLarge.minWidth
const shouldDisplayBulletPointsBelowCertificate = useWindowSize().width <= breakpoints.large.minWidth;
const shouldDisplayGatedContentOneColumn = useWindowSize().width <= breakpoints.extraLarge.minWidth
&& notificationTrayVisible;
const shouldDisplayGatedContentTwoColumns = useWindowSize().width < responsiveBreakpoints.large.minWidth
const shouldDisplayGatedContentTwoColumns = useWindowSize().width < breakpoints.large.minWidth
&& notificationTrayVisible;
const shouldDisplayGatedContentTwoColumnsHalf = useWindowSize().width <= responsiveBreakpoints.large.minWidth
const shouldDisplayGatedContentTwoColumnsHalf = useWindowSize().width <= breakpoints.large.minWidth
&& !notificationTrayVisible;
const shouldWrapTextOnButton = useWindowSize().width > responsiveBreakpoints.extraSmall.minWidth;
const shouldWrapTextOnButton = useWindowSize().width > breakpoints.extraSmall.minWidth;
if (!verifiedMode) {
return null;

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from '@edx/paragon';
import { breakpoints, Button, useWindowSize } from '@edx/paragon';
import { ChevronLeft, ChevronRight } from '@edx/paragon/icons';
import classNames from 'classnames';
import {
@@ -17,7 +17,6 @@ import SequenceNavigationTabs from './SequenceNavigationTabs';
import { useSequenceNavigationMetadata } from './hooks';
import { useModel } from '../../../../generic/model-store';
import { LOADED } from '../../../data/slice';
import useWindowSize, { responsiveBreakpoints } from '../../../../generic/tabs/useWindowSize';
import messages from './messages';
/** [MM-P2P] Experiment */
@@ -44,7 +43,7 @@ function SequenceNavigation({
sequence.gatedContent !== undefined && sequence.gatedContent.gated
) : undefined;
const shouldDisplayNotificationTriggerInSequence = useWindowSize().width < responsiveBreakpoints.small.minWidth;
const shouldDisplayNotificationTriggerInSequence = useWindowSize().width < breakpoints.small.minWidth;
const renderUnitButtons = () => {
if (isLocked) {