diff --git a/src/containers/CourseCard/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/__snapshots__/index.test.jsx.snap
new file mode 100644
index 0000000..71d58ab
--- /dev/null
+++ b/src/containers/CourseCard/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,65 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CourseCard component snapshot 1`] = `
+
+
+
+
+
+ }
+ title="hooks.title"
+ />
+
+ hooks.providerName
+ •
+ test-course-number
+ •
+
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/containers/CourseCard/components/CourseCardActions.jsx b/src/containers/CourseCard/components/CourseCardActions.jsx
deleted file mode 100644
index 84af3c2..0000000
--- a/src/containers/CourseCard/components/CourseCardActions.jsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { Button } from '@edx/paragon';
-import { Locked } from '@edx/paragon/icons';
-
-import { selectors } from 'data/redux';
-import { getCardValues } from 'hooks';
-
-const { cardData } = selectors;
-
-export const CourseCardActions = ({ courseNumber }) => {
- const {
- canUpgrade,
- isAudit,
- isAuditAccessExpired,
- isVerified,
- isPending,
- isFinished,
- } = getCardValues(courseNumber, {
- canUpgrade: cardData.canUpgrade,
- isAudit: cardData.isAudit,
- isAuditAccessExpired: cardData.isAuditAccessExpired,
- isVerified: cardData.isVerified,
- isPending: cardData.isCourseRunPending,
- isFinished: cardData.isCourseRunFinished,
- });
-
- let primary;
- let secondary = null;
- if (!isVerified) {
- secondary = (
-
- );
- }
-
- if (isPending) {
- primary = ();
- } else if (!isFinished) {
- primary = (isAudit && isAuditAccessExpired)
- ? ()
- : ();
- } else {
- primary = ();
- }
- return (<>{secondary}{primary}>);
-};
-CourseCardActions.propTypes = {
- courseNumber: PropTypes.string.isRequired,
-};
-
-export default CourseCardActions;
diff --git a/src/containers/CourseCard/components/CourseCardActions/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/components/CourseCardActions/__snapshots__/index.test.jsx.snap
new file mode 100644
index 0000000..da61d6f
--- /dev/null
+++ b/src/containers/CourseCard/components/CourseCardActions/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CourseCard Actions component does not render secondary button if null is returned for secondary props 1`] = `
+
+
+
+`;
+
+exports[`CourseCard Actions component loads primary and secondary button props from hook 1`] = `
+
+
+
+
+`;
diff --git a/src/containers/CourseCard/components/CourseCardActions/hooks.js b/src/containers/CourseCard/components/CourseCardActions/hooks.js
new file mode 100644
index 0000000..c9dcb03
--- /dev/null
+++ b/src/containers/CourseCard/components/CourseCardActions/hooks.js
@@ -0,0 +1,42 @@
+import { Locked } from '@edx/paragon/icons';
+
+import { selectors } from 'data/redux';
+import { useIntl, getCardValues } from 'hooks';
+import messages from './messages';
+
+const { cardData } = selectors;
+
+export const actionHooks = ({ courseNumber }) => {
+ const { formatMessage } = useIntl();
+ const data = getCardValues(courseNumber, {
+ canUpgrade: cardData.canUpgrade,
+ isAudit: cardData.isAudit,
+ isAuditAccessExpired: cardData.isAuditAccessExpired,
+ isVerified: cardData.isVerified,
+ isPending: cardData.isCourseRunPending,
+ isFinished: cardData.isCourseRunFinished,
+ });
+ let primary;
+ let secondary = null;
+ if (!data.isVerified) {
+ secondary = {
+ iconBefore: Locked,
+ variant: 'outline-primary',
+ disabled: !data.canUpgrade,
+ children: formatMessage(messages.upgrade),
+ };
+ }
+ if (data.isPending) {
+ primary = { children: formatMessage(messages.beginCourse) };
+ } else if (!data.isFinished) {
+ primary = {
+ children: formatMessage(messages.resume),
+ disabled: data.isAudit && data.isAuditAccessExpired,
+ };
+ } else {
+ primary = { children: formatMessage(messages.viewCourse) };
+ }
+ return { primary, secondary };
+};
+
+export default actionHooks;
diff --git a/src/containers/CourseCard/components/CourseCardActions/hooks.test.js b/src/containers/CourseCard/components/CourseCardActions/hooks.test.js
new file mode 100644
index 0000000..8b9be9f
--- /dev/null
+++ b/src/containers/CourseCard/components/CourseCardActions/hooks.test.js
@@ -0,0 +1,108 @@
+import { Locked } from '@edx/paragon/icons';
+
+import { selectors } from 'data/redux';
+
+import * as appHooks from 'hooks';
+import { testCardValues } from 'testUtils';
+import * as hooks from './hooks';
+
+import messages from './messages';
+
+const courseNumber = 'my-test-course-number';
+const { fieldKeys } = selectors.cardData;
+
+const props = {
+ canUpgrade: false,
+ isAudit: true,
+ isAuditAccessExpired: false,
+ isVerified: false,
+ isPending: false,
+ isFinished: false,
+};
+
+describe('CourseCardActions hooks', () => {
+ let out;
+ const { formatMessage } = appHooks.useIntl();
+ describe('data connection', () => {
+ beforeEach(() => {
+ out = hooks.actionHooks({ courseNumber });
+ });
+ testCardValues(courseNumber, {
+ canUpgrade: fieldKeys.canUpgrade,
+ isAudit: fieldKeys.isAudit,
+ isAuditAccessExpired: fieldKeys.isAuditAccessExpired,
+ isVerified: fieldKeys.isVerified,
+ isPending: fieldKeys.isCourseRunPending,
+ isFinished: fieldKeys.isCourseRunFinished,
+ });
+ });
+ describe('secondary action', () => {
+ it('returns null if verified', () => {
+ appHooks.getCardValues.mockReturnValueOnce({
+ ...props,
+ isAudit: false,
+ isVerified: true,
+ });
+ out = hooks.actionHooks({ courseNumber });
+ expect(out.secondary).toEqual(null);
+ });
+ it('returns disabled upgrade button if audit, but cannot upgrade', () => {
+ appHooks.getCardValues.mockReturnValueOnce(props);
+ out = hooks.actionHooks({ courseNumber });
+ expect(out.secondary).toEqual({
+ iconBefore: Locked,
+ variant: 'outline-primary',
+ disabled: true,
+ children: formatMessage(messages.upgrade),
+ });
+ });
+ it('returns enabled upgrade button if audit and can upgrade', () => {
+ appHooks.getCardValues.mockReturnValueOnce({ ...props, canUpgrade: true });
+ out = hooks.actionHooks({ courseNumber });
+ expect(out.secondary).toEqual({
+ iconBefore: Locked,
+ variant: 'outline-primary',
+ disabled: false,
+ children: formatMessage(messages.upgrade),
+ });
+ });
+ });
+ describe('primary action', () => {
+ it('returns Begin Course button if pending', () => {
+ appHooks.getCardValues.mockReturnValueOnce({ ...props, isPending: true });
+ out = hooks.actionHooks({ courseNumber });
+ expect(out.primary).toEqual({
+ children: formatMessage(messages.beginCourse),
+ });
+ });
+ it('returns enabled Resume button if active, and not audit with expired access', () => {
+ appHooks.getCardValues.mockReturnValueOnce({ ...props, isAuditAccessExpired: true });
+ out = hooks.actionHooks({ courseNumber });
+ expect(out.primary).toEqual({
+ children: formatMessage(messages.resume),
+ disabled: true,
+ });
+ });
+ it('returns disabled Resume button if active and audit without expired access', () => {
+ appHooks.getCardValues.mockReturnValueOnce({ ...props });
+ out = hooks.actionHooks({ courseNumber });
+ expect(out.primary).toEqual({
+ children: formatMessage(messages.resume),
+ disabled: false,
+ });
+ appHooks.getCardValues.mockReturnValueOnce({ ...props, isAudit: false, isVerified: true });
+ out = hooks.actionHooks({ courseNumber });
+ expect(out.primary).toEqual({
+ children: formatMessage(messages.resume),
+ disabled: false,
+ });
+ });
+ it('returns viewCourse button if finished', () => {
+ appHooks.getCardValues.mockReturnValueOnce({ ...props, isFinished: true });
+ out = hooks.actionHooks({ courseNumber });
+ expect(out.primary).toEqual({
+ children: formatMessage(messages.viewCourse),
+ });
+ });
+ });
+});
diff --git a/src/containers/CourseCard/components/CourseCardActions/index.jsx b/src/containers/CourseCard/components/CourseCardActions/index.jsx
new file mode 100644
index 0000000..a4742c8
--- /dev/null
+++ b/src/containers/CourseCard/components/CourseCardActions/index.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { Button } from '@edx/paragon';
+
+import hooks from './hooks';
+
+export const CourseCardActions = ({ courseNumber }) => {
+ const { primary, secondary } = hooks({ courseNumber });
+ return (
+ <>
+ {(secondary !== null) && (
+
+ )}
+
+ >
+ );
+};
+CourseCardActions.propTypes = {
+ courseNumber: PropTypes.string.isRequired,
+};
+
+export default CourseCardActions;
diff --git a/src/containers/CourseCard/components/CourseCardActions/index.test.jsx b/src/containers/CourseCard/components/CourseCardActions/index.test.jsx
new file mode 100644
index 0000000..d9356f8
--- /dev/null
+++ b/src/containers/CourseCard/components/CourseCardActions/index.test.jsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import hooks from './hooks';
+import CourseCardActions from '.';
+
+jest.mock('./hooks', () => ({
+ __esModule: true,
+ default: jest.fn(),
+}));
+
+const courseNumber = 'test-course-number';
+
+describe('CourseCard Actions component', () => {
+ it('loads primary and secondary button props from hook', () => {
+ const mockHook = (args) => ({
+ primary: {
+ prop1: 'primary-prop1',
+ prop2: 'primary-prop2',
+ children: 'primary-children',
+ courseNumber: args.courseNumber,
+ },
+ secondary: {
+ prop1: 'primary-prop1',
+ prop2: 'primary-prop2',
+ children: 'primary-children',
+ courseNumber: args.courseNumber,
+ },
+ });
+ hooks.mockImplementationOnce(mockHook);
+ expect(shallow()).toMatchSnapshot();
+ });
+ it('does not render secondary button if null is returned for secondary props', () => {
+ const mockHook = (args) => ({
+ primary: {
+ prop1: 'primary-prop1',
+ prop2: 'primary-prop2',
+ children: 'primary-children',
+ courseNumber: args.courseNumber,
+ },
+ secondary: null,
+ });
+ hooks.mockImplementationOnce(mockHook);
+ expect(shallow()).toMatchSnapshot();
+ });
+});
diff --git a/src/containers/CourseCard/components/CourseCardActions/messages.js b/src/containers/CourseCard/components/CourseCardActions/messages.js
new file mode 100644
index 0000000..287f18f
--- /dev/null
+++ b/src/containers/CourseCard/components/CourseCardActions/messages.js
@@ -0,0 +1,26 @@
+import { StrictDict } from 'utils';
+
+export const messages = StrictDict({
+ upgrade: {
+ id: 'learner-dash.courseCard.actions.upgrade',
+ description: 'Course card upgrade button text',
+ defaultMessage: 'Upgrade',
+ },
+ beginCourse: {
+ id: 'learner-dash.courseCard.actions.beginCourse',
+ description: 'Course card begin-course button text',
+ defaultMessage: 'Begin Course',
+ },
+ resume: {
+ id: 'learner-dash.courseCard.actions.resume',
+ description: 'Course card resume button text',
+ defaultMessage: 'Resume',
+ },
+ viewCourse: {
+ id: 'learner-dash.courseCard.actions.viewCourse',
+ description: 'Course card view-course button text',
+ defaultMessage: 'View Course',
+ },
+});
+
+export default messages;
diff --git a/src/containers/CourseCard/hooks.js b/src/containers/CourseCard/hooks.js
new file mode 100644
index 0000000..e6538c1
--- /dev/null
+++ b/src/containers/CourseCard/hooks.js
@@ -0,0 +1,49 @@
+import { selectors } from 'data/redux';
+
+import { useIntl, getCardValues } from 'hooks';
+
+import * as module from './hooks';
+import messages from './messages';
+
+const { cardData } = selectors;
+
+export const accessMessage = ({ courseNumber }) => {
+ const { formatMessage, formatDate } = useIntl();
+
+ const data = getCardValues(courseNumber, {
+ accessExpirationDate: cardData.courseRunAccessExpirationDate,
+ isAudit: cardData.isAudit,
+ isFinished: cardData.isCourseRunFinished,
+ isAuditAccessExpired: cardData.isAuditAccessExpired,
+ endDate: cardData.courseRunEndDate,
+ });
+ if (data.isAudit) {
+ return formatMessage(
+ data.isAuditAccessExpired ? messages.accessExpired : messages.accessExpires,
+ { accessExpirationDate: formatDate(data.accessExpirationDate) },
+ );
+ }
+ return formatMessage(
+ data.isFinished ? messages.courseEnded : messages.courseEnds,
+ { endDate: formatDate(data.endDate) },
+ );
+};
+
+export const cardHooks = ({ courseNumber }) => {
+ const { formatMessage } = useIntl();
+ const data = getCardValues(courseNumber, {
+ title: cardData.courseTitle,
+ bannerUrl: cardData.courseBannerUrl,
+ providerName: cardData.providerName,
+ });
+
+ return {
+ title: data.title,
+ bannerUrl: data.bannerUrl,
+ providerName: data.providerName || formatMessage(messages.unknownProviderName),
+ accessMessage: module.accessMessage({ courseNumber }),
+ formatMessage,
+ };
+};
+
+export default cardHooks;
diff --git a/src/containers/CourseCard/hooks.test.js b/src/containers/CourseCard/hooks.test.js
new file mode 100644
index 0000000..69d6f79
--- /dev/null
+++ b/src/containers/CourseCard/hooks.test.js
@@ -0,0 +1,125 @@
+import { keyStore } from 'utils';
+import { selectors } from 'data/redux';
+import * as appHooks from 'hooks';
+import { testCardValues } from 'testUtils';
+
+import * as hooks from './hooks';
+import messages from './messages';
+
+const { fieldKeys } = selectors.cardData;
+
+const courseNumber = 'my-test-course-number';
+
+describe('CourseCard hooks', () => {
+ let out;
+ const { formatMessage, formatDate } = appHooks.useIntl();
+ describe('cardHooks', () => {
+ const accessMessage = 'test-access-message';
+ const mockAccessMessage = (args) => ({ courseNumber: args.coursenumber, accessMessage });
+ const hookKeys = keyStore(hooks);
+ beforeEach(() => {
+ jest.spyOn(hooks, hookKeys.accessMessage).mockImplementationOnce(mockAccessMessage);
+ out = hooks.cardHooks({ courseNumber });
+ });
+ testCardValues(courseNumber, {
+ title: fieldKeys.courseTitle,
+ bannerUrl: fieldKeys.courseBannerUrl,
+ providerName: fieldKeys.providerName,
+ });
+ test('providerName returns Unknown message if not provided', () => {
+ appHooks.getCardValues.mockReturnValueOnce({
+ title: 'title',
+ bannerUrl: 'bannerUrl',
+ providerName: null,
+ });
+ jest.spyOn(hooks, hookKeys.accessMessage).mockImplementationOnce(mockAccessMessage);
+ out = hooks.cardHooks({ courseNumber });
+ expect(out.providerName).toEqual(formatMessage(messages.unknownProviderName));
+ });
+ describe('accessMessage', () => {
+ it('returns the output of accessMessage hook, passed courseNumber', () => {
+ expect(out.accessMessage).toEqual(mockAccessMessage({ courseNumber }));
+ });
+ });
+ it('forwards formatMessage from useIntl', () => {
+ expect(out.formatMessage).toEqual(formatMessage);
+ });
+ });
+ describe('accessMessage', () => {
+ const accessExpirationDate = 'test-expiration-date';
+ const endDate = 'test-end-date';
+ describe('loaded data', () => {
+ beforeEach(() => {
+ appHooks.getCardValues.mockClear();
+ out = hooks.accessMessage({ courseNumber });
+ });
+ testCardValues(courseNumber, {
+ accessExpirationDate: fieldKeys.courseRunAccessExpirationDate,
+ isAudit: fieldKeys.isAudit,
+ isFinished: fieldKeys.isCourseRunFinished,
+ isAuditAccessExpired: fieldKeys.isAuditAccessExpired,
+ endDate: fieldKeys.courseRunEndDate,
+ });
+ });
+ describe('if audit, and expired', () => {
+ it('returns accessExpires message with accessExpirationDate from cardData', () => {
+ appHooks.getCardValues.mockReturnValueOnce({
+ accessExpirationDate,
+ endDate,
+ isAudit: true,
+ isFinished: false,
+ isAuditAccessExpired: true,
+ });
+ expect(hooks.accessMessage({ courseNumber })).toEqual(formatMessage(
+ messages.accessExpired,
+ { accessExpirationDate: formatDate(accessExpirationDate) },
+ ));
+ });
+ });
+ describe('if audit and not expired', () => {
+ it('returns accessExpires message with accessExpirationDate from cardData', () => {
+ appHooks.getCardValues.mockReturnValueOnce({
+ accessExpirationDate,
+ endDate,
+ isAudit: true,
+ isFinished: false,
+ isAuditAccessExpired: false,
+ });
+ expect(hooks.accessMessage({ courseNumber })).toEqual(formatMessage(
+ messages.accessExpires,
+ { accessExpirationDate: formatDate(accessExpirationDate) },
+ ));
+ });
+ });
+ describe('if verified and not ended', () => {
+ it('returns accessExpires message with accessExpirationDate from cardData', () => {
+ appHooks.getCardValues.mockReturnValueOnce({
+ accessExpirationDate,
+ endDate,
+ isAudit: false,
+ isFinished: false,
+ isAuditAccessExpired: true,
+ });
+ expect(hooks.accessMessage({ courseNumber })).toEqual(formatMessage(
+ messages.courseEnds,
+ { endDate: formatDate(endDate) },
+ ));
+ });
+ });
+ describe('if verified and ended', () => {
+ it('returns accessExpires message with accessExpirationDate from cardData', () => {
+ appHooks.getCardValues.mockReturnValueOnce({
+ accessExpirationDate,
+ endDate,
+ isAudit: false,
+ isFinished: true,
+ isAuditAccessExpired: true,
+ });
+ expect(hooks.accessMessage({ courseNumber })).toEqual(formatMessage(
+ messages.courseEnded,
+ { endDate: formatDate(endDate) },
+ ));
+ });
+ });
+ });
+});
diff --git a/src/containers/CourseCard/index.jsx b/src/containers/CourseCard/index.jsx
index 4141661..1aed1d9 100644
--- a/src/containers/CourseCard/index.jsx
+++ b/src/containers/CourseCard/index.jsx
@@ -4,9 +4,7 @@ import PropTypes from 'prop-types';
// import PropTypes from 'prop-types';
import { Card } from '@edx/paragon';
-import { selectors } from 'data/redux';
-
-import { getCardValues } from 'hooks';
+import hooks from './hooks';
import RelatedProgramsBadge from './components/RelatedProgramsBadge';
import CourseCardMenu from './components/CourseCardMenu';
@@ -16,27 +14,22 @@ import {
EntitlementBanner,
} from './components/Banners';
import CourseCardActions from './components/CourseCardActions';
-
-const { cardData } = selectors;
+import messages from './messages';
export const CourseCard = ({ courseNumber }) => {
const {
title,
bannerUrl,
- accessExpirationDate,
providerName,
- } = getCardValues(courseNumber, {
- title: cardData.courseTitle,
- bannerUrl: cardData.courseBannerUrl,
- accessExpirationDate: cardData.courseRunAccessExpirationDate,
- providerName: cardData.providerName,
- });
+ accessMessage,
+ formatMessage,
+ } = hooks({ courseNumber });
return (
{
actions={}
/>
- {providerName || 'Unkown'} • {courseNumber} • Access expires {accessExpirationDate}
+ {providerName} • {courseNumber} • {accessMessage}
({
+ __esModule: true,
+ default: jest.fn(),
+}));
+
+jest.mock('./components/RelatedProgramsBadge', () => 'RelatedProgramsBadge');
+jest.mock('./components/CourseCardMenu', () => 'CourseCardMenu');
+jest.mock('./components/banners', () => ({
+ CourseBanner: () => 'CourseBanner',
+ CertificateBanner: () => 'CertificateBanner',
+ EntitlementBanner: () => 'EntitlementBanner',
+}));
+jest.mock('./components/CourseCardActions', () => 'CourseCardActions');
+
+const dataProps = {
+ title: 'hooks.title',
+ bannerUrl: 'hooks.bannerUrl',
+ providerName: 'hooks.providerName',
+ accessMessagE: 'hooks.accessMessage',
+ formatMessage: jest.fn(msg => ({ formatted: msg })),
+};
+
+const courseNumber = 'test-course-number';
+
+describe('CourseCard component', () => {
+ test('snapshot', () => {
+ hooks.mockReturnValueOnce(dataProps);
+ expect(shallow()).toMatchSnapshot();
+ expect(hooks).toHaveBeenCalledWith({ courseNumber });
+ });
+});
diff --git a/src/containers/CourseCard/messages.js b/src/containers/CourseCard/messages.js
new file mode 100644
index 0000000..4c60a24
--- /dev/null
+++ b/src/containers/CourseCard/messages.js
@@ -0,0 +1,36 @@
+import { StrictDict } from 'utils';
+
+export const messages = StrictDict({
+ accessExpired: {
+ id: 'learner-dash.courseCard.accessExpired',
+ description: 'Course access expiration date message on course card for expired access.',
+ defaultMessage: 'Access expired {accessExpirationDate}',
+ },
+ accessExpires: {
+ id: 'learner-dash.courseCard.accessExpires',
+ description: 'Course access expiration date message on course card.',
+ defaultMessage: 'Access expires {accessExpirationDate}',
+ },
+ courseEnded: {
+ id: 'learner-dash.courseCard.courseEnded',
+ description: 'Course ended message on course card.',
+ defaultMessage: 'Course ended {endDate}',
+ },
+ courseEnds: {
+ id: 'learner-dash.courseCard.courseEnds',
+ description: 'Course ending message on course card.',
+ defaultMessage: 'Course ends {endDate}',
+ },
+ bannerAlt: {
+ id: 'learner-dash.courseCard.bannerAlt',
+ description: 'Course card banner alt-text',
+ defaultMessage: 'Course thumbnail',
+ },
+ unknownProviderName: {
+ id: 'learner-dash.courseCard.unknownProviderName',
+ description: 'Provider name display when name is unknown',
+ defaultMessage: 'Unknown',
+ },
+});
+
+export default messages;
diff --git a/src/data/redux/cardData/selectors.js b/src/data/redux/cardData/selectors.js
index cd7b4a6..1f00db7 100644
--- a/src/data/redux/cardData/selectors.js
+++ b/src/data/redux/cardData/selectors.js
@@ -11,6 +11,7 @@ export const fieldSelectors = {
courseTitle: data => data.course.title,
courseBannerUrl: data => data.course.bannerUrl,
courseRunAccessExpirationDate: data => data.courseRun.accessExpirationDate,
+ courseRunEndDate: data => data.courseRun.endDate,
courseWebsite: data => data.course.website,
providerName: data => data.provider?.name,
isVerified: data => data.enrollment.isVerified,
diff --git a/src/data/services/lms/fakeData/courses.js b/src/data/services/lms/fakeData/courses.js
index f8dc78e..e62f934 100644
--- a/src/data/services/lms/fakeData/courses.js
+++ b/src/data/services/lms/fakeData/courses.js
@@ -56,6 +56,7 @@ export const genCourseRunData = (data = {}) => ({
isFinished: false,
isArchived: false,
accessExpirationDate: futureDate,
+ endDate: futureDate,
minPassingGrade: 70,
...data,
});
@@ -140,7 +141,7 @@ export const courseRuns = [
},
// verified, pending, restricted
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: true },
courseRun: { isPending: true },
certificates: genCertificateData({ isRestricted: true }),
@@ -148,7 +149,7 @@ export const courseRuns = [
},
// verified, started
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: true },
courseRun: { isStarted: true },
certificates: genCertificateData(),
@@ -156,7 +157,7 @@ export const courseRuns = [
},
// verified, not passing
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: false },
courseRun: { isStarted: true },
certificates: genCertificateData(),
@@ -164,9 +165,9 @@ export const courseRuns = [
},
// verified, finished, not passing
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: false },
- courseRun: { isFinished: true },
+ courseRun: { isFinished: true, endDate: pastDate },
certificates: genCertificateData(),
entitlements: { isEntitlement: false },
},
@@ -191,7 +192,7 @@ export const courseRuns = [
},
// verified, earned, downloadable (web + link)
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: true },
courseRun: { isStarted: true },
certificates: genCertificateData({
@@ -208,7 +209,7 @@ export const courseRuns = [
},
// verified, earned, downloadable (link)
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: true },
courseRun: { isStarted: true },
certificates: genCertificateData({
@@ -224,7 +225,7 @@ export const courseRuns = [
},
// Entitlement Course Run - Cannot view yet
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: true },
courseRun: { isPending: true },
certificates: genCertificateData(),
@@ -240,7 +241,7 @@ export const courseRuns = [
},
// Entitlement Course Run - Can View and Change
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: true },
courseRun: { isStarted: true },
certificates: genCertificateData(),
@@ -256,7 +257,7 @@ export const courseRuns = [
},
// Entitlement Course Run - Can View but not Change
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: true },
courseRun: { isStarted: true },
certificates: genCertificateData(),
@@ -272,9 +273,14 @@ export const courseRuns = [
},
// Entitlement Course Run - Expired
{
- enrollment: genEnrollmentData({ isVerified: true }),
+ enrollment: genEnrollmentData({ isAudit: false, isVerified: true }),
grades: { isPassing: true },
- courseRun: { isStarted: true, isFinished: true, isArchived: true },
+ courseRun: {
+ isStarted: true,
+ isFinished: true,
+ isArchived: true,
+ endDate: pastDate,
+ },
certificates: genCertificateData(),
entitlements: {
isEntitlement: true,
diff --git a/src/hooks.js b/src/hooks.js
index e78075d..335ca56 100644
--- a/src/hooks.js
+++ b/src/hooks.js
@@ -1,3 +1,4 @@
+import { useIntl } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';
import { selectors } from 'data/redux';
@@ -18,6 +19,10 @@ export const getCardValues = (courseNumber, mapping) => {
export const nullMethod = () => ({});
+export { useIntl };
+
export default {
+ getCardValues,
nullMethod,
+ useIntl,
};
diff --git a/src/setupTest.js b/src/setupTest.js
index 5cd82f2..7177b9a 100755
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -39,6 +39,9 @@ jest.mock('@edx/paragon', () => jest.requireActual('testUtils').mockNestedCompon
Card: {
Body: 'Card.Body',
Footer: 'Card.Footer',
+ Header: 'Card.Header',
+ ImageCap: 'Card.ImageCap',
+ Section: 'Card.Section',
},
Col: 'Col',
Collapsible: {
@@ -129,3 +132,24 @@ jest.mock('react-redux', () => {
useSelector: jest.fn((selector) => ({ useSelector: selector })),
};
});
+
+jest.mock('hooks', () => {
+ const formatMessage = jest.fn((msg, values) => ({ formatted: { msg, values } }));
+ return {
+ ...jest.requireActual('hooks'),
+ useIntl: () => ({
+ formatMessage,
+ formatDate: jest.fn((date) => ({ formatted: date })),
+ }),
+ getCardValues: jest.fn((courseNumber, mapping) => (
+ Object.keys(mapping).reduce(
+ (obj, key) => ({
+ ...obj,
+ [key]: { selector: mapping[key], courseNumber },
+ }),
+ {},
+ )
+ )),
+ nullMethod: jest.fn().mockName('hooks.nullMethod'),
+ };
+});
diff --git a/src/testUtils.js b/src/testUtils.js
index baf986a..362cf28 100644
--- a/src/testUtils.js
+++ b/src/testUtils.js
@@ -1,7 +1,13 @@
import react from 'react';
+import { selectors } from 'data/redux';
+
+import * as appHooks from 'hooks';
+
import { StrictDict } from 'utils';
+const { cardData } = selectors;
+
/**
* Mocked formatMessage provided by react-intl
*/
@@ -185,3 +191,23 @@ export class MockUseState {
});
}
}
+
+/**
+ * Test that getCardValues was called with the given courseNumber and selector mapping.
+ * @param {string} courseNumber - course run identifier
+ * @param {obj} mapping - value mapping { : }
+ */
+export const testCardValues = (courseNumber, mapping) => {
+ describe('cardData values', () => {
+ let mapped;
+ test('passess correct courseNumber', () => {
+ expect(appHooks.getCardValues.mock.calls[0][0]).toEqual(courseNumber);
+ });
+ Object.keys(mapping).forEach(key => {
+ test(`loads ${key} from card data ${mapping[key]} selector`, () => {
+ [[, mapped]] = appHooks.getCardValues.mock.calls;
+ expect(mapped[key]).toEqual(cardData[mapping[key]]);
+ });
+ });
+ });
+};