diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx
index 40ad5baa..dcd00790 100644
--- a/src/course-home/outline-tab/OutlineTab.test.jsx
+++ b/src/course-home/outline-tab/OutlineTab.test.jsx
@@ -510,6 +510,8 @@ describe('Outline Tab', () => {
});
describe('Proctoring Info Panel', () => {
+ const onboardingReleaseDate = new Date();
+ onboardingReleaseDate.setDate(new Date().getDate() - 7);
it('appears', async () => {
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
@@ -521,6 +523,7 @@ describe('Outline Tab', () => {
onboarding_status: 'verified',
onboarding_link: 'test',
expiration_date: null,
+ onboarding_release_date: onboardingReleaseDate.toISOString(),
});
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
@@ -535,6 +538,7 @@ describe('Outline Tab', () => {
onboarding_status: 'rejected',
onboarding_link: 'test',
expiration_date: null,
+ onboarding_release_date: onboardingReleaseDate.toISOString(),
});
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
@@ -549,6 +553,7 @@ describe('Outline Tab', () => {
onboarding_status: 'submitted',
onboarding_link: 'test',
expiration_date: null,
+ onboarding_release_date: onboardingReleaseDate.toISOString(),
});
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
@@ -561,6 +566,7 @@ describe('Outline Tab', () => {
onboarding_status: 'second_review_required',
onboarding_link: 'test',
expiration_date: null,
+ onboarding_release_date: onboardingReleaseDate.toISOString(),
});
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
@@ -576,6 +582,7 @@ describe('Outline Tab', () => {
onboarding_status: 'other_course_approved',
onboarding_link: 'test',
expiration_date: expirationDate.toString(),
+ onboarding_release_date: onboardingReleaseDate.toISOString(),
});
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
@@ -591,6 +598,7 @@ describe('Outline Tab', () => {
onboarding_status: 'other_course_approved',
onboarding_link: 'test',
expiration_date: expirationDate.toString(),
+ onboarding_release_date: onboardingReleaseDate.toISOString(),
});
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
@@ -603,6 +611,7 @@ describe('Outline Tab', () => {
onboarding_status: '',
onboarding_link: 'test',
expiration_date: null,
+ onboarding_release_date: onboardingReleaseDate.toISOString(),
});
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
@@ -616,5 +625,40 @@ describe('Outline Tab', () => {
axiosMock.onGet(proctoringInfoUrl).reply(404);
expect(screen.queryByRole('link', { name: 'Review instructions and system requirements' })).not.toBeInTheDocument();
});
+
+ it('appears with a disabled link if onboarding not yet released', async () => {
+ const futureReleaseDate = new Date();
+ futureReleaseDate.setDate(new Date().getDate() + 7);
+ const expectedDateStr = new Intl.DateTimeFormat('en-US', {
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric',
+ }).format(futureReleaseDate);
+
+ axiosMock.onGet(proctoringInfoUrl).reply(200, {
+ onboarding_status: '',
+ onboarding_link: 'test',
+ expiration_date: null,
+ onboarding_release_date: futureReleaseDate.toISOString(),
+ });
+ await fetchAndRender();
+ await screen.findByText('This course contains proctored exams');
+ expect(screen.queryByText(`Onboarding Opens: ${expectedDateStr}`)).toBeInTheDocument();
+ });
+
+ it('appears and ignores a missing release date', async () => {
+ axiosMock.onGet(proctoringInfoUrl).reply(200, {
+ onboarding_status: 'verified',
+ onboarding_link: 'test',
+ expiration_date: null,
+ onboarding_release_date: onboardingReleaseDate.toISOString(),
+ });
+ await fetchAndRender();
+ await screen.findByText('This course contains proctored exams');
+ expect(screen.queryByRole('link', { name: 'Complete Onboarding' })).not.toBeInTheDocument();
+ expect(screen.queryByRole('link', { name: 'Review instructions and system requirements' })).toBeInTheDocument();
+ expect(screen.queryByText('You must complete the onboarding process prior to taking any proctored exam.')).not.toBeInTheDocument();
+ expect(screen.queryByText('Onboarding profile review, including identity verification, can take 2+ business days.')).not.toBeInTheDocument();
+ });
});
});
diff --git a/src/course-home/outline-tab/messages.js b/src/course-home/outline-tab/messages.js
index 0fb24ab2..051ac1d0 100644
--- a/src/course-home/outline-tab/messages.js
+++ b/src/course-home/outline-tab/messages.js
@@ -200,6 +200,10 @@ const messages = defineMessages({
id: 'learning.proctoringPanel.onboardingButton',
defaultMessage: 'Complete Onboarding',
},
+ proctoringOnboardingButtonNotOpen: {
+ id: 'learning.proctoringPanel.onboardingButtonNotOpen',
+ defaultMessage: 'Onboarding Opens: {releaseDate}',
+ },
proctoringReviewRequirementsButton: {
id: 'learning.proctoringPanel.reviewRequirementsButton',
defaultMessage: 'Review instructions and system requirements',
diff --git a/src/course-home/outline-tab/widgets/ProctoringInfoPanel.jsx b/src/course-home/outline-tab/widgets/ProctoringInfoPanel.jsx
index 3a01c916..134c8a96 100644
--- a/src/course-home/outline-tab/widgets/ProctoringInfoPanel.jsx
+++ b/src/course-home/outline-tab/widgets/ProctoringInfoPanel.jsx
@@ -12,6 +12,7 @@ import { getProctoringInfoData } from '../../data/api';
function ProctoringInfoPanel({ courseId, intl }) {
const [status, setStatus] = useState('');
const [link, setLink] = useState('');
+ const [releaseDate, setReleaseDate] = useState(null);
const [readableStatus, setReadableStatus] = useState('');
const readableStatuses = {
@@ -47,6 +48,14 @@ function ProctoringInfoPanel({ courseId, intl }) {
return !NO_SHOW_STATES.includes(examStatus);
}
+ function isNotYetReleased(examReleaseDate) {
+ if (!examReleaseDate) {
+ return false;
+ }
+ const now = new Date();
+ return now < examReleaseDate;
+ }
+
function getBorderClass() {
let borderClass = '';
if (readableStatus === readableStatuses.submitted) {
@@ -77,6 +86,7 @@ function ProctoringInfoPanel({ courseId, intl }) {
} else {
setReadableStatus(getReadableStatusClass(response.onboarding_status));
}
+ setReleaseDate(new Date(response.onboarding_release_date));
}
},
);
@@ -116,9 +126,27 @@ function ProctoringInfoPanel({ courseId, intl }) {
>
)}
{isNotYetSubmitted(status) && (
-
+ <>
+ {!isNotYetReleased(releaseDate) && (
+
+ )}
+ {isNotYetReleased(releaseDate) && (
+
+ )}
+ >
)}