diff --git a/src/components/catalogInfoModal/CatalogInfoModal.jsx b/src/components/catalogInfoModal/CatalogInfoModal.jsx index cb1bac53..6171946b 100644 --- a/src/components/catalogInfoModal/CatalogInfoModal.jsx +++ b/src/components/catalogInfoModal/CatalogInfoModal.jsx @@ -30,7 +30,7 @@ SkillsListing.propTypes = { }; function CourseModal({ - intl, isOpen, onClose, selectedCourse, + intl, isOpen, onClose, isExecEdType, selectedCourse, }) { const { courseTitle, @@ -87,6 +87,7 @@ function CourseModal({ startDate={startDate} endDate={endDate} upcomingRuns={upcomingRuns} + execEd={isExecEdType} />

{intl.formatMessage( @@ -138,6 +139,7 @@ CourseModal.propTypes = { intl: intlShape.isRequired, isOpen: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, + isExecEdType: PropTypes.bool.isRequired, selectedCourse: PropTypes.shape({ courseTitle: PropTypes.string, courseProvider: PropTypes.string, @@ -332,6 +334,7 @@ function CatalogCourseInfoModal({ intl, isOpen, onClose, + isExecEdType, selectedCourse, selectedProgram, renderProgram, @@ -349,6 +352,7 @@ function CatalogCourseInfoModal({ intl={intl} isOpen={isOpen} onClose={onClose} + isExecEdType={isExecEdType} /> ); } @@ -366,6 +370,7 @@ function CatalogCourseInfoModal({ CatalogCourseInfoModal.defaultProps = { isOpen: false, renderProgram: false, + isExecEdType: false, selectedCourse: {}, selectedProgram: {}, onClose: () => {}, @@ -376,6 +381,7 @@ CatalogCourseInfoModal.propTypes = { intl: intlShape.isRequired, isOpen: PropTypes.bool, onClose: PropTypes.func, + isExecEdType: PropTypes.bool, selectedCourse: PropTypes.shape({ courseTitle: PropTypes.string, courseProvider: PropTypes.string, diff --git a/src/components/catalogInfoModal/CatalogInfoModal.test.jsx b/src/components/catalogInfoModal/CatalogInfoModal.test.jsx index febe1207..fd9f7aef 100644 --- a/src/components/catalogInfoModal/CatalogInfoModal.test.jsx +++ b/src/components/catalogInfoModal/CatalogInfoModal.test.jsx @@ -163,6 +163,60 @@ describe('Course info modal works as expected', () => { }); }); +describe('Executive Education info modal works as expected', () => { + const execEdTypeModalProps = { + isOpen: true, + isExecEdType: true, + selectedCourse: { + courseTitle: 'Exec Ed Title', + courseProvider: 'Provider', + coursePrice: '100.00', + courseAssociatedCatalogs: [], + courseDescription: descriptionHtml, + partnerLogoImageUrl: '', + bannerImageUrl: '', + startDate: '2020-09-15T16:00:00Z', + endDate: '2040-05-04T16:00:00Z', + upcomingRuns: 0, + marketingUrl: 'http://someurl', + skillNames: [], + }, + }; + const OLD_ENV = process.env; + beforeEach(() => { + jest.resetModules(); // Most important - it clears the cache + process.env = { ...OLD_ENV }; // Make a copy + }); + afterAll(() => { + process.env = OLD_ENV; // Restore old environment + }); + test('Executive Education info modal renders when expected', () => { + render( + + + , + ); + + const { selectedCourse } = execEdTypeModalProps; + expect(screen.queryByText(selectedCourse.courseTitle)).toBeInTheDocument(); + expect(screen.queryByText(selectedCourse.courseProvider)).toBeInTheDocument(); + expect(screen.queryByText(descriptionText)).toBeInTheDocument(); + }); + test('Executive Education modal banner renders correctly', () => { + render( + + + , + ); + expect(screen.queryByText('A la carte course price')).toBeInTheDocument(); + // price should truncate after decimal + expect(screen.queryByText('100')).toBeInTheDocument(); + expect(screen.queryByText('Executive Education')).toBeInTheDocument(); + expect(screen.queryByText('Immersive, instructor-led course')).toBeInTheDocument(); + expect(screen.queryByText('Session ends May 4, 2040')); + }); +}); + describe('Program info modal works as expected', () => { const programTypeModalProps = { ...courseTypeModalProps, diff --git a/src/components/catalogModalBanner/CatalogCourseModalBanner.jsx b/src/components/catalogModalBanner/CatalogCourseModalBanner.jsx index 2d10a4cf..daf982c2 100644 --- a/src/components/catalogModalBanner/CatalogCourseModalBanner.jsx +++ b/src/components/catalogModalBanner/CatalogCourseModalBanner.jsx @@ -3,7 +3,9 @@ import PropTypes from 'prop-types'; import { Icon } from '@edx/paragon'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { BookOpen, EventNote, MoneyOutline } from '@edx/paragon/icons'; +import { + Book, BookOpen, EventNote, MoneyOutline, +} from '@edx/paragon/icons'; import messages from './CatalogCourseModalBanner.messages'; import { checkAvailability, @@ -41,13 +43,14 @@ function CatalogCourseModalBanner({ startDate, endDate, upcomingRuns, + execEd, }) { return (

- {coursePrice} + {coursePrice.split('.')[0]}
{intl.formatMessage( @@ -56,21 +59,39 @@ function CatalogCourseModalBanner({
/
- {checkSubscriptions(courseAssociatedCatalogs) && ( -
-
- - {intl.formatMessage( - messages['CatalogCourseModalBanner.bannerCatalogText'], - )} -
-
- {checkSubscriptions(courseAssociatedCatalogs)} + {checkSubscriptions(courseAssociatedCatalogs) && !execEd && ( + <> +
+
+ + {intl.formatMessage( + messages['CatalogCourseModalBanner.bannerCatalogText'], + )} +
+
+ {checkSubscriptions(courseAssociatedCatalogs)} +
-
+
/
+ )} - {checkSubscriptions(courseAssociatedCatalogs) && ( -
/
+ {execEd && ( + <> +
+
+ + {intl.formatMessage( + messages['CatalogCourseModalBanner.bannerExecEdText'], + )} +
+
+ {intl.formatMessage( + messages['CatalogCourseModalBanner.bannerExecEdSubtext'], + )} +
+
+
/
+ )}
@@ -91,6 +112,7 @@ CatalogCourseModalBanner.defaultProps = { startDate: '', endDate: '', upcomingRuns: 0, + execEd: false, }; CatalogCourseModalBanner.propTypes = { @@ -100,6 +122,7 @@ CatalogCourseModalBanner.propTypes = { startDate: PropTypes.string, endDate: PropTypes.string, upcomingRuns: PropTypes.number, + execEd: PropTypes.bool, }; export default injectIntl(CatalogCourseModalBanner); diff --git a/src/components/catalogModalBanner/CatalogCourseModalBanner.messages.js b/src/components/catalogModalBanner/CatalogCourseModalBanner.messages.js index f9ed2d09..bdc601c0 100644 --- a/src/components/catalogModalBanner/CatalogCourseModalBanner.messages.js +++ b/src/components/catalogModalBanner/CatalogCourseModalBanner.messages.js @@ -6,6 +6,11 @@ const messages = defineMessages({ defaultMessage: 'Included with subscription', description: 'Text for modal subheader banner for catalog inclusion.', }, + 'CatalogCourseModalBanner.bannerExecEdText': { + id: 'CatalogCourseModalBanner.bannerExecEdText', + defaultMessage: 'Executive Education', + description: 'Text for modal subheader banner for exec ed courses.', + }, 'CatalogCourseModalBanner.bannerPriceText': { id: 'CatalogCourseModalBanner.bannerPriceText', defaultMessage: 'A la carte course price', @@ -21,6 +26,11 @@ const messages = defineMessages({ defaultMessage: 'Skill-building selection', description: 'Text for modal subheader banner for courses.', }, + 'CatalogCourseModalBanner.bannerExecEdSubtext': { + id: 'CatalogCourseModalBanner.bannerExecEdSubtext', + defaultMessage: 'Immersive, instructor-led course', + description: 'Text for modal subheader banner for exec ed courses.', + }, }); export default messages; diff --git a/src/components/catalogSearchResults/CatalogSearchResults.jsx b/src/components/catalogSearchResults/CatalogSearchResults.jsx index cd11d74d..42d81ffb 100644 --- a/src/components/catalogSearchResults/CatalogSearchResults.jsx +++ b/src/components/catalogSearchResults/CatalogSearchResults.jsx @@ -249,7 +249,7 @@ export function BaseCatalogSearchResults({ Header: TABLE_HEADERS.price, accessor: 'entitlements', Cell: ({ row }) => (row.values.entitlements[0].price - ? `$${row.values.entitlements[0].price}` + ? `$${Math.trunc(row.values.entitlements[0].price)}` : null), }, { @@ -414,6 +414,7 @@ export function BaseCatalogSearchResults({ isOpen={isCourse} onClose={() => setSelectedCourse(null)} selectedCourse={selectedCourse} + isExecEdType={isExecEdType} /> )} {isProgramType && ( diff --git a/src/components/catalogSearchResults/CatalogSearchResults.test.jsx b/src/components/catalogSearchResults/CatalogSearchResults.test.jsx index cca17ae4..25e0ed1a 100644 --- a/src/components/catalogSearchResults/CatalogSearchResults.test.jsx +++ b/src/components/catalogSearchResults/CatalogSearchResults.test.jsx @@ -151,7 +151,7 @@ const searchResultsExecEd = { availability: ['Available Now'], course_keys: [], content_type: EXECUTIVE_EDUCATION_2U_COURSE_TYPE, - entitlements: [{ price: '100' }], + entitlements: [{ price: '100.00' }], advertised_course_run: { start: '2020-01-24T05:00:00Z', end: '2080-01-01T17:00:00Z', diff --git a/src/components/courseCard/CourseCard.jsx b/src/components/courseCard/CourseCard.jsx index f2f9d0a0..49eb3e8a 100644 --- a/src/components/courseCard/CourseCard.jsx +++ b/src/components/courseCard/CourseCard.jsx @@ -29,7 +29,12 @@ function CourseCard({ priceText = rowPrice != null ? `$${rowPrice.toString()}` : 'N/A'; } else { [rowPrice] = entitlements || [null]; - priceText = rowPrice != null ? `$${rowPrice.price?.toString()}` : 'N/A'; + priceText = rowPrice != null ? `$${Math.trunc(rowPrice.price)?.toString()}` : 'N/A'; + } + + let pacingType = 'NA'; + if (advertised_course_run) { + pacingType = advertised_course_run.pacing_type === 'self_paced' ? 'Self paced' : 'Instructor led'; } const imageSrc = card_image_url || defaultCardHeader; @@ -52,7 +57,7 @@ function CourseCard({

- {priceText} • {advertised_course_run ? advertised_course_run.pacing_type?.replace('_', ' ') : 'NA'} + {priceText} • {pacingType}

{enterprise_catalog_query_titles.includes( diff --git a/src/components/courseCard/CourseCard.test.jsx b/src/components/courseCard/CourseCard.test.jsx index 4a382d0d..aee1b495 100644 --- a/src/components/courseCard/CourseCard.test.jsx +++ b/src/components/courseCard/CourseCard.test.jsx @@ -35,7 +35,7 @@ const execEdData = { original_image_url: '', enterprise_catalog_query_titles: TEST_CATALOG, advertised_course_run: { pacing_type: 'instructor_paced' }, - entitlements: [{ price: '999' }], + entitlements: [{ price: '999.00' }], }; const execEdProps = { @@ -57,7 +57,7 @@ describe('Course card works as expected', () => { expect( screen.queryByText(defaultProps.original.partners[0].name), ).toBeInTheDocument(); - expect(screen.queryByText('$100 • self paced')).toBeInTheDocument(); + expect(screen.queryByText('$100 • Self paced')).toBeInTheDocument(); expect(screen.queryByText('Business')).toBeInTheDocument(); }); test('test card renders default image', async () => { @@ -80,7 +80,8 @@ describe('Course card works as expected', () => { , ); expect(screen.queryByText(execEdProps.original.title)).toBeInTheDocument(); - expect(screen.queryByText('$999 • instructor paced')).toBeInTheDocument(); + // price decimal should be truncated + expect(screen.queryByText('$999 • Instructor led')).toBeInTheDocument(); expect(screen.queryByText('Business')).toBeInTheDocument(); }); });