From 17f426af2819734fa86c849b73d13807cf349c6f Mon Sep 17 00:00:00 2001
From: Cyril Nxumalo <80963114+zwidekalanga@users.noreply.github.com>
Date: Mon, 26 Aug 2024 16:10:35 +0200
Subject: [PATCH] chore: Migrate deprecated Paragon components (#1158)
---
.../dashboard/SubscriptionExpirationModal.jsx | 136 +++++++++---------
.../email-settings/EmailSettingsModal.jsx | 80 +++++------
.../tests/EmailSettingsModal.test.jsx | 1 +
.../mark-complete-modal/MarkCompleteModal.jsx | 44 +++---
.../tests/MarkCompleteModal.test.jsx | 2 +-
.../MoveToInProgressModal.jsx | 44 +++---
.../styles/_EmailSettingsModal.scss | 5 +
.../course-cards/styles/index.scss | 2 +
.../tests/BaseCourseCard.test.jsx | 7 +-
.../tests/CourseEnrollments.test.jsx | 51 ++++++-
.../tests/enrollment-testutils.js | 14 +-
.../dashboard/tests/DashboardPage.test.jsx | 6 +-
.../IntegrationWarningModal.jsx | 22 ++-
src/styles/index.scss | 2 +-
14 files changed, 234 insertions(+), 182 deletions(-)
create mode 100644 src/components/dashboard/main-content/course-enrollments/course-cards/styles/_EmailSettingsModal.scss
create mode 100644 src/components/dashboard/main-content/course-enrollments/course-cards/styles/index.scss
diff --git a/src/components/dashboard/SubscriptionExpirationModal.jsx b/src/components/dashboard/SubscriptionExpirationModal.jsx
index e2ee689140..4977f1bafc 100644
--- a/src/components/dashboard/SubscriptionExpirationModal.jsx
+++ b/src/components/dashboard/SubscriptionExpirationModal.jsx
@@ -1,5 +1,7 @@
import React, { useContext } from 'react';
-import { MailtoLink, Modal } from '@openedx/paragon';
+import {
+ ActionRow, AlertModal, Button, MailtoLink, useToggle,
+} from '@openedx/paragon';
import { AppContext } from '@edx/frontend-platform/react';
import { useIntl } from '@edx/frontend-platform/i18n';
@@ -23,6 +25,7 @@ const SubscriptionExpirationModal = () => {
} = useContext(AppContext);
const intl = useIntl();
+ const [isOpen, , close] = useToggle(true);
const { data: enterpriseCustomer } = useEnterpriseCustomer();
const { data: subscriptions } = useSubscriptions();
const { subscriptionPlan, subscriptionLicense } = subscriptions;
@@ -33,17 +36,6 @@ const SubscriptionExpirationModal = () => {
isCurrent,
} = subscriptionPlan;
- const renderTitle = () => {
- if (isCurrent) {
- return (
- {SUBSCRIPTION_EXPIRING_MODAL_TITLE}
- );
- }
- return (
- {SUBSCRIPTION_EXPIRED_MODAL_TITLE}
- );
- };
-
const renderContactText = () => {
const contactText = 'contact your learning manager';
const email = getContactEmail(enterpriseCustomer);
@@ -92,43 +84,10 @@ const SubscriptionExpirationModal = () => {
);
};
- const renderBody = () => (
- <>
-
- Your organization's access to your current subscription is expiring in
- {timeUntilExpiration()} After it expires you will only have audit access to your courses.
-
-
- If you are currently taking courses, plan your learning accordingly. You should also take
- this time to {renderCertificateText()}.
-
-
- If you think this is an error or need help, {renderContactText()}.
-
-
- Access expires on {i18nFormatTimestamp({ intl, timestamp: expirationDate })}.
-
- >
- );
-
- const renderExpiredBody = () => (
- <>
-
- Your organization's access to your subscription has expired. You will only have audit
- access to the courses you were enrolled in with your subscription (courses from vouchers
- will still be fully accessible).
-
-
- You can also {renderCertificateText()}.
-
-
- If you think this is an error or need help, {renderContactText()}.
-
-
- Access expired on {dayjs(expirationDate).format('MMM D, YYYY')}.
-
- >
- );
+ const handleSubscriptionExpiredModalDismissal = () => {
+ close();
+ global.localStorage.setItem(EXPIRED_SUBSCRIPTION_MODAL_LOCALSTORAGE_KEY(subscriptionLicense), 'true');
+ };
const seenExpiredSubscriptionModal = !!global.localStorage.getItem(
EXPIRED_SUBSCRIPTION_MODAL_LOCALSTORAGE_KEY(subscriptionLicense),
@@ -139,18 +98,33 @@ const SubscriptionExpirationModal = () => {
return null;
}
return (
- {
- global.localStorage.setItem(EXPIRED_SUBSCRIPTION_MODAL_LOCALSTORAGE_KEY(subscriptionLicense), 'true');
- }}
- open
+
+ footerNode={(
+
+ OK
+
+ )}
+ hasCloseButton
+ >
+
+ Your organization's access to your subscription has expired. You will only have audit
+ access to the courses you were enrolled in with your subscription (courses from vouchers
+ will still be fully accessible).
+
+
+ You can also {renderCertificateText()}.
+
+
+ If you think this is an error or need help, {renderContactText()}.
+
+
+ Access expired on {dayjs(expirationDate).format('MMM D, YYYY')}.
+
+
);
}
@@ -178,20 +152,40 @@ const SubscriptionExpirationModal = () => {
return null;
}
+ const handleSubscriptionExpiringModalDismissal = () => {
+ close();
+ global.localStorage.setItem(expirationModalLocalStorageName, 'true');
+ };
+
return (
- {
- global.localStorage.setItem(expirationModalLocalStorageName, 'true');
- }}
- open
+ isOpen={isOpen}
data-testid="expiration-modal"
- />
+ footerNode={(
+
+ OK
+
+ )}
+ hasCloseButton
+ >
+
+ Your organization's access to your current subscription is expiring in
+ {timeUntilExpiration()} After it expires you will only have audit access to your courses.
+
+
+ If you are currently taking courses, plan your learning accordingly. You should also take
+ this time to {renderCertificateText()}.
+
+
+ If you think this is an error or need help, {renderContactText()}.
+
+
+ Access expires on {i18nFormatTimestamp({ intl, timestamp: expirationDate })}.
+
+
);
};
diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/email-settings/EmailSettingsModal.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/email-settings/EmailSettingsModal.jsx
index a86aed7926..0b23b63ef9 100644
--- a/src/components/dashboard/main-content/course-enrollments/course-cards/email-settings/EmailSettingsModal.jsx
+++ b/src/components/dashboard/main-content/course-enrollments/course-cards/email-settings/EmailSettingsModal.jsx
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
- Input, Modal, Alert, StatefulButton,
+ Form, Alert, StatefulButton, ActionRow, Button, StandardModal,
} from '@openedx/paragon';
import { Error } from '@openedx/paragon/icons';
@@ -108,51 +108,47 @@ class EmailSettingsModal extends Component {
const {
error, hasEmailsEnabled, isSubmitting,
} = this.state;
- const { open, courseRunId } = this.props;
+ const { open } = this.props;
return (
-
- {error && (
-
- An error occurred while saving your email settings. Please try again.
-
- )}
-
-
- {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
-
- Receive course emails such as reminders, schedule updates, and
- other critical announcements.
-
-
- >
- )}
+ isOpen={open}
onClose={this.handleOnClose}
- buttons={[
- ,
- ]}
- open={open}
- />
+ hasCloseButton
+ isFullscreenOnMobile
+ footerNode={(
+
+ Close
+
+
+ )}
+ >
+ {error && (
+
+ An error occurred while saving your email settings. Please try again.
+
+ )}
+
+
+ Receive course emails such as reminders, schedule updates, and other critical announcements.
+
+
+
);
}
}
diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/email-settings/tests/EmailSettingsModal.test.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/email-settings/tests/EmailSettingsModal.test.jsx
index 13822c9d47..459c56d25d 100644
--- a/src/components/dashboard/main-content/course-enrollments/course-cards/email-settings/tests/EmailSettingsModal.test.jsx
+++ b/src/components/dashboard/main-content/course-enrollments/course-cards/email-settings/tests/EmailSettingsModal.test.jsx
@@ -15,6 +15,7 @@ describe(' ', () => {
{}}
courseRunId="my+course+key"
+ open
/>
));
diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/mark-complete-modal/MarkCompleteModal.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/mark-complete-modal/MarkCompleteModal.jsx
index e457576567..15bcd617b5 100644
--- a/src/components/dashboard/main-content/course-enrollments/course-cards/mark-complete-modal/MarkCompleteModal.jsx
+++ b/src/components/dashboard/main-content/course-enrollments/course-cards/mark-complete-modal/MarkCompleteModal.jsx
@@ -1,6 +1,8 @@
import React, { useState, useMemo } from 'react';
import PropTypes from 'prop-types';
-import { Modal, StatefulButton } from '@openedx/paragon';
+import {
+ ActionRow, Button, StandardModal, StatefulButton,
+} from '@openedx/paragon';
import { camelCaseObject } from '@edx/frontend-platform';
import MarkCompleteModalContext from './MarkCompleteModalContext';
@@ -73,26 +75,30 @@ const MarkCompleteModal = ({
return (
- }
- buttons={[
- ,
- ]}
- open={isOpen && !confirmSuccessful}
+ isOpen={isOpen && !confirmSuccessful}
onClose={handleModalOnClose}
- closeText="Cancel"
- />
+ hasCloseButton
+ isFullscreenOnMobile
+ footerNode={(
+
+ Cancel
+
+
+ )}
+ >
+
+
);
};
diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/mark-complete-modal/tests/MarkCompleteModal.test.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/mark-complete-modal/tests/MarkCompleteModal.test.jsx
index b01b930fbb..19df155ed5 100644
--- a/src/components/dashboard/main-content/course-enrollments/course-cards/mark-complete-modal/tests/MarkCompleteModal.test.jsx
+++ b/src/components/dashboard/main-content/course-enrollments/course-cards/mark-complete-modal/tests/MarkCompleteModal.test.jsx
@@ -85,7 +85,7 @@ describe(' ', () => {
/>
));
act(() => {
- wrapper.find('.modal-footer button.btn-link').hostNodes().simulate('click');
+ wrapper.find('[data-testid="mark-complete-modal-cancel-btn"]').hostNodes().simulate('click');
});
expect(mockOnClose).toBeCalledTimes(1);
});
diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/move-to-in-progress-modal/MoveToInProgressModal.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/move-to-in-progress-modal/MoveToInProgressModal.jsx
index e80fa79f76..f91cab4734 100644
--- a/src/components/dashboard/main-content/course-enrollments/course-cards/move-to-in-progress-modal/MoveToInProgressModal.jsx
+++ b/src/components/dashboard/main-content/course-enrollments/course-cards/move-to-in-progress-modal/MoveToInProgressModal.jsx
@@ -1,6 +1,8 @@
import React, { useState, useMemo } from 'react';
import PropTypes from 'prop-types';
-import { Modal, StatefulButton } from '@openedx/paragon';
+import {
+ ActionRow, Button, StandardModal, StatefulButton,
+} from '@openedx/paragon';
import { camelCaseObject } from '@edx/frontend-platform';
import MoveToInProgressModalContext from './MoveToInProgressModalContext';
@@ -67,26 +69,30 @@ const MoveToInProgressModal = ({
return (
- }
- buttons={[
- ,
- ]}
- open={isOpen && !confirmSuccessful}
+ isOpen={isOpen && !confirmSuccessful}
onClose={handleModalOnClose}
- closeText="Cancel"
- />
+ hasCloseButton
+ isFullscreenOnMobile
+ footerNode={(
+
+ Cancel
+
+
+ )}
+ >
+
+
);
};
diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/styles/_EmailSettingsModal.scss b/src/components/dashboard/main-content/course-enrollments/course-cards/styles/_EmailSettingsModal.scss
new file mode 100644
index 0000000000..6b6780fe43
--- /dev/null
+++ b/src/components/dashboard/main-content/course-enrollments/course-cards/styles/_EmailSettingsModal.scss
@@ -0,0 +1,5 @@
+.email-checkbox {
+ .pgn__form-checkbox-input {
+ flex-shrink: 0 !important;
+ }
+}
diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/styles/index.scss b/src/components/dashboard/main-content/course-enrollments/course-cards/styles/index.scss
new file mode 100644
index 0000000000..0d56f9921e
--- /dev/null
+++ b/src/components/dashboard/main-content/course-enrollments/course-cards/styles/index.scss
@@ -0,0 +1,2 @@
+@import "./CourseCard";
+@import "./EmailSettingsModal";
diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/tests/BaseCourseCard.test.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/tests/BaseCourseCard.test.jsx
index bcc5a64a30..8f94880b00 100644
--- a/src/components/dashboard/main-content/course-enrollments/course-cards/tests/BaseCourseCard.test.jsx
+++ b/src/components/dashboard/main-content/course-enrollments/course-cards/tests/BaseCourseCard.test.jsx
@@ -78,11 +78,8 @@ describe(' ', () => {
});
it('handles email settings modal close/cancel', async () => {
- userEvent.click(screen.getByTestId('modal-footer-btn', { name: 'Close' }));
- await waitFor(() => {
- const dialogElement = screen.getByTestId('modal');
- expect(dialogElement).not.toHaveClass('show');
- });
+ userEvent.click(screen.getByTestId('email-setting-modal-close-btn', { name: 'Close' }));
+ expect(await screen.queryByRole('dialog')).not.toBeInTheDocument();
});
});
diff --git a/src/components/dashboard/main-content/course-enrollments/tests/CourseEnrollments.test.jsx b/src/components/dashboard/main-content/course-enrollments/tests/CourseEnrollments.test.jsx
index e038b7dd97..88f27e3121 100644
--- a/src/components/dashboard/main-content/course-enrollments/tests/CourseEnrollments.test.jsx
+++ b/src/components/dashboard/main-content/course-enrollments/tests/CourseEnrollments.test.jsx
@@ -69,7 +69,13 @@ jest.mock('react-router-dom', () => ({
const inProgCourseRun = createCourseEnrollmentWithStatus({ status: COURSE_STATUSES.inProgress });
const upcomingCourseRun = createCourseEnrollmentWithStatus({ status: COURSE_STATUSES.upcoming });
const completedCourseRun = createCourseEnrollmentWithStatus({ status: COURSE_STATUSES.completed });
-const savedForLaterCourseRun = createCourseEnrollmentWithStatus({ status: COURSE_STATUSES.savedForLater });
+const savedForLaterCourseRun = createCourseEnrollmentWithStatus(
+ {
+ status: COURSE_STATUSES.savedForLater,
+ start: dayjs().subtract(1, 'day').toISOString(),
+ end: dayjs().add(1, 'day').toISOString(),
+ },
+);
const cancelledAssignedCourseRun = createCourseEnrollmentWithStatus({
status: COURSE_STATUSES.assigned,
isCancelledAssignment: true,
@@ -88,7 +94,7 @@ const assignmentData = {
contentKey: 'test-contentKey',
contentTitle: 'test-title',
contentMetadata: {
- endDate: '2018-08-18T05:00:00Z',
+ endDate: '2024-08-18T05:00:00Z',
startDate: '2017-02-05T05:00:00Z',
courseType: 'test-course-type',
enrollByDate: '2017-02-05T05:00:00Z',
@@ -107,10 +113,10 @@ jest.mock('../../../../app/data', () => ({
const mockAuthenticatedUser = authenticatedUserFactory();
const mockEnterpriseCustomer = enterpriseCustomerFactory();
-const CourseEnrollmentsWrapper = () => (
+const CourseEnrollmentsWrapper = ({ appContextProps = {} }) => (
-
+
@@ -347,6 +353,16 @@ describe('Course enrollments', () => {
},
});
renderWithRouter( );
+ const { title } = savedForLaterCourseRun;
+
+ // Open the dropdown menu for the course
+ userEvent.click(screen.getByLabelText(`course settings for ${title}`));
+
+ // Wait for the dropdown to be visible and use getByRole with name to find the correct menuitem
+ const moveToInProgressMenuItem = await screen.findByRole('menuitem', { name: /Move to In Progress/i });
+ userEvent.click(moveToInProgressMenuItem);
+
+ // Clicks the "Move course to In Progress" button, moving the course back to in progress status
userEvent.click(screen.getByRole('button', { name: MARK_MOVE_TO_IN_PROGRESS_DEFAULT_LABEL }));
// TODO This test only validates 'half way', we ideally want to update it to
@@ -360,18 +376,41 @@ describe('Course enrollments', () => {
});
it('generates course status update on move to saved for later action', async () => {
+ const appContext = {
+ courseCards: {
+ 'in-progress': {
+ settingsMenu: {
+ hasMarkComplete: true,
+ },
+ },
+ },
+ };
useLocation.mockReturnValue({
state: {
markedSavedForLaterSuccess: true,
markedInProgressSuccess: false,
},
});
- renderWithRouter( );
+ renderWithRouter( );
+ const { title } = inProgCourseRun;
+
+ // Open the dropdown menu for the course
+ userEvent.click(screen.getByLabelText(`course settings for ${title}`));
+
+ // Wait for the dropdown to be visible and use getByRole with name to find the correct menuitem
+ const saveForLaterMenuItem = await screen.findByRole('menuitem', { name: /Save course for later/i });
+ userEvent.click(saveForLaterMenuItem);
+
+ // Clicks the "Save course for later" button, identified by its role
userEvent.click(screen.getByRole('button', { name: MARK_SAVED_FOR_LATER_DEFAULT_LABEL }));
+
+ // Verify the course status update request is made
await waitFor(() => {
expect(updateCourseCompleteStatusRequest).toHaveBeenCalledTimes(1);
});
- expect(await screen.findByText('Your course was saved for later.'));
+
+ // Ensure the success message is displayed
+ expect(await screen.findByText('Your course was saved for later.')).toBeInTheDocument();
});
it('renders in progress, upcoming, and requested course enrollments in the same section', async () => {
diff --git a/src/components/dashboard/main-content/course-enrollments/tests/enrollment-testutils.js b/src/components/dashboard/main-content/course-enrollments/tests/enrollment-testutils.js
index 6f05e4d577..a07d30f072 100644
--- a/src/components/dashboard/main-content/course-enrollments/tests/enrollment-testutils.js
+++ b/src/components/dashboard/main-content/course-enrollments/tests/enrollment-testutils.js
@@ -4,7 +4,15 @@ import { COURSE_STATUSES } from '../data/constants';
* Generate an enrollment with given status.
* Can be used as a baseline to override and generate new courseRuns.
*/
-const createCourseEnrollmentWithStatus = ({ status = COURSE_STATUSES.inProgress, mode = 'verified', isCancelledAssignment = false }) => {
+const createCourseEnrollmentWithStatus = (
+ {
+ status = COURSE_STATUSES.inProgress,
+ mode = 'verified',
+ isCancelledAssignment = false,
+ start = '2017-02-05T05:00:00Z',
+ end = '2018-08-18T05:00:00Z',
+ },
+) => {
const randomNumber = Math.random();
return ({
courseRunId: `$course-v1:edX+DemoX+Demo_Course-${randomNumber}`,
@@ -13,8 +21,8 @@ const createCourseEnrollmentWithStatus = ({ status = COURSE_STATUSES.inProgress,
title: `edX Demonstration Course-${randomNumber}`,
notifications: [],
created: '2017-02-05T05:00:00Z',
- startDate: '2017-02-05T05:00:00Z',
- endDate: '2018-08-18T05:00:00Z',
+ startDate: start,
+ endDate: end,
hasEmailsEnabled: true,
isRevoked: false,
mode,
diff --git a/src/components/dashboard/tests/DashboardPage.test.jsx b/src/components/dashboard/tests/DashboardPage.test.jsx
index a5efaf5911..2feecb4c87 100644
--- a/src/components/dashboard/tests/DashboardPage.test.jsx
+++ b/src/components/dashboard/tests/DashboardPage.test.jsx
@@ -533,8 +533,8 @@ describe(' ', () => {
expect(screen.queryByText(SUBSCRIPTION_EXPIRING_MODAL_TITLE)).toBeFalsy();
expect(screen.queryByText(SUBSCRIPTION_EXPIRED_MODAL_TITLE)).toBeTruthy();
- userEvent.click(screen.getByTestId('modal-footer-btn'));
- await waitFor(() => expect(screen.queryByText(SUBSCRIPTION_EXPIRED_MODAL_TITLE)).toBeTruthy());
+ userEvent.click(screen.getByTestId('subscription-expiration-button'));
+ await waitFor(() => expect(screen.queryByText(SUBSCRIPTION_EXPIRED_MODAL_TITLE)).toBeFalsy());
const expiredModalLocalStorageKey = !!global.localStorage.getItem(
EXPIRED_SUBSCRIPTION_MODAL_LOCALSTORAGE_KEY(subscriptionLicense),
);
@@ -619,7 +619,7 @@ describe(' ', () => {
);
expect(screen.queryByText(SUBSCRIPTION_EXPIRING_MODAL_TITLE)).toBeTruthy();
expect(screen.queryByText(SUBSCRIPTION_EXPIRED_MODAL_TITLE)).toBeFalsy();
- userEvent.click(screen.getByTestId('modal-footer-btn'));
+ userEvent.click(screen.getByTestId('subscription-expiration-button'));
const hasExpirationModal = !!global.localStorage.getItem(`${SEEN_SUBSCRIPTION_EXPIRATION_MODAL_COOKIE_PREFIX}${threshold}-${subscriptionPlanId}`);
expect(hasExpirationModal).toEqual(true);
});
diff --git a/src/components/integration-warning-modal/IntegrationWarningModal.jsx b/src/components/integration-warning-modal/IntegrationWarningModal.jsx
index 9648765076..27c97b2bc2 100644
--- a/src/components/integration-warning-modal/IntegrationWarningModal.jsx
+++ b/src/components/integration-warning-modal/IntegrationWarningModal.jsx
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Cookies from 'universal-cookie';
-import { Modal, Button } from '@openedx/paragon';
+import { Button, AlertModal } from '@openedx/paragon';
import { getConfig } from '@edx/frontend-platform/config';
import { MODAL_BUTTON_TEXT, MODAL_TITLE } from './data/constants';
import ModalBody from './ModalBody';
@@ -27,23 +27,21 @@ const IntegrationWarningModal = ({
};
return (
- }
- open={dismissed}
- onClose={handleModalOnClose}
+
{MODAL_BUTTON_TEXT}
- ,
- ]}
- />
+
+ )}
+ >
+
+
);
};
diff --git a/src/styles/index.scss b/src/styles/index.scss
index fea8242b4a..b2986dfb2c 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -20,7 +20,7 @@
@import "../components/course/styles/ProgramSidebar";
@import "../components/course/styles/CourseSkills";
@import "../components/course/styles/CourseRecommendations";
-@import "../components/dashboard/main-content/course-enrollments/course-cards/styles/CourseCard";
+@import "../components/dashboard/main-content/course-enrollments/course-cards/styles";
@import "../components/dashboard/main-content/course-enrollments/styles/CourseSection";
@import "../components/dashboard/sidebar/styles/SidebarCard";
@import "../components/dashboard/styles/SubscriptionExpirationModal";