From aa3fc1321dfe58758a773ad50eb22ccbc7c975be Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Thu, 9 Jun 2022 22:28:10 -0400 Subject: [PATCH] feat: CourseCard and CourseCardActions i18n and tests --- .../__snapshots__/index.test.jsx.snap | 65 +++++++++ .../components/CourseCardActions.jsx | 58 -------- .../__snapshots__/index.test.jsx.snap | 32 +++++ .../components/CourseCardActions/hooks.js | 42 ++++++ .../CourseCardActions/hooks.test.js | 108 +++++++++++++++ .../components/CourseCardActions/index.jsx | 23 ++++ .../CourseCardActions/index.test.jsx | 46 +++++++ .../components/CourseCardActions/messages.js | 26 ++++ src/containers/CourseCard/hooks.js | 49 +++++++ src/containers/CourseCard/hooks.test.js | 125 ++++++++++++++++++ src/containers/CourseCard/index.jsx | 21 +-- src/containers/CourseCard/index.test.jsx | 37 ++++++ src/containers/CourseCard/messages.js | 36 +++++ src/data/redux/cardData/selectors.js | 1 + src/data/services/lms/fakeData/courses.js | 30 +++-- src/hooks.js | 5 + src/setupTest.js | 24 ++++ src/testUtils.js | 26 ++++ 18 files changed, 670 insertions(+), 84 deletions(-) create mode 100644 src/containers/CourseCard/__snapshots__/index.test.jsx.snap delete mode 100644 src/containers/CourseCard/components/CourseCardActions.jsx create mode 100644 src/containers/CourseCard/components/CourseCardActions/__snapshots__/index.test.jsx.snap create mode 100644 src/containers/CourseCard/components/CourseCardActions/hooks.js create mode 100644 src/containers/CourseCard/components/CourseCardActions/hooks.test.js create mode 100644 src/containers/CourseCard/components/CourseCardActions/index.jsx create mode 100644 src/containers/CourseCard/components/CourseCardActions/index.test.jsx create mode 100644 src/containers/CourseCard/components/CourseCardActions/messages.js create mode 100644 src/containers/CourseCard/hooks.js create mode 100644 src/containers/CourseCard/hooks.test.js create mode 100644 src/containers/CourseCard/index.test.jsx create mode 100644 src/containers/CourseCard/messages.js 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) && ( +