diff --git a/src/course-home/progress-tab/ProgressTab.test.jsx b/src/course-home/progress-tab/ProgressTab.test.jsx
index 275a7aab..d2a46e49 100644
--- a/src/course-home/progress-tab/ProgressTab.test.jsx
+++ b/src/course-home/progress-tab/ProgressTab.test.jsx
@@ -161,6 +161,22 @@ describe('Progress Tab', () => {
expect(screen.getByText('B: 80%-90%'));
expect(screen.getByText('F: <80%'));
});
+
+ it('renders locked feature preview when user has locked content', async () => {
+ setTabData({
+ completion_summary: {
+ complete_count: 1,
+ incomplete_count: 1,
+ locked_count: 1,
+ },
+ });
+ await fetchAndRender();
+ expect(screen.getByText('locked feature')).toBeInTheDocument();
+ });
+ it('does not render locked feature preview when user does not have locked content', async () => {
+ await fetchAndRender();
+ expect(screen.queryByText('locked feature')).not.toBeInTheDocument();
+ });
});
describe('Grade Summary', () => {
@@ -200,21 +216,23 @@ describe('Progress Tab', () => {
});
describe('Certificate Status', () => {
- Object.defineProperty(window, 'matchMedia', {
- writable: true,
- value: jest.fn().mockImplementation(query => {
- const matches = !!(query === 'screen and (min-width: 768px)' || query === 'screen and (min-width: 992px)');
- return {
- matches,
- media: query,
- onchange: null,
- addListener: jest.fn(), // deprecated
- removeListener: jest.fn(), // deprecated
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- dispatchEvent: jest.fn(),
- };
- }),
+ beforeAll(() => {
+ Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation(query => {
+ const matches = !!(query === 'screen and (min-width: 992px)');
+ return {
+ matches,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ };
+ }),
+ });
});
describe('enrolled user', () => {
@@ -223,16 +241,12 @@ describe('Progress Tab', () => {
});
it('Displays text for nonPassing case when learner does not have a passing grade', async () => {
- setTabData({
- user_has_passing_grade: false,
- });
await fetchAndRender();
expect(screen.getByText('In order to qualify for a certificate, you must have a passing grade.')).toBeInTheDocument();
});
it('Displays text for inProgress case when more content is scheduled and the learner does not have a passing grade', async () => {
setTabData({
- user_has_passing_grade: false,
has_scheduled_content: true,
});
await fetchAndRender();
@@ -319,7 +333,6 @@ describe('Progress Tab', () => {
it('Displays nothing if audit only', async () => {
setTabData({
certificate_data: { cert_status: 'audit_passing' },
- verified_mode: null,
});
await fetchAndRender();
// Keep these queries in sync with "upgrade link" test above, so we don't end up checking for text that is
@@ -342,7 +355,6 @@ describe('Progress Tab', () => {
});
it('Does not display the certificate component if the user is not enrolled', async () => {
- setMetadata({ is_enrolled: false });
await fetchAndRender();
expect(screen.queryByTestId('certificate-status-component')).not.toBeInTheDocument();
});
diff --git a/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx
index 8876592b..c84b38fb 100644
--- a/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx
+++ b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx
@@ -151,10 +151,12 @@ function CertificateStatus({ intl }) {
return (
-
+
-
{header}
-
+
+
{header}
+
+
{body}
{buttonText && (buttonLocation || buttonAction) && (
diff --git a/src/course-home/progress-tab/certificate-status/messages.js b/src/course-home/progress-tab/certificate-status/messages.js
index 719940f2..f8253eba 100644
--- a/src/course-home/progress-tab/certificate-status/messages.js
+++ b/src/course-home/progress-tab/certificate-status/messages.js
@@ -2,79 +2,79 @@ import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
notPassingHeader: {
- id: 'notPassingHeader',
+ id: 'progress.certificateStatus.notPassingHeader',
defaultMessage: 'Certificate status',
},
notPassingBody: {
- id: 'notPassingBody',
+ id: 'progress.certificateStatus.notPassingBody',
defaultMessage: 'In order to qualify for a certificate, you must have a passing grade.',
},
inProgressHeader: {
- id: 'inProgressHeader',
+ id: 'progress.certificateStatus.inProgressHeader',
defaultMessage: 'More content is coming soon!',
},
inProgressBody: {
- id: 'inProgressBody',
+ id: 'progress.certificateStatus.inProgressBody',
defaultMessage: 'It looks like there is more content in this course that will be released in the future. Look out for email updates or check back on your course for when this content will be available.',
},
requestableHeader: {
- id: 'requestableHeader',
+ id: 'progress.certificateStatus.requestableHeader',
defaultMessage: 'Certificate status',
},
requestableBody: {
- id: 'requestableBody',
+ id: 'progress.certificateStatus.requestableBody',
defaultMessage: 'Congratulations, you qualified for a certificate! In order to access your certificate, request it below.',
},
requestableButton: {
- id: 'requestableButton',
+ id: 'progress.certificateStatus.requestableButton',
defaultMessage: 'Request certificate',
},
unverifiedHeader: {
- id: 'unverifiedHeader',
+ id: 'progress.certificateStatus.unverifiedHeader',
defaultMessage: 'Certificate status',
},
unverifiedButton: {
- id: 'unverifiedButton',
+ id: 'progress.certificateStatus.unverifiedButton',
defaultMessage: 'Verify ID',
},
unverifiedPendingBody: {
- id: 'courseCelebration.verificationPending',
+ id: 'progress.certificateStatus.courseCelebration.verificationPending',
defaultMessage: 'Your ID verification is pending and your certificate will be available once approved.',
},
downloadableHeader: {
- id: 'downloadableHeader',
+ id: 'progress.certificateStatus.downloadableHeader',
defaultMessage: 'Your certificate is available!',
},
downloadableBody: {
- id: 'downloadableBody',
+ id: 'progress.certificateStatus.downloadableBody',
defaultMessage: 'Showcase your accomplishment on LinkedIn or your resume today. You can download your certificate now and access it any time from your Dashboard and Profile.',
},
downloadableButton: {
- id: 'downloadableButton',
+ id: 'progress.certificateStatus.downloadableButton',
defaultMessage: 'Download my certificate',
},
viewableButton: {
- id: 'viewableButton',
+ id: 'progress.certificateStatus.viewableButton',
defaultMessage: 'View my certificate',
},
notAvailableHeader: {
- id: 'notAvailableHeader',
+ id: 'progress.certificateStatus.notAvailableHeader',
defaultMessage: 'Certificate status',
},
notAvailableBody: {
- id: 'notAvailableBody',
+ id: 'progress.certificateStatus.notAvailableBody',
defaultMessage: 'Your certificate will be available soon! After this course officially ends on {end_date}, you will receive an email notification with your certificate.',
},
upgradeHeader: {
- id: 'upgradeHeader',
+ id: 'progress.certificateStatus.upgradeHeader',
defaultMessage: 'Earn a certificate',
},
upgradeBody: {
- id: 'upgradeBody',
+ id: 'progress.certificateStatus.upgradeBody',
defaultMessage: 'You are in an audit track and do not qualify for a certificate. In order to work towards a certificate, upgrade your course today.',
},
upgradeButton: {
- id: 'upgradeButton',
+ id: 'progress.certificateStatus.upgradeButton',
defaultMessage: 'Upgrade now',
},
});
diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx
index ea655fc2..862d930e 100644
--- a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx
+++ b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx
@@ -5,6 +5,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useModel } from '../../../../generic/model-store';
import CourseGradeFooter from './CourseGradeFooter';
+import CourseGradeHeader from './CourseGradeHeader';
import GradeBar from './GradeBar';
import messages from '../messages';
@@ -15,6 +16,9 @@ function CourseGrade({ intl }) {
} = useSelector(state => state.courseHome);
const {
+ completionSummary: {
+ lockedCount,
+ },
gradingPolicy: {
gradeRange,
},
@@ -29,18 +33,24 @@ function CourseGrade({ intl }) {
passingGrade = Number(passingGrade.toFixed(0));
+ const isLocked = lockedCount > 0;
+ const applyLockedOverlay = isLocked ? 'locked-overlay' : '';
+
return (
-