From 653b70629d0145a18d0d499f849721cafeefe387 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Fri, 24 Oct 2025 11:58:28 -0600 Subject: [PATCH 1/3] feat: creating components for the enrollment summary section --- src/courseInfo/CourseInfoPage.tsx | 15 +- .../EnrollmentCounter.test.tsx | 295 ++++++++++++++++++ .../EnrollmentSummary/EnrollmentCounter.tsx | 32 ++ .../EnrollmentSummary.test.tsx | 269 ++++++++++++++++ .../EnrollmentSummary/EnrollmentSummary.tsx | 69 ++++ .../components/EnrollmentSummary/index.scss | 7 + .../components/EnrollmentSummary/index.ts | 2 + .../components/EnrollmentSummary/messages.ts | 37 +++ .../EnrollmentSummary/utils.test.ts | 24 ++ .../components/EnrollmentSummary/utils.ts | 7 + 10 files changed, 754 insertions(+), 3 deletions(-) create mode 100644 src/courseInfo/components/EnrollmentSummary/EnrollmentCounter.test.tsx create mode 100644 src/courseInfo/components/EnrollmentSummary/EnrollmentCounter.tsx create mode 100644 src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.test.tsx create mode 100644 src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx create mode 100644 src/courseInfo/components/EnrollmentSummary/index.scss create mode 100644 src/courseInfo/components/EnrollmentSummary/index.ts create mode 100644 src/courseInfo/components/EnrollmentSummary/messages.ts create mode 100644 src/courseInfo/components/EnrollmentSummary/utils.test.ts create mode 100644 src/courseInfo/components/EnrollmentSummary/utils.ts diff --git a/src/courseInfo/CourseInfoPage.tsx b/src/courseInfo/CourseInfoPage.tsx index 1213dd7c..c2b79ffb 100644 --- a/src/courseInfo/CourseInfoPage.tsx +++ b/src/courseInfo/CourseInfoPage.tsx @@ -1,13 +1,22 @@ -import { Container } from '@openedx/paragon'; import { PendingTasks } from '@src/components/PendingTasks'; import { GeneralCourseInfo } from '@src/courseInfo/components/generalCourseInfo'; +import { EnrollmentSummary } from './components/EnrollmentSummary'; const CourseInfoPage = () => { + // TODO: replace this mock data with real data from API + const mockEnrollmentCounts = { + total: 3640, + staffAndAdmins: 10, + learners: 3630, + verified: 2400, + audit: 1230, + }; return ( - +
+ - +
); }; diff --git a/src/courseInfo/components/EnrollmentSummary/EnrollmentCounter.test.tsx b/src/courseInfo/components/EnrollmentSummary/EnrollmentCounter.test.tsx new file mode 100644 index 00000000..2dddafed --- /dev/null +++ b/src/courseInfo/components/EnrollmentSummary/EnrollmentCounter.test.tsx @@ -0,0 +1,295 @@ +import { render, screen } from '@testing-library/react'; +import { Verified, Person, Group } from '@openedx/paragon/icons'; +import { EnrollmentCounter } from './'; + +describe('EnrollmentCounter', () => { + it('displays the enrollment label and count', () => { + render( + + ); + + expect(screen.getByText('Total Students')).toBeInTheDocument(); + expect(screen.getByText('1,500')).toBeInTheDocument(); + }); + + it('formats numbers with thousands separators', () => { + render( + + ); + + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getByText('3,640')).toBeInTheDocument(); + }); + + it('displays and checks an SVG icon when provided', () => { + render( + } + /> + ); + + expect(screen.getByText('Verified Students')).toBeInTheDocument(); + expect(screen.getByText('410')).toBeInTheDocument(); + + const svgElement = document.querySelector('svg'); + expect(svgElement).toBeInTheDocument(); + }); + + it('renders without an icon when not provided', () => { + render( + + ); + + expect(screen.getByText('Audit Students')).toBeInTheDocument(); + expect(screen.getByText('3,230')).toBeInTheDocument(); + + // Should not have any icon elements + const svgElement = document.querySelector('svg'); + expect(svgElement).not.toBeInTheDocument(); + }); + + it('displays different types of enrollment data correctly', () => { + const { rerender } = render( + + ); + + expect(screen.getByText('Staff and Admins')).toBeInTheDocument(); + expect(screen.getByText('10')).toBeInTheDocument(); + + rerender( + } + /> + ); + + expect(screen.getByText('Learners')).toBeInTheDocument(); + expect(screen.getByText('3,630')).toBeInTheDocument(); + }); + + it('handles large numbers correctly with comma formatting', () => { + render( + + ); + + expect(screen.getByText('Total Enrollments')).toBeInTheDocument(); + expect(screen.getByText('1,234,567')).toBeInTheDocument(); + }); + + it('handles small numbers without unnecessary formatting', () => { + render( + + ); + + expect(screen.getByText('Admin Users')).toBeInTheDocument(); + expect(screen.getByText('1')).toBeInTheDocument(); + }); + + it('displays numbers in the hundreds correctly', () => { + render( + + ); + + expect(screen.getByText('Premium Users')).toBeInTheDocument(); + expect(screen.getByText('150')).toBeInTheDocument(); + }); + + it('maintains proper visual hierarchy with label above count', () => { + render( + + ); + + const label = screen.getByText('Total Active'); + const count = screen.getByText('999'); + + // Label should appear before count in the DOM + expect(label.compareDocumentPosition(count) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + }); + + it('handles zero count correctly', () => { + render( + + ); + + expect(screen.getByText('Pending Approvals')).toBeInTheDocument(); + expect(screen.getByText('0')).toBeInTheDocument(); + }); + + it('works with different paragon icon types', () => { + const { rerender } = render( + } + /> + ); + + expect(screen.getByText('Verified Users')).toBeInTheDocument(); + let svgElement = document.querySelector('svg'); + expect(svgElement).toBeInTheDocument(); + + rerender( + } + /> + ); + + expect(screen.getByText('Regular Users')).toBeInTheDocument(); + svgElement = document.querySelector('svg'); + expect(svgElement).toBeInTheDocument(); + }); + + it('is accessible to screen readers', () => { + render( + } + /> + ); + + // Text should be accessible and visible + expect(screen.getByText('Verified Certificates')).toBeVisible(); + expect(screen.getByText('125')).toBeVisible(); + + // Content should be readable by screen readers + const labelElement = screen.getByText('Verified Certificates'); + const countElement = screen.getByText('125'); + + expect(labelElement).not.toHaveAttribute('aria-hidden'); + expect(countElement).not.toHaveAttribute('aria-hidden'); + }); + + it('displays complete enrollment information as a cohesive unit', () => { + render( + } + /> + ); + + // All elements should be present and visible together + expect(screen.getByText('Premium Members')).toBeVisible(); + expect(screen.getByText('1,250')).toBeVisible(); + + // The container should hold all related information + const container = screen.getByText('Premium Members').closest('div'); + expect(container).toContainElement(screen.getByText('1,250')); + }); + + it('handles various enrollment scenarios users might see', () => { + const scenarios = [ + { label: 'All Enrollments', count: '3640' }, + { label: 'Staff and Admins', count: '10' }, + { label: 'Learners', count: '3630' }, + { label: 'Verified', count: '410' }, + { label: 'Audit', count: '3230' }, + ]; + + scenarios.forEach(({ label, count }) => { + const { unmount } = render( + : undefined} + /> + ); + + expect(screen.getByText(label)).toBeInTheDocument(); + expect(screen.getByText(count === '3640' ? '3,640' + : count === '3630' ? '3,630' + : count === '3230' ? '3,230' : count)).toBeInTheDocument(); + + if (label === 'Verified') { + const svgElement = document.querySelector('svg'); + expect(svgElement).toBeInTheDocument(); + } + + unmount(); + }); + }); + + it('provides clear visual distinction between label and count', () => { + render( + + ); + + const label = screen.getByText('Course Participants'); + const count = screen.getByText('2,500'); + + // Both should be visible but with different styling + expect(label).toBeVisible(); + expect(count).toBeVisible(); + + // They should be in separate elements + expect(label.tagName).toBe('P'); + expect(count.tagName).toBe('P'); + }); + + it('handles edge case of very large numbers', () => { + render( + + ); + + expect(screen.getByText('Global Users')).toBeInTheDocument(); + expect(screen.getByText('10,000,000')).toBeInTheDocument(); + }); + + it('displays icon and text content together when both are provided', () => { + render( + } + /> + ); + + const container = screen.getByText('Certified Learners').closest('div'); + + // Should contain both text elements and icon + expect(container).toContainElement(screen.getByText('Certified Learners')); + expect(container).toContainElement(screen.getByText('850')); + + const svgElement = document.querySelector('svg'); + expect(svgElement).toBeInTheDocument(); + }); +}); diff --git a/src/courseInfo/components/EnrollmentSummary/EnrollmentCounter.tsx b/src/courseInfo/components/EnrollmentSummary/EnrollmentCounter.tsx new file mode 100644 index 00000000..747c8c1d --- /dev/null +++ b/src/courseInfo/components/EnrollmentSummary/EnrollmentCounter.tsx @@ -0,0 +1,32 @@ +import React, { FC } from 'react'; +import { formatNumberWithCommas } from './utils'; + +interface EnrollmentCounterProps { + label: string, + count: string, + icon?: React.ReactNode, +} + +const EnrollmentCounter: FC = (props) => { + const renderCounter = () => { + return (

{formatNumberWithCommas(props.count)}

); + }; + + const renderCounterWithIcon = () => { + return ( +
+ {props.icon} + {renderCounter()} +
+ ); + }; + + return ( +
+

{props.label}

+ { props.icon ? renderCounterWithIcon() : renderCounter() } +
+ ); +}; + +export { EnrollmentCounter }; diff --git a/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.test.tsx b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.test.tsx new file mode 100644 index 00000000..9cce1b78 --- /dev/null +++ b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.test.tsx @@ -0,0 +1,269 @@ +import { screen } from '@testing-library/react'; +import { EnrollmentSummary } from './EnrollmentSummary'; +import { renderWithIntl } from '../../../testUtils'; + +describe('EnrollmentSummary', () => { + const mockEnrollmentCounts = { + total: 5000, + staffAndAdmins: 25, + learners: 4975, + verified: 3500, + audit: 1475, + }; + + it('displays the enrollment summary title', () => { + renderWithIntl(); + + expect(screen.getByRole('heading', { name: /course enrollment/i })).toBeInTheDocument(); + }); + + it('displays total enrollment count with proper formatting', () => { + renderWithIntl(); + + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getByText('5,000')).toBeInTheDocument(); + }); + + it('displays Staff / Admin count when provided', () => { + renderWithIntl(); + + expect(screen.getByText('Staff / Admin')).toBeInTheDocument(); + expect(screen.getByText('25')).toBeInTheDocument(); + }); + + it('displays learners count when provided', () => { + renderWithIntl(); + + expect(screen.getByText('Learners')).toBeInTheDocument(); + expect(screen.getByText('4,975')).toBeInTheDocument(); + }); + + it('displays verified count with svg icon when provided', () => { + renderWithIntl(); + + expect(screen.getByText('Verified')).toBeInTheDocument(); + expect(screen.getByText('3,500')).toBeInTheDocument(); + + const svgElements = document.querySelectorAll('svg'); + expect(svgElements.length).toBeGreaterThan(0); + }); + + it('displays audit count when provided', () => { + renderWithIntl(); + + expect(screen.getByText('Audit')).toBeInTheDocument(); + expect(screen.getByText('1,475')).toBeInTheDocument(); + }); + + it('does not display Staff / Admin section when not provided', () => { + const countsWithoutStaff = { + total: 3000, + learners: 3000, + verified: 2000, + audit: 1000, + }; + + renderWithIntl(); + + expect(screen.queryByText('Staff / Admin')).not.toBeInTheDocument(); + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getByText('Learners')).toBeInTheDocument(); + }); + + it('does not display learners section when not provided', () => { + const countsWithoutLearners = { + total: 500, + staffAndAdmins: 50, + verified: 400, + audit: 50, + }; + + renderWithIntl(); + + expect(screen.queryByText('Learners')).not.toBeInTheDocument(); + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getByText('Staff / Admin')).toBeInTheDocument(); + }); + + it('does not display verified section when not provided', () => { + const countsWithoutVerified = { + total: 2000, + staffAndAdmins: 20, + learners: 1980, + audit: 1980, + }; + + renderWithIntl(); + + expect(screen.queryByText('Verified')).not.toBeInTheDocument(); + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getByText('Learners')).toBeInTheDocument(); + }); + + it('does not display audit section when not provided', () => { + const countsWithoutAudit = { + total: 1500, + staffAndAdmins: 15, + learners: 1485, + verified: 1485, + }; + + renderWithIntl(); + + expect(screen.queryByText('Audit')).not.toBeInTheDocument(); + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getByText('Verified')).toBeInTheDocument(); + }); + + it('displays only total when other counts are not provided', () => { + const minimalCounts = { total: 100 }; + + renderWithIntl(); + + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getByText('100')).toBeInTheDocument(); + expect(screen.queryByText('Staff / Admin')).not.toBeInTheDocument(); + expect(screen.queryByText('Learners')).not.toBeInTheDocument(); + expect(screen.queryByText('Verified')).not.toBeInTheDocument(); + expect(screen.queryByText('Audit')).not.toBeInTheDocument(); + }); + + it('handles zero values correctly', () => { + const countsWithZeros = { + total: 0, + staffAndAdmins: 0, + learners: 0, + verified: 0, + audit: 0, + }; + + renderWithIntl(); + + // Should still display sections with zero values when provided + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getAllByText('0')).toHaveLength(1); // Only total should appear with 0 + }); + + it('displays enrollment data in proper visual order', () => { + renderWithIntl(); + + const allEnrollments = screen.getByText('All Enrollments'); + const staffAndAdmins = screen.getByText('Staff / Admin'); + const learners = screen.getByText('Learners'); + const verified = screen.getByText('Verified'); + const audit = screen.getByText('Audit'); + + // Check DOM order + expect(allEnrollments.compareDocumentPosition(staffAndAdmins) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect(staffAndAdmins.compareDocumentPosition(learners) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect(learners.compareDocumentPosition(verified) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect(verified.compareDocumentPosition(audit) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + }); + + it('displays large numbers with proper comma formatting', () => { + const largeCounts = { + total: 1234567, + staffAndAdmins: 1234, + learners: 1233333, + verified: 987654, + audit: 245913, + }; + + renderWithIntl(); + + expect(screen.getByText('1,234,567')).toBeInTheDocument(); + expect(screen.getByText('1,234')).toBeInTheDocument(); + expect(screen.getByText('1,233,333')).toBeInTheDocument(); + expect(screen.getByText('987,654')).toBeInTheDocument(); + expect(screen.getByText('245,913')).toBeInTheDocument(); + }); + + it('is accessible to screen readers', () => { + renderWithIntl(); + + // Main heading should be accessible + const heading = screen.getByRole('heading', { name: /course enrollment/i }); + expect(heading).toBeInTheDocument(); + expect(heading).toBeVisible(); + + // All enrollment information should be visible and accessible + expect(screen.getByText('All Enrollments')).toBeVisible(); + expect(screen.getByText('Staff / Admin')).toBeVisible(); + expect(screen.getByText('Learners')).toBeVisible(); + expect(screen.getByText('Verified')).toBeVisible(); + expect(screen.getByText('Audit')).toBeVisible(); + + // All counts should be visible + expect(screen.getByText('5,000')).toBeVisible(); + expect(screen.getByText('25')).toBeVisible(); + expect(screen.getByText('4,975')).toBeVisible(); + expect(screen.getByText('3,500')).toBeVisible(); + expect(screen.getByText('1,475')).toBeVisible(); + }); + + it('maintains proper heading hierarchy', () => { + renderWithIntl(); + + const heading = screen.getByRole('heading', { level: 3 }); + expect(heading).toHaveTextContent('Course Enrollment'); + }); + + it('displays enrollment counters in horizontal layout', () => { + const { container } = renderWithIntl(); + + // Check for horizontal stack layout + const stackElement = container.querySelector('.d-flex'); + expect(stackElement).toBeInTheDocument(); + }); + + it('shows verified enrollment with distinctive icon presentation', () => { + renderWithIntl(); + + // Verified section should have both text and icon + expect(screen.getByText('Verified')).toBeInTheDocument(); + expect(screen.getByText('3,500')).toBeInTheDocument(); + + // Should have an SVG icon for verified + const svgElements = document.querySelectorAll('svg'); + expect(svgElements.length).toBeGreaterThan(0); + }); + + it('handles edge case with only verified enrollments', () => { + const verifiedOnlyCounts = { + total: 500, + verified: 500, + }; + + renderWithIntl(); + + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getByText('Verified')).toBeInTheDocument(); + expect(screen.getAllByText('500')).toHaveLength(2); + + // Should not show other sections + expect(screen.queryByText('Staff / Admin')).not.toBeInTheDocument(); + expect(screen.queryByText('Learners')).not.toBeInTheDocument(); + expect(screen.queryByText('Audit')).not.toBeInTheDocument(); + }); + + it('provides complete enrollment overview for course administrators', () => { + renderWithIntl(); + + // Should show comprehensive enrollment data + expect(screen.getByText('Course Enrollment')).toBeInTheDocument(); + + // All major enrollment categories should be visible + expect(screen.getByText('All Enrollments')).toBeInTheDocument(); + expect(screen.getByText('Staff / Admin')).toBeInTheDocument(); + expect(screen.getByText('Learners')).toBeInTheDocument(); + expect(screen.getByText('Verified')).toBeInTheDocument(); + expect(screen.getByText('Audit')).toBeInTheDocument(); + + // All counts should be properly formatted and visible + expect(screen.getByText('5,000')).toBeInTheDocument(); + expect(screen.getByText('25')).toBeInTheDocument(); + expect(screen.getByText('4,975')).toBeInTheDocument(); + expect(screen.getByText('3,500')).toBeInTheDocument(); + expect(screen.getByText('1,475')).toBeInTheDocument(); + }); +}); diff --git a/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx new file mode 100644 index 00000000..3e45e3d7 --- /dev/null +++ b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx @@ -0,0 +1,69 @@ +import { useIntl } from '@openedx/frontend-base'; +import { FC } from 'react'; +import messages from './messages'; +import { Col, Container, Row, Stack } from '@openedx/paragon'; +import { Verified } from '@openedx/paragon/icons'; +import { EnrollmentCounter } from './'; + +import './index.scss'; + +interface EnrollmentSummaryProps { + enrollmentCounts: { + total: number, + staffAndAdmins?: number, + learners?: number, + verified?: number, + audit?: number, + }, +}; + +const EnrollmentSummary: FC = ({ enrollmentCounts }) => { + const intl = useIntl(); + + return ( + + + +

{intl.formatMessage(messages.enrollmentSummaryTitle)}

+ +
+ + +
+ {enrollmentCounts?.staffAndAdmins && ( + + )} + {enrollmentCounts?.learners && ( + + )} + {(enrollmentCounts?.learners ?? enrollmentCounts?.verified) && ( +
+ )} + {enrollmentCounts?.verified && ( + } + /> + )} + {enrollmentCounts?.audit && ( + + )} + + + ); +}; + +export { EnrollmentSummary }; diff --git a/src/courseInfo/components/EnrollmentSummary/index.scss b/src/courseInfo/components/EnrollmentSummary/index.scss new file mode 100644 index 00000000..442af573 --- /dev/null +++ b/src/courseInfo/components/EnrollmentSummary/index.scss @@ -0,0 +1,7 @@ +@use "@openedx/paragon/scss/core/core" as paragon; + +.vertical-separator { + border-left: 1px solid paragon.$light-400; + height: auto; + align-self: stretch; +} \ No newline at end of file diff --git a/src/courseInfo/components/EnrollmentSummary/index.ts b/src/courseInfo/components/EnrollmentSummary/index.ts new file mode 100644 index 00000000..f56ed0f1 --- /dev/null +++ b/src/courseInfo/components/EnrollmentSummary/index.ts @@ -0,0 +1,2 @@ +export { EnrollmentSummary } from './EnrollmentSummary'; +export { EnrollmentCounter } from './EnrollmentCounter'; diff --git a/src/courseInfo/components/EnrollmentSummary/messages.ts b/src/courseInfo/components/EnrollmentSummary/messages.ts new file mode 100644 index 00000000..59e9de38 --- /dev/null +++ b/src/courseInfo/components/EnrollmentSummary/messages.ts @@ -0,0 +1,37 @@ +import { defineMessages } from '@openedx/frontend-base'; + +const messages = defineMessages({ + + enrollmentSummaryTitle: { + id: 'instruct.courseInfo.enrollmentSummary.title', + defaultMessage: 'Course Enrollment', + description: 'Title for the enrollment summary section', + }, + allEnrollmentsLabel: { + id: 'instruct.courseInfo.enrollmentSummary.allEnrollments', + defaultMessage: 'All Enrollments', + description: 'Label for all enrollments count', + }, + staffAndAdminsLabel: { + id: 'instruct.courseInfo.enrollmentSummary.staffAndAdmins', + defaultMessage: 'Staff / Admin', + description: 'Label for staff and admins count', + }, + learnersLabel: { + id: 'instruct.courseInfo.enrollmentSummary.learners', + defaultMessage: 'Learners', + description: 'Label for learners count', + }, + verifiedLabel: { + id: 'instruct.courseInfo.enrollmentSummary.verified', + defaultMessage: 'Verified', + description: 'Label for verified enrollments count', + }, + auditLabel: { + id: 'instruct.courseInfo.enrollmentSummary.audit', + defaultMessage: 'Audit', + description: 'Label for audit enrollments count', + }, +}); + +export default messages; diff --git a/src/courseInfo/components/EnrollmentSummary/utils.test.ts b/src/courseInfo/components/EnrollmentSummary/utils.test.ts new file mode 100644 index 00000000..b23eebe1 --- /dev/null +++ b/src/courseInfo/components/EnrollmentSummary/utils.test.ts @@ -0,0 +1,24 @@ +import { formatNumberWithCommas } from './utils'; + +describe('formatNumberWithCommas', () => { + it('formats valid numbers with commas', () => { + expect(formatNumberWithCommas('1000')).toBe('1,000'); + expect(formatNumberWithCommas('1234567')).toBe('1,234,567'); + }); + + it('removes existing commas and spaces before formatting', () => { + expect(formatNumberWithCommas('1,000')).toBe('1,000'); + expect(formatNumberWithCommas('1 000')).toBe('1,000'); + expect(formatNumberWithCommas('1, 000')).toBe('1,000'); + }); + + it('returns original string for invalid numbers', () => { + expect(formatNumberWithCommas('abc')).toBe('abc'); + expect(formatNumberWithCommas('12abc')).toBe('12abc'); + }); + + it('handles small numbers without commas', () => { + expect(formatNumberWithCommas('123')).toBe('123'); + expect(formatNumberWithCommas('0')).toBe('0'); + }); +}); diff --git a/src/courseInfo/components/EnrollmentSummary/utils.ts b/src/courseInfo/components/EnrollmentSummary/utils.ts new file mode 100644 index 00000000..7cfd0fe3 --- /dev/null +++ b/src/courseInfo/components/EnrollmentSummary/utils.ts @@ -0,0 +1,7 @@ +export const formatNumberWithCommas = (numberString: string): string => { + const cleanNumber = numberString.replace(/[,\s]/g, ''); + if (isNaN(Number(cleanNumber))) { + return numberString; + } + return Number(cleanNumber).toLocaleString('en-US'); +}; From 7dbd9621edaa9f642b21e79e29e26dbec677c59d Mon Sep 17 00:00:00 2001 From: diana-villalvazo-wgu Date: Thu, 19 Feb 2026 00:02:57 -0600 Subject: [PATCH 2/3] style: use bootstrap classes instead of scss --- .../components/EnrollmentSummary/EnrollmentSummary.tsx | 6 ++---- src/courseInfo/components/EnrollmentSummary/index.scss | 7 ------- 2 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 src/courseInfo/components/EnrollmentSummary/index.scss diff --git a/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx index 3e45e3d7..50a3c70b 100644 --- a/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx +++ b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx @@ -5,8 +5,6 @@ import { Col, Container, Row, Stack } from '@openedx/paragon'; import { Verified } from '@openedx/paragon/icons'; import { EnrollmentCounter } from './'; -import './index.scss'; - interface EnrollmentSummaryProps { enrollmentCounts: { total: number, @@ -32,7 +30,7 @@ const EnrollmentSummary: FC = ({ enrollmentCounts }) => label={intl.formatMessage(messages.allEnrollmentsLabel)} count={enrollmentCounts.total.toString()} /> -
+
{enrollmentCounts?.staffAndAdmins && ( = ({ enrollmentCounts }) => /> )} {(enrollmentCounts?.learners ?? enrollmentCounts?.verified) && ( -
+
)} {enrollmentCounts?.verified && ( Date: Thu, 19 Feb 2026 23:01:47 -0600 Subject: [PATCH 3/3] feat: add backend integration, fix tests --- src/courseInfo/CourseInfoPage.tsx | 24 +- .../EnrollmentSummary.test.tsx | 335 ++++++++++-------- .../EnrollmentSummary/EnrollmentSummary.tsx | 99 +++--- .../components/EnrollmentSummary/messages.ts | 54 ++- 4 files changed, 305 insertions(+), 207 deletions(-) diff --git a/src/courseInfo/CourseInfoPage.tsx b/src/courseInfo/CourseInfoPage.tsx index c2b79ffb..b06c0cf3 100644 --- a/src/courseInfo/CourseInfoPage.tsx +++ b/src/courseInfo/CourseInfoPage.tsx @@ -2,22 +2,12 @@ import { PendingTasks } from '@src/components/PendingTasks'; import { GeneralCourseInfo } from '@src/courseInfo/components/generalCourseInfo'; import { EnrollmentSummary } from './components/EnrollmentSummary'; -const CourseInfoPage = () => { - // TODO: replace this mock data with real data from API - const mockEnrollmentCounts = { - total: 3640, - staffAndAdmins: 10, - learners: 3630, - verified: 2400, - audit: 1230, - }; - return ( -
- - - -
- ); -}; +const CourseInfoPage = () => ( + <> + + + + +); export default CourseInfoPage; diff --git a/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.test.tsx b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.test.tsx index 9cce1b78..9cdd3fc2 100644 --- a/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.test.tsx +++ b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.test.tsx @@ -1,157 +1,186 @@ import { screen } from '@testing-library/react'; import { EnrollmentSummary } from './EnrollmentSummary'; import { renderWithIntl } from '../../../testUtils'; +import { useCourseInfo } from '@src/data/apiHook'; +import messages from './messages'; -describe('EnrollmentSummary', () => { - const mockEnrollmentCounts = { +jest.mock('react-router-dom', () => ({ + useParams: () => ({ + courseId: 'course-v1:edX+DemoX+Demo_Course', + }), +})); + +jest.mock('@src/data/apiHook', () => ({ + useCourseInfo: jest.fn(), +})); + +const mockCounter = { + enrollmentCounts: { total: 5000, - staffAndAdmins: 25, - learners: 4975, verified: 3500, - audit: 1475, - }; + audit: 1500, + }, + staffCount: 25, + learnerCount: 4975, +}; + +describe('EnrollmentSummary', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); it('displays the enrollment summary title', () => { - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + renderWithIntl(); expect(screen.getByRole('heading', { name: /course enrollment/i })).toBeInTheDocument(); }); it('displays total enrollment count with proper formatting', () => { - renderWithIntl(); - - expect(screen.getByText('All Enrollments')).toBeInTheDocument(); - expect(screen.getByText('5,000')).toBeInTheDocument(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + renderWithIntl(); + screen.debug(); + + expect(screen.getByText(messages.allEnrollmentsLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(mockCounter.enrollmentCounts.total.toLocaleString())).toBeInTheDocument(); }); it('displays Staff / Admin count when provided', () => { - renderWithIntl(); - - expect(screen.getByText('Staff / Admin')).toBeInTheDocument(); - expect(screen.getByText('25')).toBeInTheDocument(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + renderWithIntl(); + + expect(screen.getByText(messages.staffAndAdminsLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(mockCounter.staffCount.toLocaleString())).toBeInTheDocument(); }); it('displays learners count when provided', () => { - renderWithIntl(); - - expect(screen.getByText('Learners')).toBeInTheDocument(); - expect(screen.getByText('4,975')).toBeInTheDocument(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + renderWithIntl(); + + expect(screen.getByText(messages.learnersLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(mockCounter.learnerCount.toLocaleString())).toBeInTheDocument(); }); it('displays verified count with svg icon when provided', () => { - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + renderWithIntl(); - expect(screen.getByText('Verified')).toBeInTheDocument(); - expect(screen.getByText('3,500')).toBeInTheDocument(); + expect(screen.getByText(messages.verified.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(mockCounter.enrollmentCounts.verified.toLocaleString())).toBeInTheDocument(); const svgElements = document.querySelectorAll('svg'); expect(svgElements.length).toBeGreaterThan(0); }); it('displays audit count when provided', () => { - renderWithIntl(); - - expect(screen.getByText('Audit')).toBeInTheDocument(); - expect(screen.getByText('1,475')).toBeInTheDocument(); - }); - - it('does not display Staff / Admin section when not provided', () => { - const countsWithoutStaff = { - total: 3000, - learners: 3000, - verified: 2000, - audit: 1000, - }; - - renderWithIntl(); - - expect(screen.queryByText('Staff / Admin')).not.toBeInTheDocument(); - expect(screen.getByText('All Enrollments')).toBeInTheDocument(); - expect(screen.getByText('Learners')).toBeInTheDocument(); - }); - - it('does not display learners section when not provided', () => { - const countsWithoutLearners = { - total: 500, - staffAndAdmins: 50, - verified: 400, - audit: 50, - }; - - renderWithIntl(); - - expect(screen.queryByText('Learners')).not.toBeInTheDocument(); - expect(screen.getByText('All Enrollments')).toBeInTheDocument(); - expect(screen.getByText('Staff / Admin')).toBeInTheDocument(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + renderWithIntl(); + + expect(screen.getByText(messages.audit.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(mockCounter.enrollmentCounts.audit.toLocaleString())).toBeInTheDocument(); }); it('does not display verified section when not provided', () => { const countsWithoutVerified = { total: 2000, - staffAndAdmins: 20, - learners: 1980, - audit: 1980, + audit: 2000, }; - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: { enrollmentCounts: countsWithoutVerified, staffCount: 20, learnerCount: 1980 }, + isLoading: false, + }); + + renderWithIntl(); - expect(screen.queryByText('Verified')).not.toBeInTheDocument(); - expect(screen.getByText('All Enrollments')).toBeInTheDocument(); - expect(screen.getByText('Learners')).toBeInTheDocument(); + expect(screen.queryByText(messages.verified.defaultMessage)).not.toBeInTheDocument(); + expect(screen.getByText(messages.allEnrollmentsLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(messages.learnersLabel.defaultMessage)).toBeInTheDocument(); }); it('does not display audit section when not provided', () => { const countsWithoutAudit = { total: 1500, - staffAndAdmins: 15, - learners: 1485, - verified: 1485, + verified: 1500, }; - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: { enrollmentCounts: countsWithoutAudit, staffCount: 15, learnerCount: 1485 }, + isLoading: false, + }); - expect(screen.queryByText('Audit')).not.toBeInTheDocument(); - expect(screen.getByText('All Enrollments')).toBeInTheDocument(); - expect(screen.getByText('Verified')).toBeInTheDocument(); + renderWithIntl(); + + expect(screen.queryByText(messages.audit.defaultMessage)).not.toBeInTheDocument(); + expect(screen.getByText(messages.allEnrollmentsLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(messages.verified.defaultMessage)).toBeInTheDocument(); }); it('displays only total when other counts are not provided', () => { const minimalCounts = { total: 100 }; + (useCourseInfo as jest.Mock).mockReturnValue({ + data: { enrollmentCounts: minimalCounts, staffCount: 0, learnerCount: 0 }, + isLoading: false, + }); - renderWithIntl(); + renderWithIntl(); - expect(screen.getByText('All Enrollments')).toBeInTheDocument(); - expect(screen.getByText('100')).toBeInTheDocument(); - expect(screen.queryByText('Staff / Admin')).not.toBeInTheDocument(); - expect(screen.queryByText('Learners')).not.toBeInTheDocument(); - expect(screen.queryByText('Verified')).not.toBeInTheDocument(); - expect(screen.queryByText('Audit')).not.toBeInTheDocument(); + expect(screen.getByText(messages.allEnrollmentsLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(minimalCounts.total.toLocaleString())).toBeInTheDocument(); + expect(screen.queryByText(messages.verified.defaultMessage)).not.toBeInTheDocument(); + expect(screen.queryByText(messages.audit.defaultMessage)).not.toBeInTheDocument(); }); it('handles zero values correctly', () => { const countsWithZeros = { total: 0, - staffAndAdmins: 0, - learners: 0, verified: 0, audit: 0, }; - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: { enrollmentCounts: countsWithZeros, staffCount: 0, learnerCount: 0 }, + isLoading: false, + }); + + renderWithIntl(); // Should still display sections with zero values when provided - expect(screen.getByText('All Enrollments')).toBeInTheDocument(); - expect(screen.getAllByText('0')).toHaveLength(1); // Only total should appear with 0 + expect(screen.getByText(messages.allEnrollmentsLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getAllByText('0')).toHaveLength(5); }); it('displays enrollment data in proper visual order', () => { - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); - const allEnrollments = screen.getByText('All Enrollments'); - const staffAndAdmins = screen.getByText('Staff / Admin'); - const learners = screen.getByText('Learners'); - const verified = screen.getByText('Verified'); - const audit = screen.getByText('Audit'); + renderWithIntl(); + + const allEnrollments = screen.getByText(messages.allEnrollmentsLabel.defaultMessage); + const staffAndAdmins = screen.getByText(messages.staffAndAdminsLabel.defaultMessage); + const learners = screen.getByText(messages.learnersLabel.defaultMessage); + const verified = screen.getByText(messages.verified.defaultMessage); + const audit = screen.getByText(messages.audit.defaultMessage); // Check DOM order expect(allEnrollments.compareDocumentPosition(staffAndAdmins) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); @@ -162,24 +191,35 @@ describe('EnrollmentSummary', () => { it('displays large numbers with proper comma formatting', () => { const largeCounts = { - total: 1234567, - staffAndAdmins: 1234, - learners: 1233333, - verified: 987654, - audit: 245913, + enrollmentCounts: { + total: 1234567, + verified: 987654, + audit: 245913, + }, + staffCount: 1234, + learnerCount: 1233333, }; - - renderWithIntl(); - - expect(screen.getByText('1,234,567')).toBeInTheDocument(); - expect(screen.getByText('1,234')).toBeInTheDocument(); - expect(screen.getByText('1,233,333')).toBeInTheDocument(); - expect(screen.getByText('987,654')).toBeInTheDocument(); - expect(screen.getByText('245,913')).toBeInTheDocument(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: largeCounts, + isLoading: false, + }); + + renderWithIntl(); + + expect(screen.getByText(largeCounts.enrollmentCounts.total.toLocaleString())).toBeInTheDocument(); + expect(screen.getByText(largeCounts.enrollmentCounts.verified.toLocaleString())).toBeInTheDocument(); + expect(screen.getByText(largeCounts.enrollmentCounts.audit.toLocaleString())).toBeInTheDocument(); + expect(screen.getByText(largeCounts.staffCount.toLocaleString())).toBeInTheDocument(); + expect(screen.getByText(largeCounts.learnerCount.toLocaleString())).toBeInTheDocument(); }); it('is accessible to screen readers', () => { - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + + renderWithIntl(); // Main heading should be accessible const heading = screen.getByRole('heading', { name: /course enrollment/i }); @@ -187,29 +227,33 @@ describe('EnrollmentSummary', () => { expect(heading).toBeVisible(); // All enrollment information should be visible and accessible - expect(screen.getByText('All Enrollments')).toBeVisible(); - expect(screen.getByText('Staff / Admin')).toBeVisible(); - expect(screen.getByText('Learners')).toBeVisible(); - expect(screen.getByText('Verified')).toBeVisible(); - expect(screen.getByText('Audit')).toBeVisible(); + expect(screen.getByText(messages.allEnrollmentsLabel.defaultMessage)).toBeVisible(); + expect(screen.getByText(messages.staffAndAdminsLabel.defaultMessage)).toBeVisible(); + expect(screen.getByText(messages.learnersLabel.defaultMessage)).toBeVisible(); + expect(screen.getByText(messages.verified.defaultMessage)).toBeVisible(); + expect(screen.getByText(messages.audit.defaultMessage)).toBeVisible(); // All counts should be visible - expect(screen.getByText('5,000')).toBeVisible(); - expect(screen.getByText('25')).toBeVisible(); - expect(screen.getByText('4,975')).toBeVisible(); - expect(screen.getByText('3,500')).toBeVisible(); - expect(screen.getByText('1,475')).toBeVisible(); + expect(screen.getByText(mockCounter.enrollmentCounts.total.toLocaleString())).toBeVisible(); + expect(screen.getByText(mockCounter.enrollmentCounts.verified.toLocaleString())).toBeVisible(); + expect(screen.getByText(mockCounter.enrollmentCounts.audit.toLocaleString())).toBeVisible(); + expect(screen.getByText(mockCounter.staffCount.toLocaleString())).toBeVisible(); + expect(screen.getByText(mockCounter.learnerCount.toLocaleString())).toBeVisible(); }); it('maintains proper heading hierarchy', () => { - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + renderWithIntl(); const heading = screen.getByRole('heading', { level: 3 }); expect(heading).toHaveTextContent('Course Enrollment'); }); it('displays enrollment counters in horizontal layout', () => { - const { container } = renderWithIntl(); + const { container } = renderWithIntl(); // Check for horizontal stack layout const stackElement = container.querySelector('.d-flex'); @@ -217,11 +261,15 @@ describe('EnrollmentSummary', () => { }); it('shows verified enrollment with distinctive icon presentation', () => { - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + renderWithIntl(); // Verified section should have both text and icon - expect(screen.getByText('Verified')).toBeInTheDocument(); - expect(screen.getByText('3,500')).toBeInTheDocument(); + expect(screen.getByText(messages.verified.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(mockCounter.enrollmentCounts.verified.toLocaleString())).toBeInTheDocument(); // Should have an SVG icon for verified const svgElements = document.querySelectorAll('svg'); @@ -230,40 +278,51 @@ describe('EnrollmentSummary', () => { it('handles edge case with only verified enrollments', () => { const verifiedOnlyCounts = { - total: 500, - verified: 500, + enrollmentCounts: { + total: 500, + verified: 500, + }, + staffCount: 0, + learnerCount: 0, }; - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: verifiedOnlyCounts, + isLoading: false, + }); + + renderWithIntl(); - expect(screen.getByText('All Enrollments')).toBeInTheDocument(); - expect(screen.getByText('Verified')).toBeInTheDocument(); - expect(screen.getAllByText('500')).toHaveLength(2); + expect(screen.getByText(messages.allEnrollmentsLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(messages.verified.defaultMessage)).toBeInTheDocument(); + expect(screen.getAllByText(verifiedOnlyCounts.enrollmentCounts.verified.toLocaleString())).toHaveLength(2); // Should not show other sections - expect(screen.queryByText('Staff / Admin')).not.toBeInTheDocument(); - expect(screen.queryByText('Learners')).not.toBeInTheDocument(); - expect(screen.queryByText('Audit')).not.toBeInTheDocument(); + expect(screen.queryByText(messages.audit.defaultMessage)).not.toBeInTheDocument(); }); it('provides complete enrollment overview for course administrators', () => { - renderWithIntl(); + (useCourseInfo as jest.Mock).mockReturnValue({ + data: mockCounter, + isLoading: false, + }); + renderWithIntl(); // Should show comprehensive enrollment data - expect(screen.getByText('Course Enrollment')).toBeInTheDocument(); + expect(screen.getByText(messages.enrollmentSummaryTitle.defaultMessage)).toBeInTheDocument(); // All major enrollment categories should be visible - expect(screen.getByText('All Enrollments')).toBeInTheDocument(); - expect(screen.getByText('Staff / Admin')).toBeInTheDocument(); - expect(screen.getByText('Learners')).toBeInTheDocument(); - expect(screen.getByText('Verified')).toBeInTheDocument(); - expect(screen.getByText('Audit')).toBeInTheDocument(); + expect(screen.getByText(messages.allEnrollmentsLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(messages.staffAndAdminsLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(messages.learnersLabel.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(messages.verified.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(messages.audit.defaultMessage)).toBeInTheDocument(); // All counts should be properly formatted and visible - expect(screen.getByText('5,000')).toBeInTheDocument(); - expect(screen.getByText('25')).toBeInTheDocument(); - expect(screen.getByText('4,975')).toBeInTheDocument(); - expect(screen.getByText('3,500')).toBeInTheDocument(); - expect(screen.getByText('1,475')).toBeInTheDocument(); + expect(screen.getByText(mockCounter.enrollmentCounts.total.toLocaleString())).toBeInTheDocument(); + expect(screen.getByText(mockCounter.staffCount.toLocaleString())).toBeInTheDocument(); + expect(screen.getByText(mockCounter.learnerCount.toLocaleString())).toBeInTheDocument(); + expect(screen.getByText(mockCounter.enrollmentCounts.verified.toLocaleString())).toBeInTheDocument(); + expect(screen.getByText(mockCounter.enrollmentCounts.audit.toLocaleString())).toBeInTheDocument(); }); }); diff --git a/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx index 50a3c70b..150891eb 100644 --- a/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx +++ b/src/courseInfo/components/EnrollmentSummary/EnrollmentSummary.tsx @@ -1,66 +1,65 @@ import { useIntl } from '@openedx/frontend-base'; -import { FC } from 'react'; import messages from './messages'; -import { Col, Container, Row, Stack } from '@openedx/paragon'; +import { Col, Row, Skeleton, Stack } from '@openedx/paragon'; import { Verified } from '@openedx/paragon/icons'; import { EnrollmentCounter } from './'; +import { useParams } from 'react-router-dom'; +import { useCourseInfo } from '@src/data/apiHook'; -interface EnrollmentSummaryProps { - enrollmentCounts: { - total: number, - staffAndAdmins?: number, - learners?: number, - verified?: number, - audit?: number, - }, -}; - -const EnrollmentSummary: FC = ({ enrollmentCounts }) => { +const EnrollmentSummary = () => { const intl = useIntl(); + const { courseId = '' } = useParams(); + const { data: courseInfo, isLoading } = useCourseInfo(courseId); + const { enrollmentCounts, staffCount = 0, learnerCount = 0 } = courseInfo ?? {}; return ( - + <>

{intl.formatMessage(messages.enrollmentSummaryTitle)}

- - -
- {enrollmentCounts?.staffAndAdmins && ( - - )} - {enrollmentCounts?.learners && ( - - )} - {(enrollmentCounts?.learners ?? enrollmentCounts?.verified) && ( -
- )} - {enrollmentCounts?.verified && ( - } - /> - )} - {enrollmentCounts?.audit && ( - - )} - - + { + isLoading ? ( + <> + + + + ) : ( + + +
+ + +
+ { + Object.entries(enrollmentCounts).map(([type, count]) => { + if (type === 'total' || count === undefined) { + return null; + } + return ( + : undefined} + /> + ); + }) + } + + ) + } + ); }; diff --git a/src/courseInfo/components/EnrollmentSummary/messages.ts b/src/courseInfo/components/EnrollmentSummary/messages.ts index 59e9de38..aaf14e05 100644 --- a/src/courseInfo/components/EnrollmentSummary/messages.ts +++ b/src/courseInfo/components/EnrollmentSummary/messages.ts @@ -22,16 +22,66 @@ const messages = defineMessages({ defaultMessage: 'Learners', description: 'Label for learners count', }, - verifiedLabel: { + verified: { id: 'instruct.courseInfo.enrollmentSummary.verified', defaultMessage: 'Verified', description: 'Label for verified enrollments count', }, - auditLabel: { + audit: { id: 'instruct.courseInfo.enrollmentSummary.audit', defaultMessage: 'Audit', description: 'Label for audit enrollments count', }, + masters: { + id: 'instruct.courseInfo.enrollmentSummary.masters', + defaultMessage: 'Master\'s', + description: 'Label for master\'s enrollments count', + }, + honor: { + id: 'instruct.courseInfo.enrollmentSummary.honor', + defaultMessage: 'Honor', + description: 'Label for honor enrollments count', + }, + professional: { + id: 'instruct.courseInfo.enrollmentSummary.professional', + defaultMessage: 'Professional', + description: 'Label for professional enrollments count', + }, + noIdProfessional: { + id: 'instruct.courseInfo.enrollmentSummary.noIdProfessional', + defaultMessage: 'No ID Professional', + description: 'Label for no ID professional enrollments count', + }, + credit: { + id: 'instruct.courseInfo.enrollmentSummary.credit', + defaultMessage: 'Credit', + description: 'Label for credit enrollments count', + }, + paidBootcamp: { + id: 'instruct.courseInfo.enrollmentSummary.paidBootcamp', + defaultMessage: 'Paid Bootcamp', + description: 'Label for paid bootcamp enrollments count', + }, + unpaidBootcamp: { + id: 'instruct.courseInfo.enrollmentSummary.unpaidBootcamp', + defaultMessage: 'Unpaid Bootcamp', + description: 'Label for unpaid bootcamp enrollments count', + }, + executiveEducation: { + id: 'instruct.courseInfo.enrollmentSummary.executiveEducation', + defaultMessage: 'Executive Education', + description: 'Label for executive education enrollments count', + }, + paidExecutiveEducation: { + id: 'instruct.courseInfo.enrollmentSummary.paidExecutiveEducation', + defaultMessage: 'Paid Executive Education', + description: 'Label for paid executive education enrollments count', + }, + unpaidExecutiveEducation: { + id: 'instruct.courseInfo.enrollmentSummary.unpaidExecutiveEducation', + defaultMessage: 'Unpaid Executive Education', + description: 'Label for unpaid executive education enrollments count', + }, }); export default messages;