Skip to content

Commit

Permalink
Merge pull request #138 from edx/aakbar/PROD-2391
Browse files Browse the repository at this point in the history
refactor: self-contain course summary component
  • Loading branch information
DawoudSheraz authored Jul 12, 2021
2 parents eb596ff + 1de7b5c commit 8e516bb
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 98 deletions.
82 changes: 45 additions & 37 deletions src/users/courseSummary/CourseSummary.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
import React from 'react';
import React, {
useEffect, useState, useContext, useLayoutEffect,
} from 'react';
import PropTypes from 'prop-types';
import { camelCaseObject } from '@edx/frontend-platform';
import PageLoading from '../../components/common/PageLoading';
import { formatDate } from '../../utils';
import UserMessagesContext from '../../userMessages/UserMessagesContext';
import AlertList from '../../userMessages/AlertList';
import { getCourseData } from '../data/api';

export default function CourseSummary({
courseData,
errors,
courseUUID,
clearHandler,
forwardedRef,
}) {
const { add, clear } = useContext(UserMessagesContext);
const [courseSummaryErrors, setCourseSummaryErrors] = useState(false);
const [courseSummaryData, setCourseSummaryData] = useState(null);
useEffect(() => {
clear('course-summary');
if (courseUUID !== null && courseUUID !== undefined) {
setCourseSummaryData(null);
getCourseData(courseUUID).then((result) => {
const camelCaseResult = camelCaseObject(result);
if (camelCaseResult.errors) {
camelCaseResult.errors.forEach(error => add(error));
setCourseSummaryErrors(true);
} else {
setCourseSummaryErrors(false);
setCourseSummaryData(camelCaseResult);
}
});
}
}, [courseUUID]);
useLayoutEffect(() => {
if (forwardedRef && forwardedRef.current) { forwardedRef.current.focus(); }
});
function renderCourseRuns(data) {
const { courseRuns } = data;
if (courseRuns) {
Expand Down Expand Up @@ -45,48 +71,44 @@ export default function CourseSummary({
}
return (
<section className="card mb-3">
{!courseData && !errors && <PageLoading srMessage="Loading" />}
{errors && (
{!courseSummaryData && !courseSummaryErrors && <PageLoading srMessage="Loading" />}
{courseSummaryErrors && (
<>
<AlertList topic="course-summary" className="m-3" />
{renderHideButton()}
</>
)}
{courseData && !errors && (
{courseSummaryData && !courseSummaryErrors && (
<div className="m-3">
<h4>Course Summary: {courseData.title}</h4>
<h4>Course Summary: {courseSummaryData.title}</h4>
<table className="table">
<tbody>
<tr>
<td>UUID</td>
<td>{courseData ? courseData.uuid : ''}</td>
<td>{courseSummaryData.uuid}</td>
</tr>
<tr>
<td>Course Key</td>
<td>{courseData ? courseData.key : ''}</td>
<td>{courseSummaryData.key}</td>
</tr>
<tr>
<td>Course Runs</td>
<td>{renderCourseRuns(courseData)}</td>
<td>{renderCourseRuns(courseSummaryData)}</td>
</tr>
<tr>
<td>Level</td>
<td>{courseData ? courseData.levelType : ''}</td>
<td>{courseSummaryData.levelType}</td>
</tr>
<tr>
<td>Marketing</td>
<td>
{courseData ? (
<a
href={courseData.marketingUrl}
rel="noopener noreferrer"
target="_blank"
>
Marketing URL
</a>
) : (
''
)}
<a
href={courseSummaryData.marketingUrl}
rel="noopener noreferrer"
target="_blank"
>
Marketing URL
</a>
</td>
</tr>
</tbody>
Expand All @@ -99,26 +121,12 @@ export default function CourseSummary({
}

CourseSummary.propTypes = {
courseData: PropTypes.shape({
title: PropTypes.string,
uuid: PropTypes.string,
key: PropTypes.string,
levelType: PropTypes.string,
marketingUrl: PropTypes.string,
courseRuns: PropTypes.arrayOf(
PropTypes.shape({
key: PropTypes.string,
}),
),
}),
errors: PropTypes.bool,
courseUUID: PropTypes.string.isRequired,
clearHandler: PropTypes.func,
forwardedRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
};

CourseSummary.defaultProps = {
courseData: null,
errors: false,
clearHandler: null,
forwardedRef: null,
};
51 changes: 29 additions & 22 deletions src/users/courseSummary/CourseSummary.test.jsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
import { mount } from 'enzyme';
import React from 'react';

import { waitForComponentToPaint } from '../../setupTest';
import CourseSummary from './CourseSummary';
import CourseSummaryData from '../data/test/courseSummary';
import courseSummaryData from '../data/test/courseSummary';
import UserMessagesProvider from '../../userMessages/UserMessagesProvider';
import * as api from '../data/api';

const CourseSummaryWrapper = (props) => (
<UserMessagesProvider>
<CourseSummary {...props} />
</UserMessagesProvider>
);

describe('Course Summary', () => {
let wrapper;

beforeEach(() => {
wrapper = mount(<CourseSummary {...CourseSummaryData} />);
});

it('Default Prop Values', () => {
const componentPropData = wrapper.prop('courseData');
const expectedPropData = CourseSummaryData.courseData;

expect(componentPropData.key).toEqual(expectedPropData.key);
expect(componentPropData.uuid).toEqual(expectedPropData.uuid);
expect(componentPropData.title).toEqual(expectedPropData.title);
expect(componentPropData.level).toEqual(expectedPropData.level);
expect(componentPropData.marketingUrl).toEqual(expectedPropData.marketingUrl);
expect(componentPropData.courseRuns).toMatchObject(expectedPropData.courseRuns);
const props = {
courseUUID: 'course-uuid',
clearHandler: jest.fn(() => {}),
};

beforeEach(async () => {
jest.spyOn(api, 'getCourseData').mockImplementationOnce(() => Promise.resolve(courseSummaryData));
wrapper = mount(<CourseSummaryWrapper {...props} />);
await waitForComponentToPaint(wrapper);
});

it('Missing Course Run Information', () => {
const courseData = { ...CourseSummaryData.courseData, courseRuns: [] };
const summaryData = { ...CourseSummaryData, courseData };
wrapper = mount(<CourseSummary {...CourseSummaryData} courseData={summaryData} />);
it('Missing Course Run Information', async () => {
const courseData = { ...courseSummaryData.courseData, courseRuns: [] };
const summaryData = { ...courseSummaryData, courseData };
jest.spyOn(api, 'getCourseData').mockImplementationOnce(() => Promise.resolve(summaryData));
wrapper = mount(<CourseSummaryWrapper {...props} />);
await waitForComponentToPaint(wrapper);
expect(wrapper.html()).toEqual(expect.stringContaining('No Course Runs available'));
});

it('Render loading page if data is not present', () => {
wrapper = mount(<CourseSummary {...CourseSummaryData} courseData={null} errors={false} />);
it('Render loading page correctly', async () => {
jest.spyOn(api, 'getCourseData').mockImplementationOnce(() => Promise.resolve({ ...courseSummaryData, courseData: null }));
wrapper = mount(<CourseSummaryWrapper {...props} />);
expect(wrapper.find('PageLoading').html()).toEqual(expect.stringContaining('Loading'));
await waitForComponentToPaint(wrapper);
});

it('Hide Course Summary Button', () => {
Expand Down
5 changes: 2 additions & 3 deletions src/users/data/test/courseSummary.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const CourseSummaryData = {
const courseSummaryData = {
courseData: {
title: 'Test Course',
uuid: '1234-5678-12345678',
Expand All @@ -18,7 +18,6 @@ const CourseSummaryData = {
},
],
},
clearHandler: jest.fn(() => {}),
};

export default CourseSummaryData;
export default courseSummaryData;
41 changes: 5 additions & 36 deletions src/users/entitlements/Entitlements.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { CREATE, REISSUE, EXPIRE } from './EntitlementActions';
import PageLoading from '../../components/common/PageLoading';
import Table from '../../Table';
import CourseSummary from '../courseSummary/CourseSummary';
import { getCourseData, getEntitlements } from '../data/api';
import { getEntitlements } from '../data/api';
import UserMessagesContext from '../../userMessages/UserMessagesContext';
import { formatDate, sort } from '../../utils';
import AlertList from '../../userMessages/AlertList';
Expand All @@ -26,8 +26,6 @@ export default function Entitlements({
const [formType, setFormType] = useState(null);
const [userEntitlement, setUserEntitlement] = useState(undefined);
const [courseSummaryUUID, setCourseSummaryUUID] = useState(null);
const [courseSummaryData, setCourseSummaryData] = useState(null);
const [courseSummaryErrors, setCourseSummaryErrors] = useState(false);
const [entitlementData, setEntitlementData] = useState(null);
const [entitlementDetailModalIsOpen, setEntitlementDetailModalIsOpen] = useState(false);
const [entitlementSupportDetailsTitle, setEntitlementSupportDetailsTitle] = useState('');
Expand All @@ -51,18 +49,9 @@ export default function Entitlements({
useLayoutEffect(() => {
if (formType != null) {
formRef.current.focus();
} else if (formType === null && (courseSummaryData != null || courseSummaryErrors)) {
summaryRef.current.focus();
}
});

function clearCourseSummary() {
clear('course-summary');
setCourseSummaryData(null);
setCourseSummaryUUID(null);
setCourseSummaryErrors(false);
}

// Modal to display Support Details for each Entitlement
const openEntitlementsSupportDetailsModal = (title, supportDetails) => {
const tableData = supportDetails.map(supportDetail => ({
Expand All @@ -77,23 +66,6 @@ export default function Entitlements({
setEntitlementDetailModalIsOpen(true);
};

const handleCourseSummaryDataGet = useCallback((courseUUID) => {
if (courseUUID !== null && courseUUID !== undefined) {
setCourseSummaryData(null);
getCourseData(courseUUID).then((result) => {
const camelResult = camelCaseObject(result);
clear('course-summary');
if (camelResult.errors) {
camelResult.errors.forEach(error => add(error));
setCourseSummaryErrors(true);
} else {
setCourseSummaryErrors(false);
setCourseSummaryData(camelResult);
}
});
}
});

const tableData = useMemo(() => {
if (entitlementData === null) {
return [];
Expand All @@ -107,7 +79,6 @@ export default function Entitlements({
setFormType(null);
setUserEntitlement(undefined);
setCourseSummaryUUID(entitlement.courseUuid);
handleCourseSummaryDataGet(entitlement.courseUuid);
}}
>
{entitlement.courseUuid}
Expand Down Expand Up @@ -173,7 +144,7 @@ export default function Entitlements({
variant="outline-primary mt-2 mr-2"
disabled={Boolean(!entitlement.enrollmentCourseRun)}
onClick={() => {
clearCourseSummary();
setCourseSummaryUUID(null);
setUserEntitlement(entitlement);
setFormType(REISSUE);
}}
Expand All @@ -185,7 +156,7 @@ export default function Entitlements({
variant="outline-danger mt-2"
disabled={Boolean(entitlement.expiredAt)}
onClick={() => {
clearCourseSummary();
setCourseSummaryUUID(null);
setUserEntitlement(entitlement);
setFormType(EXPIRE);
}}
Expand Down Expand Up @@ -263,7 +234,7 @@ export default function Entitlements({
type="button"
variant="outline-primary"
onClick={() => {
clearCourseSummary();
setCourseSummaryUUID(null);
setUserEntitlement(undefined);
setFormType(CREATE);
}}
Expand Down Expand Up @@ -293,10 +264,8 @@ export default function Entitlements({
key="course-summary"
courseUUID={courseSummaryUUID}
clearHandler={() => {
clearCourseSummary();
setCourseSummaryUUID(null);
}}
courseData={courseSummaryData}
errors={courseSummaryErrors}
forwardedRef={summaryRef}
/>
) : (<React.Fragment key="nothing" />)}
Expand Down
1 change: 1 addition & 0 deletions src/users/licenses/Licenses.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('User Licenses Listing', () => {
wrapper = mount(<LicensesPageWrapper {...props} />);
const collapsible = wrapper.find('CollapsibleAdvanced').find('.collapsible-trigger').hostNodes();
expect(collapsible.text()).toEqual('Licenses (0)Fetch Status: Loading...');
await waitForComponentToPaint(wrapper);
});

it('No License Data', async () => {
Expand Down

0 comments on commit 8e516bb

Please sign in to comment.