From 7c7b3cdc078d8ab0efc4c7512c7c35a93240025b Mon Sep 17 00:00:00 2001 From: Zachary Hancock Date: Thu, 9 Nov 2023 08:55:25 -0500 Subject: [PATCH] feat: remove old/duplicate proctoring component (#671) --- README.rst | 1 - src/CourseAuthoringRoutes.jsx | 7 +- src/CourseAuthoringRoutes.test.jsx | 24 - src/index.scss | 1 - .../proctoring/Settings.jsx | 2 +- .../ProctoredExamSettings.jsx | 601 ------------ .../ProctoredExamSettings.messages.jsx | 71 -- .../ProctoredExamSettings.test.jsx | 886 ------------------ src/proctored-exam-settings/data/thunks.js | 21 - .../proctoredExamSettings.scss | 10 - 10 files changed, 5 insertions(+), 1619 deletions(-) delete mode 100644 src/proctored-exam-settings/ProctoredExamSettings.jsx delete mode 100644 src/proctored-exam-settings/ProctoredExamSettings.messages.jsx delete mode 100644 src/proctored-exam-settings/ProctoredExamSettings.test.jsx delete mode 100644 src/proctored-exam-settings/data/thunks.js delete mode 100644 src/proctored-exam-settings/proctoredExamSettings.scss diff --git a/README.rst b/README.rst index 471600a11..bc7c97066 100644 --- a/README.rst +++ b/README.rst @@ -259,7 +259,6 @@ Developing If your devstack includes the default Demo course, you can visit the following URLs to see content: -- `Proctored Exam Settings `_ - `Pages and Resources `_ Troubleshooting diff --git a/src/CourseAuthoringRoutes.jsx b/src/CourseAuthoringRoutes.jsx index 7bf5b864a..0aa1043a6 100644 --- a/src/CourseAuthoringRoutes.jsx +++ b/src/CourseAuthoringRoutes.jsx @@ -1,10 +1,11 @@ import React from 'react'; -import { Routes, Route, useParams } from 'react-router-dom'; +import { + Navigate, Routes, Route, useParams, +} from 'react-router-dom'; import { PageWrap } from '@edx/frontend-platform/react'; import Placeholder from '@edx/frontend-lib-content-components'; import CourseAuthoringPage from './CourseAuthoringPage'; import { PagesAndResources } from './pages-and-resources'; -import ProctoredExamSettings from './proctored-exam-settings/ProctoredExamSettings'; import EditorContainer from './editors/EditorContainer'; import VideoSelectorContainer from './selectors/VideoSelectorContainer'; import CustomPages from './custom-pages'; @@ -61,7 +62,7 @@ const CourseAuthoringRoutes = () => { /> } + element={} /> (props) => { mockComponentFn(props); return pagesAndResourcesMockText; }); -jest.mock('./proctored-exam-settings/ProctoredExamSettings', () => (props) => { - mockComponentFn(props); - return proctoredExamSeetingsMockText; -}); jest.mock('./editors/EditorContainer', () => (props) => { mockComponentFn(props); return editorContainerMockText; @@ -76,25 +71,6 @@ describe('', () => { ); expect(screen.getByText(pagesAndResourcesMockText)).toBeVisible(); - expect(screen.queryByText(proctoredExamSeetingsMockText)).not.toBeInTheDocument(); - expect(mockComponentFn).toHaveBeenCalledWith( - expect.objectContaining({ - courseId, - }), - ); - }); - - it('renders the ProctoredExamSettings component when the proctored exam settings route is active', () => { - render( - - - - - , - ); - - expect(screen.queryByText(proctoredExamSeetingsMockText)).toBeInTheDocument(); - expect(screen.queryByText(pagesAndResourcesMockText)).not.toBeInTheDocument(); expect(mockComponentFn).toHaveBeenCalledWith( expect.objectContaining({ courseId, diff --git a/src/index.scss b/src/index.scss index 3481701d6..3fed92570 100755 --- a/src/index.scss +++ b/src/index.scss @@ -7,7 +7,6 @@ @import "assets/scss/form"; @import "assets/scss/utilities"; @import "assets/scss/animations"; -@import "proctored-exam-settings/proctoredExamSettings"; @import "pages-and-resources/discussions/app-list/AppList"; @import "advanced-settings/scss/AdvancedSettings"; @import "grading-settings/scss/GradingSettings"; diff --git a/src/pages-and-resources/proctoring/Settings.jsx b/src/pages-and-resources/proctoring/Settings.jsx index 34136c64f..a4523902a 100644 --- a/src/pages-and-resources/proctoring/Settings.jsx +++ b/src/pages-and-resources/proctoring/Settings.jsx @@ -235,7 +235,7 @@ const ProctoringSettings = ({ intl, onClose }) => { ); } - const learnMoreLink = appInfo.documentationLinks?.learnMoreConfiguration && ( + const learnMoreLink = appInfo?.documentationLinks?.learnMoreConfiguration && ( { - const dispatch = useDispatch(); - const [loading, setLoading] = useState(true); - const [loaded, setLoaded] = useState(false); - const [loadingConnectionError, setLoadingConnectionError] = useState(false); - const [loadingPermissionError, setLoadingPermissionError] = useState(false); - const [enableProctoredExams, setEnableProctoredExams] = useState(true); - const [allowOptingOut, setAllowOptingOut] = useState(false); - const [allowLtiProviders, setAllowLtiProviders] = useState(false); - const [proctoringProvider, setProctoringProvider] = useState(''); - const [availableProctoringProviders, setAvailableProctoringProviders] = useState([]); - const [ltiProctoringProviders, setLtiProctoringProviders] = useState([]); - const [proctortrackEscalationEmail, setProctortrackEscalationEmail] = useState(''); - const [createZendeskTickets, setCreateZendeskTickets] = useState(false); - const [courseStartDate, setCourseStartDate] = useState(''); - const [saveSuccess, setSaveSuccess] = useState(false); - const [saveError, setSaveError] = useState(false); - const [submissionInProgress, setSubmissionInProgress] = useState(false); - const [showProctortrackEscalationEmail, setShowProctortrackEscalationEmail] = useState(false); - const isEdxStaff = getAuthenticatedUser().administrator; - const [formStatus, setFormStatus] = useState({ - isValid: true, - errors: {}, - }); - - const courseDetails = useModel('courseDetails', courseId); - document.title = getPageHeadTitle(courseDetails?.name, 'Proctored Exam Settings'); - - const alertRef = React.createRef(); - const saveStatusAlertRef = React.createRef(); - const proctoringEscalationEmailInputRef = useRef(null); - - const onEnableProctoredExamsChange = (event) => { - setEnableProctoredExams(event.target.checked); - }; - - function onAllowOptingOutChange(value) { - setAllowOptingOut(value); - } - - function onCreateZendeskTicketsChange(value) { - setCreateZendeskTickets(value); - } - - const onProctoringProviderChange = (event) => { - const provider = event.target.value; - setProctoringProvider(provider); - - if (provider === 'proctortrack') { - setCreateZendeskTickets(false); - setShowProctortrackEscalationEmail(true); - } else { - if (provider === 'software_secure') { - setCreateZendeskTickets(true); - } - setShowProctortrackEscalationEmail(false); - } - }; - - const onProctortrackEscalationEmailChange = (event) => { - setProctortrackEscalationEmail(event.target.value); - }; - - const setFocusToProctortrackEscalationEmailInput = () => { - if (proctoringEscalationEmailInputRef && proctoringEscalationEmailInputRef.current) { - proctoringEscalationEmailInputRef.current.focus(); - } - }; - - function isLtiProvider(provider) { - return ltiProctoringProviders.some(p => p.name === provider); - } - - function postSettingsBackToServer() { - const providerIsLti = isLtiProvider(proctoringProvider); - const studioDataToPostBack = { - proctored_exam_settings: { - enable_proctored_exams: enableProctoredExams, - // lti providers are managed outside edx-platform, lti_external indicates this - proctoring_provider: providerIsLti ? 'lti_external' : proctoringProvider, - create_zendesk_tickets: createZendeskTickets, - }, - }; - if (isEdxStaff) { - studioDataToPostBack.proctored_exam_settings.allow_proctoring_opt_out = allowOptingOut; - } - - if (proctoringProvider === 'proctortrack') { - studioDataToPostBack.proctored_exam_settings.proctoring_escalation_email = proctortrackEscalationEmail === '' ? null : proctortrackEscalationEmail; - } - - setSubmissionInProgress(true); - - // only save back to exam service if necessary - const saveOperations = [StudioApiService.saveProctoredExamSettingsData(courseId, studioDataToPostBack)]; - if (allowLtiProviders && ExamsApiService.isAvailable()) { - saveOperations.push( - ExamsApiService.saveCourseExamConfiguration(courseId, { provider: providerIsLti ? proctoringProvider : null }), - ); - } - Promise.all(saveOperations) - .then(() => { - setSaveSuccess(true); - setSaveError(false); - setSubmissionInProgress(false); - }).catch(() => { - setSaveSuccess(false); - setSaveError(true); - setSubmissionInProgress(false); - }); - } - - const handleSubmit = (event) => { - event.preventDefault(); - if (proctoringProvider === 'proctortrack' && !EmailValidator.validate(proctortrackEscalationEmail) && !(proctortrackEscalationEmail === '' && !enableProctoredExams)) { - if (proctortrackEscalationEmail === '') { - const errorMessage = intl.formatMessage(messages['authoring.examsettings.escalationemail.error.blank']); - - setFormStatus({ - isValid: false, - errors: { - formProctortrackEscalationEmail: { - dialogErrorMessage: ({errorMessage}), - inputErrorMessage: errorMessage, - }, - }, - }); - } else { - const errorMessage = intl.formatMessage(messages['authoring.examsettings.escalationemail.error.invalid']); - - setFormStatus({ - isValid: false, - errors: { - formProctortrackEscalationEmail: { - dialogErrorMessage: ({errorMessage}), - inputErrorMessage: errorMessage, - }, - }, - }); - } - } else { - postSettingsBackToServer(); - const errors = { ...formStatus.errors }; - delete errors.formProctortrackEscalationEmail; - setFormStatus({ - isValid: true, - errors, - }); - } - }; - - function cannotEditProctoringProvider() { - const currentDate = moment(moment()).format('YYYY-MM-DD[T]hh:mm:ss[Z]'); - const isAfterCourseStart = currentDate > courseStartDate; - - // if the user is not edX staff and it is after the course start date, user cannot edit proctoring provider - return !isEdxStaff && isAfterCourseStart; - } - - function isDisabledOption(provider) { - let markDisabled = false; - if (cannotEditProctoringProvider()) { - markDisabled = provider !== proctoringProvider; - } - return markDisabled; - } - - function getProviderDisplayLabel(provider) { - // if a display label exists for this provider return it - return ltiProctoringProviders.find(p => p.name === provider)?.verbose_name || provider; - } - - function getProctoringProviderOptions(providers) { - return providers.map(provider => ( - - )); - } - - function getFormErrorMessage() { - const numOfErrors = Object.keys(formStatus.errors).length; - const errors = Object.entries(formStatus.errors).map(([id, error]) =>
  • {error.dialogErrorMessage}
  • ); - const messageId = numOfErrors > 1 ? 'authoring.examsettings.error.multiple' : 'authoring.examsettings.error.single'; - - return ( - <> -
    {intl.formatMessage(messages[messageId], { numOfErrors })}
    -
      - {errors} -
    - - ); - } - - function renderContent() { - return ( -
    - {!formStatus.isValid && formStatus.errors.formProctortrackEscalationEmail - && ( - // tabIndex="-1" to make non-focusable element focusable - - {getFormErrorMessage()} - - )} - {/* ENABLE PROCTORED EXAMS */} - - - - - - - - {/* ALLOW OPTING OUT OF PROCTORED EXAMS */} - { isEdxStaff && enableProctoredExams && ( -
    - - - - - onAllowOptingOutChange(true)} - data-testid="allowOptingOutYes" - /> - onAllowOptingOutChange(false)} - data-testid="allowOptingOutNo" - /> - - - - -
    - )} - - {/* PROCTORING PROVIDER */} - { enableProctoredExams && ( - - - - - - {getProctoringProviderOptions(availableProctoringProviders)} - - - {cannotEditProctoringProvider() ? intl.formatMessage(messages['authoring.examsettings.provider.help.aftercoursestart']) : intl.formatMessage(messages['authoring.examsettings.provider.help'])} - - - )} - - {/* PROCTORTRACK ESCALATION EMAIL */} - {showProctortrackEscalationEmail && enableProctoredExams && ( - - - - - - {formStatus.errors.formProctortrackEscalationEmail && formStatus.errors.formProctortrackEscalationEmail.inputErrorMessage} - - - - - )} - {/* CREATE ZENDESK TICKETS */} - { isEdxStaff && enableProctoredExams && !isLtiProvider(proctoringProvider) && ( -
    - - - - - onCreateZendeskTicketsChange(true)} - data-testid="createZendeskTicketsYes" - /> - onCreateZendeskTicketsChange(false)} - data-testid="createZendeskTicketsNo" - /> - - - - -
    - )} - {' '} - {submissionInProgress && } - - ); - } - - function renderLoading() { - return ( - - ); - } - - function renderConnectionError() { - return ( - - ); - } - - function renderPermissionError() { - return ( - - ); - } - - function renderSaveSuccess() { - const studioCourseRunURL = StudioApiService.getStudioCourseRunUrl(courseId); - return ( - setSaveSuccess(false)} - > - here }} - /> - - ); - } - - function renderSaveError() { - return ( - setSaveError(false)} - > - - {intl.formatMessage(messages['authoring.examsettings.support.text'])} - - ), - }} - /> - - ); - } - - useEffect(() => { - dispatch(fetchExamSettingsPending(courseId)); - - Promise.all([ - StudioApiService.getProctoredExamSettingsData(courseId), - ExamsApiService.isAvailable() ? ExamsApiService.getCourseExamConfiguration(courseId) : Promise.resolve(), - ExamsApiService.isAvailable() ? ExamsApiService.getAvailableProviders() : Promise.resolve(), - ]) - .then( - ([settingsResponse, examConfigResponse, ltiProvidersResponse]) => { - const proctoredExamSettings = settingsResponse.data.proctored_exam_settings; - setLoaded(true); - setLoading(false); - setSubmissionInProgress(false); - setCourseStartDate(settingsResponse.data.course_start_date); - setEnableProctoredExams(proctoredExamSettings.enable_proctored_exams); - setAllowOptingOut(proctoredExamSettings.allow_proctoring_opt_out); - const isProctortrack = proctoredExamSettings.proctoring_provider === 'proctortrack'; - setShowProctortrackEscalationEmail(isProctortrack); - - // The list of providers returned by studio settings are the default behavior. If lti_external - // is available as an option display the list of LTI providers returned by the exam service. - // Setting 'lti_external' in studio indicates an LTI provider configured outside of edx-platform. - // This option is not directly selectable. - const proctoringProvidersStudio = settingsResponse.data.available_proctoring_providers; - const proctoringProvidersLti = ltiProvidersResponse?.data || []; - const enableLtiProviders = proctoringProvidersStudio.includes('lti_external'); - setAllowLtiProviders(enableLtiProviders); - setLtiProctoringProviders(proctoringProvidersLti); - // flatten provider objects and coalesce values to just the provider key - let availableProviders = proctoringProvidersStudio.filter(value => value !== 'lti_external'); - if (enableLtiProviders) { - availableProviders = proctoringProvidersLti.reduce( - (result, provider) => [...result, provider.name], - availableProviders, - ); - } - setAvailableProctoringProviders(availableProviders); - - if (proctoredExamSettings.proctoring_provider === 'lti_external') { - setProctoringProvider(examConfigResponse.data.provider); - } else { - setProctoringProvider(proctoredExamSettings.proctoring_provider); - } - - // The backend API may return null for the proctoringEscalationEmail value, which is the default. - // In order to keep our email input component controlled, we use the empty string as the default - // and perform this conversion during GETs and POSTs. - const proctoringEscalationEmail = proctoredExamSettings.proctoring_escalation_email; - setProctortrackEscalationEmail(proctoringEscalationEmail === null ? '' : proctoringEscalationEmail); - - setCreateZendeskTickets(proctoredExamSettings.create_zendesk_tickets); - dispatch(fetchExamSettingsSuccess(courseId)); - }, - ) - .catch( - error => { - if (error.response?.status === 403) { - setLoadingPermissionError(true); - } else { - setLoadingConnectionError(true); - } - setLoading(false); - setLoaded(false); - setSubmissionInProgress(false); - dispatch(fetchExamSettingsFailure(courseId)); - }, - ); - }, []); - - useEffect(() => { - if ((saveSuccess || saveError) && !!saveStatusAlertRef.current) { - saveStatusAlertRef.current.focus(); - } - if (!formStatus.isValid && !!alertRef.current) { - alertRef.current.focus(); - } - }, [formStatus, saveSuccess, saveError]); - - return ( -
    -

    - Proctored Exam Settings -

    -
    - {loading ? renderLoading() : null} - {saveSuccess ? renderSaveSuccess() : null} - {saveError ? renderSaveError() : null} - {loaded ? renderContent() : null} - {loadingConnectionError ? renderConnectionError() : null} - {loadingPermissionError ? renderPermissionError() : null} -
    -
    - ); -}; - -ProctoredExamSettings.propTypes = { - courseId: PropTypes.string.isRequired, - intl: intlShape.isRequired, -}; - -ProctoredExamSettings.defaultProps = {}; - -export default injectIntl(ProctoredExamSettings); diff --git a/src/proctored-exam-settings/ProctoredExamSettings.messages.jsx b/src/proctored-exam-settings/ProctoredExamSettings.messages.jsx deleted file mode 100644 index aa5491954..000000000 --- a/src/proctored-exam-settings/ProctoredExamSettings.messages.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - 'authoring.examsettings.allowoptout.no': { - id: 'authoring.examsettings.allowoptout.no', - defaultMessage: 'No', - description: '"No" option for yes/no radio button set', - }, - 'authoring.examsettings.allowoptout.yes': { - id: 'authoring.examsettings.allowoptout.yes', - defaultMessage: 'Yes', - description: '"Yes" option for yes/no radio button set', - }, - 'authoring.examsettings.createzendesk.no': { - id: 'authoring.examsettings.createzendesk.no', - defaultMessage: 'No', - description: '"No" option for yes/no radio button set.', - }, - 'authoring.examsettings.createzendesk.yes': { - id: 'authoring.examsettings.createzendesk.yes', - defaultMessage: 'Yes', - description: '"Yes" option for yes/no radio button set.', - }, - 'authoring.examsettings.support.text': { - id: 'authoring.examsettings.support.text', - defaultMessage: 'Support Page', - description: 'Text linking to the support page.', - }, - 'authoring.examsettings.enableproctoredexams.label': { - id: 'authoring.examsettings.escalationemail.enableproctoredexams.label', - defaultMessage: 'Enable Proctored Exams', - description: 'Label for checkbox to enable proctored exams.', - }, - 'authoring.examsettings.escalationemail.error.blank': { - id: 'authoring.examsettings.escalationemail.error.blank', - defaultMessage: 'The Proctortrack Escalation Email field cannot be empty if proctortrack is the selected provider.', - description: 'Error message for missing required email field.', - }, - 'authoring.examsettings.escalationemail.error.invalid': { - id: 'authoring.examsettings.escalationemail.error.invalid', - defaultMessage: 'The Proctortrack Escalation Email field is in the wrong format and is not valid.', - description: 'Error message for a invalid email format.', - }, - 'authoring.examsettings.error.single': { - id: 'authoring.examsettings.error.single', - defaultMessage: 'There is 1 error in this form.', - description: 'Error alert for one and only one error in the form.', - }, - 'authoring.examsettings.error.multiple': { - id: 'authoring.examsettings.escalationemail.error.multiple', - defaultMessage: 'There are {numOfErrors} errors in this form.', - description: 'Error alert for multiple errors in the form.', - }, - 'authoring.examsettings.provider.label': { - id: 'authoring.examsettings.provider.label', - defaultMessage: 'Proctoring Provider', - description: 'Label for provider dropdown selection.', - }, - 'authoring.examsettings.provider.help': { - id: 'authoring.examsettings.provider.help', - defaultMessage: 'Select the proctoring provider you want to use for this course run.', - description: 'Help text for selecting a proctoring provider.', - }, - 'authoring.examsettings.provider.help.aftercoursestart': { - id: 'authoring.examsettings.provider.help.aftercoursestart', - defaultMessage: 'Proctoring provider cannot be modified after course start date.', - description: 'Help text notifying the user that the provider cannot be changed for a course that has already begun.', - }, -}); - -export default messages; diff --git a/src/proctored-exam-settings/ProctoredExamSettings.test.jsx b/src/proctored-exam-settings/ProctoredExamSettings.test.jsx deleted file mode 100644 index 8a7846f4e..000000000 --- a/src/proctored-exam-settings/ProctoredExamSettings.test.jsx +++ /dev/null @@ -1,886 +0,0 @@ -import React from 'react'; -import { - render, screen, cleanup, waitFor, waitForElementToBeRemoved, fireEvent, act, -} from '@testing-library/react'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; -// import * as auth from '@edx/frontend-platform/auth'; -import MockAdapter from 'axios-mock-adapter'; -import { initializeMockApp, mergeConfig } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { AppProvider } from '@edx/frontend-platform/react'; -import ProctoredExamSettings from './ProctoredExamSettings'; -import StudioApiService from '../data/services/StudioApiService'; -import ExamsApiService from '../data/services/ExamsApiService'; -import initializeStore from '../store'; - -const defaultProps = { - courseId: 'course-v1%3AedX%2BDemoX%2BDemo_Course', -}; - -const IntlProctoredExamSettings = injectIntl(ProctoredExamSettings); - -let axiosMock; -let store; - -const intlWrapper = children => ( - - - {children} - - -); - -describe('ProctoredExamSettings', () => { - function setupApp(isAdmin = true) { - mergeConfig({ - EXAMS_BASE_URL: 'http://exams.testing.co', - }, 'CourseAuthoringConfig'); - - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: isAdmin, - roles: [], - }, - }); - store = initializeStore(); - - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); - axiosMock.onGet( - `${ExamsApiService.getExamsBaseUrl()}/api/v1/providers`, - ).reply(200, [ - { - name: 'test_lti', - verbose_name: 'LTI Provider', - }, - ]); - axiosMock.onGet( - `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`, - ).reply(200, { - provider: null, - }); - - axiosMock.onGet( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(200, { - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'mockproc', - proctoring_escalation_email: 'test@example.com', - create_zendesk_tickets: true, - }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc', 'lti_external'], - course_start_date: '2070-01-01T00:00:00Z', - }); - } - - afterEach(() => { - cleanup(); - axiosMock.reset(); - }); - beforeEach(async () => { - setupApp(); - }); - - describe('Field dependencies', () => { - beforeEach(async () => { - await act(async () => render(intlWrapper())); - }); - - it('Updates Zendesk ticket field if proctortrack is provider', async () => { - await waitFor(() => { - screen.getByDisplayValue('mockproc'); - }); - const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); - }); - const zendeskTicketInput = screen.getByTestId('createZendeskTicketsNo'); - expect(zendeskTicketInput.checked).toEqual(true); - }); - - it('Updates Zendesk ticket field if software_secure is provider', async () => { - await waitFor(() => { - screen.getByDisplayValue('mockproc'); - }); - const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'software_secure' } }); - }); - const zendeskTicketInput = screen.getByTestId('createZendeskTicketsYes'); - expect(zendeskTicketInput.checked).toEqual(true); - }); - - it('Does not update zendesk ticket field for any other provider', async () => { - await waitFor(() => { - screen.getByDisplayValue('mockproc'); - }); - const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'mockproc' } }); - }); - const zendeskTicketInput = screen.getByTestId('createZendeskTicketsYes'); - expect(zendeskTicketInput.checked).toEqual(true); - }); - - it('Hides all other fields when enabledProctorExam is false when first loaded', async () => { - cleanup(); - // Overrides the handler defined in beforeEach. - axiosMock.onGet( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(200, { - proctored_exam_settings: { - enable_proctored_exams: false, - allow_proctoring_opt_out: false, - proctoring_provider: 'mockproc', - proctoring_escalation_email: 'test@example.com', - create_zendesk_tickets: true, - }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], - course_start_date: '2070-01-01T00:00:00Z', - }); - - await act(async () => render(intlWrapper())); - await waitFor(() => { - screen.getByLabelText('Enable Proctored Exams'); - }); - const enabledProctoredExamCheck = screen.getByLabelText('Enable Proctored Exams'); - expect(enabledProctoredExamCheck.checked).toEqual(false); - expect(screen.queryByText('Allow Opting Out of Proctored Exams')).toBeNull(); - expect(screen.queryByDisplayValue('mockproc')).toBeNull(); - expect(screen.queryByTestId('escalationEmail')).toBeNull(); - expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull(); - expect(screen.queryByTestId('createZendeskTicketsNo')).toBeNull(); - }); - - it('Hides all other fields when enableProctoredExams toggled to false', async () => { - await waitFor(() => { - screen.getByLabelText('Enable Proctored Exams'); - }); - expect(screen.queryByText('Allow Opting Out of Proctored Exams')).toBeDefined(); - expect(screen.queryByDisplayValue('mockproc')).toBeDefined(); - expect(screen.queryByTestId('escalationEmail')).toBeDefined(); - expect(screen.queryByTestId('createZendeskTicketsYes')).toBeDefined(); - expect(screen.queryByTestId('createZendeskTicketsNo')).toBeDefined(); - - let enabledProctorExamCheck = screen.getByLabelText('Enable Proctored Exams'); - expect(enabledProctorExamCheck.checked).toEqual(true); - await act(async () => { - fireEvent.click(enabledProctorExamCheck, { target: { value: false } }); - }); - enabledProctorExamCheck = screen.getByLabelText('Enable Proctored Exams'); - expect(enabledProctorExamCheck.checked).toEqual(false); - expect(screen.queryByText('Allow Opting Out of Proctored Exams')).toBeNull(); - expect(screen.queryByDisplayValue('mockproc')).toBeNull(); - expect(screen.queryByTestId('escalationEmail')).toBeNull(); - expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull(); - expect(screen.queryByTestId('createZendeskTicketsNo')).toBeNull(); - }); - - it('Hides unsupported fields when lti provider is selected', async () => { - await waitFor(() => { - screen.getByDisplayValue('mockproc'); - }); - const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'test_lti' } }); - }); - expect(screen.queryByTestId('escalationEmail')).toBeNull(); - expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull(); - expect(screen.queryByTestId('createZendeskTicketsNo')).toBeNull(); - }); - }); - - describe('Validation with invalid escalation email', () => { - beforeEach(async () => { - axiosMock.onGet( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(200, { - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'proctortrack', - proctoring_escalation_email: 'test@example.com', - create_zendesk_tickets: true, - }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], - course_start_date: '2070-01-01T00:00:00Z', - }); - - await act(async () => render(intlWrapper())); - }); - - it('Creates an alert when no proctoring escalation email is provided with proctortrack selected', async () => { - await waitFor(() => { - screen.getByDisplayValue('proctortrack'); - }); - const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); - }); - const selectButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(selectButton); - }); - - // verify alert content and focus management - const escalationEmailError = screen.getByTestId('proctortrackEscalationEmailError'); - expect(escalationEmailError.textContent).not.toBeNull(); - expect(document.activeElement).toEqual(escalationEmailError); - - // verify alert link links to offending input - const errorLink = screen.getByTestId('proctorTrackEscalationEmailErrorLink'); - await act(async () => { - fireEvent.click(errorLink); - }); - const escalationEmailInput = screen.getByTestId('escalationEmail'); - expect(document.activeElement).toEqual(escalationEmailInput); - }); - - it('Creates an alert when invalid proctoring escalation email is provided with proctortrack selected', async () => { - await waitFor(() => { - screen.getByDisplayValue('proctortrack'); - }); - const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } }); - }); - const selectButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(selectButton); - }); - - // verify alert content and focus management - const escalationEmailError = screen.getByTestId('proctortrackEscalationEmailError'); - expect(document.activeElement).toEqual(escalationEmailError); - expect(escalationEmailError.textContent).not.toBeNull(); - expect(document.activeElement).toEqual(escalationEmailError); - - // verify alert link links to offending input - const errorLink = screen.getByTestId('proctorTrackEscalationEmailErrorLink'); - await act(async () => { - fireEvent.click(errorLink); - }); - const escalationEmailInput = screen.getByTestId('escalationEmail'); - expect(document.activeElement).toEqual(escalationEmailInput); - }); - - it('Creates an alert when invalid proctoring escalation email is provided with proctoring disabled', async () => { - await waitFor(() => { - screen.getByDisplayValue('proctortrack'); - }); - const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } }); - }); - const enableProctoringElement = screen.getByLabelText('Enable Proctored Exams'); - await act(async () => fireEvent.click(enableProctoringElement)); - const selectButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(selectButton); - }); - - // verify alert content and focus management - const escalationEmailError = screen.getByTestId('proctortrackEscalationEmailError'); - expect(document.activeElement).toEqual(escalationEmailError); - expect(escalationEmailError.textContent).not.toBeNull(); - expect(document.activeElement).toEqual(escalationEmailError); - }); - - it('Has no error when invalid proctoring escalation email is provided with proctoring disabled', async () => { - axiosMock.onPost( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(200, 'success'); - axiosMock.onPatch( - `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`, - ).reply(200, 'success'); - await waitFor(() => { - screen.getByDisplayValue('proctortrack'); - }); - - const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); - }); - const enableProctoringElement = screen.getByLabelText('Enable Proctored Exams'); - await act(async () => fireEvent.click(enableProctoringElement)); - const selectButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(selectButton); - }); - - // verify there is no escalation email alert, and focus has been set on save success alert - expect(screen.queryByTestId('proctortrackEscalationEmailError')).toBeNull(); - - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); - }); - - it('Has no error when valid proctoring escalation email is provided with proctortrack selected', async () => { - axiosMock.onPost( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(200, 'success'); - axiosMock.onPatch( - `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`, - ).reply(200, 'success'); - - await waitFor(() => { - screen.getByDisplayValue('proctortrack'); - }); - const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo@bar.com' } }); - }); - const selectButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(selectButton); - }); - - // verify there is no escalation email alert, and focus has been set on save success alert - expect(screen.queryByTestId('proctortrackEscalationEmailError')).toBeNull(); - - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); - }); - - it('Escalation email field hidden when proctoring backend is not Proctortrack', async () => { - await waitFor(() => { - screen.getByDisplayValue('proctortrack'); - }); - const proctoringBackendSelect = screen.getByDisplayValue('proctortrack'); - const selectEscalationEmailElement = screen.getByTestId('escalationEmail'); - expect(selectEscalationEmailElement.value).toEqual('test@example.com'); - await act(async () => { - fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } }); - }); - expect(screen.queryByTestId('escalationEmail')).toBeNull(); - }); - - it('Escalation email Field Show when proctoring backend is switched back to Proctortrack', async () => { - await waitFor(() => { - screen.getByDisplayValue('proctortrack'); - }); - const proctoringBackendSelect = screen.getByDisplayValue('proctortrack'); - let selectEscalationEmailElement = screen.getByTestId('escalationEmail'); - await act(async () => { - fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } }); - }); - expect(screen.queryByTestId('escalationEmail')).toBeNull(); - await act(async () => { - fireEvent.change(proctoringBackendSelect, { target: { value: 'proctortrack' } }); - }); - expect(screen.queryByTestId('escalationEmail')).toBeDefined(); - selectEscalationEmailElement = screen.getByTestId('escalationEmail'); - expect(selectEscalationEmailElement.value).toEqual('test@example.com'); - }); - - it('Submits form when "Enter" key is hit in the escalation email field', async () => { - await waitFor(() => { - screen.getByDisplayValue('proctortrack'); - }); - const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); - }); - await act(async () => { - fireEvent.submit(selectEscalationEmailElement); - }); - // if the error appears, the form has been submitted - expect(screen.getByTestId('proctortrackEscalationEmailError')).toBeDefined(); - }); - }); - - describe('Proctoring provider options', () => { - const mockGetFutureCourseData = { - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'mockproc', - proctoring_escalation_email: 'test@example.com', - create_zendesk_tickets: true, - }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], - course_start_date: '2099-01-01T00:00:00Z', - }; - - const mockGetPastCourseData = { - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'mockproc', - proctoring_escalation_email: 'test@example.com', - create_zendesk_tickets: true, - }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], - course_start_date: '2013-01-01T00:00:00Z', - }; - - function mockCourseData(data) { - axiosMock.onGet(StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId)).reply(200, data); - } - - it('Disables irrelevant proctoring provider fields when user is not an administrator and it is after start date', async () => { - const isAdmin = false; - setupApp(isAdmin); - mockCourseData(mockGetPastCourseData); - await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); - expect(providerOption.hasAttribute('disabled')).toEqual(true); - }); - - it('Enables all proctoring provider options if user is not an administrator and it is before start date', async () => { - const isAdmin = false; - setupApp(isAdmin); - mockCourseData(mockGetFutureCourseData); - await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); - expect(providerOption.hasAttribute('disabled')).toEqual(false); - }); - - it('Enables all proctoring provider options if user administrator and it is after start date', async () => { - const isAdmin = true; - setupApp(isAdmin); - mockCourseData(mockGetPastCourseData); - await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); - expect(providerOption.hasAttribute('disabled')).toEqual(false); - }); - - it('Enables all proctoring provider options if user administrator and it is before start date', async () => { - const isAdmin = true; - setupApp(isAdmin); - mockCourseData(mockGetFutureCourseData); - await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); - expect(providerOption.hasAttribute('disabled')).toEqual(false); - }); - - it('Does not include lti_external as a selectable option', async () => { - const courseData = mockGetFutureCourseData; - courseData.available_proctoring_providers = ['lti_external', 'proctortrack', 'mockproc']; - mockCourseData(courseData); - await act(async () => render(intlWrapper())); - await waitFor(() => { - screen.getByDisplayValue('mockproc'); - }); - expect(screen.queryByTestId('lti_external')).toBeNull(); - }); - - it('Includes lti proctoring provider options when lti_external is allowed by studio', async () => { - const courseData = mockGetFutureCourseData; - courseData.available_proctoring_providers = ['lti_external', 'proctortrack', 'mockproc']; - mockCourseData(courseData); - await act(async () => render(intlWrapper())); - await waitFor(() => { - screen.getByDisplayValue('mockproc'); - }); - const providerOption = screen.getByTestId('test_lti'); - // as as admin the provider should not be disabled - expect(providerOption.hasAttribute('disabled')).toEqual(false); - }); - - it('Does not request lti provider options if there is no exam service url configuration', async () => { - mergeConfig({ - EXAMS_BASE_URL: null, - }, 'CourseAuthoringConfig'); - - await act(async () => render(intlWrapper())); - await waitFor(() => { - screen.getByDisplayValue('mockproc'); - }); - // only outgoing request should be for studio settings - expect(axiosMock.history.get.length).toBe(1); - expect(axiosMock.history.get[0].url.includes('proctored_exam_settings')).toEqual(true); - }); - - it('Selected LTI proctoring provider is shown on page load', async () => { - const courseData = { ...mockGetFutureCourseData }; - courseData.available_proctoring_providers = ['lti_external', 'proctortrack', 'mockproc']; - courseData.proctored_exam_settings.proctoring_provider = 'lti_external'; - mockCourseData(courseData); - axiosMock.onGet( - `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`, - ).reply(200, { - provider: 'test_lti', - }); - await act(async () => render(intlWrapper())); - await waitFor(() => { - screen.getByText('Proctoring Provider'); - }); - - // make sure test_lti is the selected provider - expect(screen.getByDisplayValue('LTI Provider')).toBeInTheDocument(); - }); - }); - - describe('Toggles field visibility based on user permissions', () => { - it('Hides opting out and zendesk tickets for non edX staff', async () => { - setupApp(false); - await act(async () => render(intlWrapper())); - expect(screen.queryByTestId('allowOptingOutYes')).toBeNull(); - expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull(); - }); - - it('Shows opting out and zendesk tickets for edX staff', async () => { - setupApp(true); - await act(async () => render(intlWrapper())); - expect(screen.queryByTestId('allowOptingOutYes')).not.toBeNull(); - expect(screen.queryByTestId('createZendeskTicketsYes')).not.toBeNull(); - }); - }); - - describe('Connection states', () => { - it('Shows the spinner before the connection is complete', async () => { - await act(async () => { - render(intlWrapper()); - // This expectation is _inside_ the `act` intentionally, so that it executes immediately. - const spinner = screen.getByRole('status'); - expect(spinner.textContent).toEqual('Loading...'); - }); - }); - - it('Show connection error message when we suffer studio server side error', async () => { - axiosMock.onGet( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(500); - - await act(async () => render(intlWrapper())); - const connectionError = screen.getByTestId('connectionErrorAlert'); - expect(connectionError.textContent).toEqual( - expect.stringContaining('We encountered a technical error when loading this page.'), - ); - }); - - it('Show connection error message when we suffer edx-exams server side error', async () => { - axiosMock.onGet( - `${ExamsApiService.getExamsBaseUrl()}/api/v1/providers`, - ).reply(500); - - await act(async () => render(intlWrapper())); - const connectionError = screen.getByTestId('connectionErrorAlert'); - expect(connectionError.textContent).toEqual( - expect.stringContaining('We encountered a technical error when loading this page.'), - ); - }); - - it('Show permission error message when user do not have enough permission', async () => { - axiosMock.onGet( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(403); - - await act(async () => render(intlWrapper())); - const permissionError = screen.getByTestId('permissionDeniedAlert'); - expect(permissionError.textContent).toEqual( - expect.stringContaining('You are not authorized to view this page'), - ); - }); - }); - - describe('Save settings', () => { - beforeEach(async () => { - axiosMock.onPost( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(200, 'success'); - axiosMock.onPatch( - `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`, - ).reply(200, 'success'); - }); - - it('Show spinner while saving', async () => { - axiosMock.onPost( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(200, 'success'); - - await act(async () => render(intlWrapper())); - const submitButton = screen.getByTestId('submissionButton'); - expect(screen.queryByTestId('saveInProgress')).toBeFalsy(); - act(() => { - fireEvent.click(submitButton); - }); - - const submitSpinner = screen.getByTestId('saveInProgress'); - expect(submitSpinner).toBeDefined(); - - await waitForElementToBeRemoved(submitSpinner); - // request studio settings, exam config, and exam service providers - expect(axiosMock.history.get.length).toBe(3); - expect(axiosMock.history.post.length).toBe(1); // studio - expect(axiosMock.history.patch.length).toBe(1); // edx-exams - expect(screen.queryByTestId('saveInProgress')).toBeFalsy(); - }); - - it('Makes API call successfully with proctoring_escalation_email if proctortrack', async () => { - await act(async () => render(intlWrapper())); - // Make a change to the provider to proctortrack and set the email - const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); - }); - const escalationEmail = screen.getByTestId('escalationEmail'); - expect(escalationEmail.value).toEqual('test@example.com'); - await act(async () => { - fireEvent.change(escalationEmail, { target: { value: 'proctortrack@example.com' } }); - }); - expect(escalationEmail.value).toEqual('proctortrack@example.com'); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); - expect(axiosMock.history.post.length).toBe(1); - expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'proctortrack', - proctoring_escalation_email: 'proctortrack@example.com', - create_zendesk_tickets: false, - }, - }); - - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); - }); - - it('Makes API call successfully without proctoring_escalation_email if not proctortrack', async () => { - await act(async () => render(intlWrapper())); - - // make sure we have not selected proctortrack as the proctoring provider - expect(screen.getByDisplayValue('mockproc')).toBeDefined(); - - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); - expect(axiosMock.history.post.length).toBe(1); - expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'mockproc', - create_zendesk_tickets: true, - }, - }); - - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); - }); - - it('Successfully updates exam configuration and studio provider is set to "lti_external" for lti providers', async () => { - await act(async () => render(intlWrapper())); - // Make a change to the provider to proctortrack and set the email - const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'test_lti' } }); - }); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); - - // update exam service config - expect(axiosMock.history.patch.length).toBe(1); - expect(JSON.parse(axiosMock.history.patch[0].data)).toEqual({ - provider: 'test_lti', - }); - - // update studio settings - expect(axiosMock.history.post.length).toBe(1); - expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'lti_external', - create_zendesk_tickets: true, - }, - }); - - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); - }); - - it('Sets exam service provider to null if a non-lti provider is selected', async () => { - await act(async () => render(intlWrapper())); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); - // update exam service config - expect(axiosMock.history.patch.length).toBe(1); - expect(JSON.parse(axiosMock.history.patch[0].data)).toEqual({ - provider: null, - }); - expect(axiosMock.history.patch.length).toBe(1); - expect(axiosMock.history.post.length).toBe(1); - expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'mockproc', - create_zendesk_tickets: true, - }, - }); - - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); - }); - - it('Does not update exam service if lti is not enabled in studio', async () => { - axiosMock.onGet( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(200, { - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'mockproc', - proctoring_escalation_email: 'test@example.com', - create_zendesk_tickets: true, - }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], - course_start_date: '2070-01-01T00:00:00Z', - }); - - await act(async () => render(intlWrapper())); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); - // does not update exam service config - expect(axiosMock.history.patch.length).toBe(0); - // does update studio - expect(axiosMock.history.post.length).toBe(1); - expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ - proctored_exam_settings: { - enable_proctored_exams: true, - allow_proctoring_opt_out: false, - proctoring_provider: 'mockproc', - create_zendesk_tickets: true, - }, - }); - - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); - }); - - it('Makes studio API call generated error', async () => { - axiosMock.onPost( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(500); - - await act(async () => render(intlWrapper())); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); - expect(axiosMock.history.post.length).toBe(1); - const errorAlert = screen.getByTestId('saveError'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'), - ); - expect(document.activeElement).toEqual(errorAlert); - }); - - it('Makes exams API call generated error', async () => { - axiosMock.onPatch( - `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`, - ).reply(500, 'error'); - - await act(async () => render(intlWrapper())); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); - expect(axiosMock.history.post.length).toBe(1); - const errorAlert = screen.getByTestId('saveError'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'), - ); - expect(document.activeElement).toEqual(errorAlert); - }); - - it('Manages focus correctly after different save statuses', async () => { - // first make a call that will cause a save error - axiosMock.onPost( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(500); - - await act(async () => render(intlWrapper())); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); - expect(axiosMock.history.post.length).toBe(1); - const errorAlert = screen.getByTestId('saveError'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'), - ); - expect(document.activeElement).toEqual(errorAlert); - - // now make a call that will allow for a successful save - axiosMock.onPost( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - ).reply(200, 'success'); - await act(async () => { - fireEvent.click(submitButton); - }); - - expect(axiosMock.history.post.length).toBe(2); - const successAlert = screen.getByTestId('saveSuccess'); - expect(successAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(successAlert); - }); - - it('Include Zendesk ticket in post request if user is not an admin', async () => { - // use non-admin user for test - const isAdmin = false; - setupApp(isAdmin); - - await act(async () => render(intlWrapper())); - // Make a change to the proctoring provider - await waitFor(() => { - screen.getByDisplayValue('mockproc'); - }); - const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); - }); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); - expect(axiosMock.history.post.length).toBe(1); - expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ - proctored_exam_settings: { - enable_proctored_exams: true, - proctoring_provider: 'proctortrack', - proctoring_escalation_email: 'test@example.com', - create_zendesk_tickets: false, - }, - }); - }); - }); -}); diff --git a/src/proctored-exam-settings/data/thunks.js b/src/proctored-exam-settings/data/thunks.js deleted file mode 100644 index ac32ad4ad..000000000 --- a/src/proctored-exam-settings/data/thunks.js +++ /dev/null @@ -1,21 +0,0 @@ -import { RequestStatus } from '../../data/constants'; -import { updateLoadingStatus } from '../../pages-and-resources/data/slice'; - -/* eslint-disable import/prefer-default-export */ -export function fetchExamSettingsPending(courseId) { - return async (dispatch) => { - dispatch(updateLoadingStatus({ courseId, status: RequestStatus.IN_PROGRESS })); - }; -} - -export function fetchExamSettingsSuccess(courseId) { - return async (dispatch) => { - dispatch(updateLoadingStatus({ courseId, status: RequestStatus.SUCCESSFUL })); - }; -} - -export function fetchExamSettingsFailure(courseId) { - return async (dispatch) => { - dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED })); - }; -} diff --git a/src/proctored-exam-settings/proctoredExamSettings.scss b/src/proctored-exam-settings/proctoredExamSettings.scss deleted file mode 100644 index 7dc00ecad..000000000 --- a/src/proctored-exam-settings/proctoredExamSettings.scss +++ /dev/null @@ -1,10 +0,0 @@ -// legend styling should match label styling -legend { - font-size: $spacer; - font-weight: 700; -} - -// override conflicting browser styles -select { - appearance: none; -}