From 1baecebabba8d47c10ae89c4c2bd5754186d7a50 Mon Sep 17 00:00:00 2001 From: David Joy Date: Wed, 24 Mar 2021 10:39:21 -0400 Subject: [PATCH] refactor: using initializeMockApp in ProctoredExamSettings tests (#59) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - This uses a new function in frontend-platform 1.9.0 that sets up a mock application for use in test suites. - The ProctoredExamSettings.test.jsx tests have been refactored to use axios-mock-adapter and initializeMockApp. - A tweak was made to ProctoredExamSettings to use error.response.status instead of error.customAttributes.httpErrorStatus - the former is a more canonical way of getting at this data, rather than using the customAttributes object we add to our errors. It also means the code will work with the MockAuthService instead of AxiosJwtAuthService. - Removing axios dev dependency - we don’t need it anymore! --- package-lock.json | 70 +- package.json | 3 +- .../ProctoredExamSettings.jsx | 4 +- .../ProctoredExamSettings.test.jsx | 1082 +++++++++-------- src/setupTest.js | 4 - 5 files changed, 598 insertions(+), 565 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9c200880..689fb4ee8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3483,12 +3483,12 @@ } }, "@edx/frontend-platform": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-1.8.0.tgz", - "integrity": "sha512-R4LGBKaSBWC9xxG3eTN78zw5CkBes4xQgazt78ApgenxzIoun+y0tdWyc/8PpjbKmyj0nACpsfARyeQy4rnGKA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-1.9.0.tgz", + "integrity": "sha512-fcXEJ+acdVAYmNXXY0Edrk47N2A+BNxcHUUO4v/n+JK2JYdYKyVCFgJI6Y7QamzW02+SYTJs0IZbls1OrRk1iw==", "requires": { "@cospired/i18n-iso-languages": "2.1.2", - "axios": "0.18.1", + "axios": "0.21.1", "axios-cache-adapter": "^2.5.0", "form-urlencoded": "4.1.4", "glob": "7.1.6", @@ -3504,17 +3504,6 @@ "pubsub-js": "1.7.0", "react-intl": "2.9.0", "universal-cookie": "4.0.4" - }, - "dependencies": { - "axios": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", - "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", - "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" - } - } } }, "@edx/paragon": { @@ -6548,7 +6537,6 @@ "version": "0.21.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dev": true, "requires": { "follow-redirects": "^1.10.0" }, @@ -6556,18 +6544,17 @@ "follow-redirects": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", - "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==", - "dev": true + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" } } }, "axios-cache-adapter": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/axios-cache-adapter/-/axios-cache-adapter-2.5.0.tgz", - "integrity": "sha512-YcMPdMoqmSLoZx7A5YD/PdYGuX6/Y9M2tHBhaIXvXrPeGgNnbW7nb3+uArWlT53WGHLfclnu2voMmS7jGXVg6A==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/axios-cache-adapter/-/axios-cache-adapter-2.7.3.tgz", + "integrity": "sha512-A+ZKJ9lhpjthOEp4Z3QR/a9xC4du1ALaAsejgRGrH9ef6kSDxdFrhRpulqsh9khsEnwXxGfgpUuDp1YXMNMEiQ==", "requires": { "cache-control-esm": "1.0.0", - "lodash": "^4.17.11" + "md5": "^2.2.1" } }, "axios-mock-adapter": { @@ -8011,6 +7998,11 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, "check-types": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", @@ -8659,6 +8651,11 @@ } } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -9070,6 +9067,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -11296,6 +11294,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "dev": true, "requires": { "debug": "=3.1.0" } @@ -13088,9 +13087,10 @@ } }, "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true }, "is-callable": { "version": "1.2.0", @@ -17849,6 +17849,23 @@ "css-mediaquery": "^0.1.2" } }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + } + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -18306,7 +18323,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "multicast-dns": { "version": "6.2.3", diff --git a/package.json b/package.json index 7662e5eae..b9e42dd6d 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "dependencies": { "@edx/brand": "npm:@edx/brand-openedx@1.1.0", "@edx/frontend-component-footer": "10.0.11", - "@edx/frontend-platform": "1.8.0", + "@edx/frontend-platform": "1.9.0", "@edx/paragon": "13.13.5", "@fortawesome/fontawesome-svg-core": "1.2.28", "@fortawesome/free-brands-svg-icons": "5.11.2", @@ -64,7 +64,6 @@ "devDependencies": { "@edx/frontend-build": "5.6.9", "@testing-library/react": "10.4.7", - "axios": "0.21.1", "axios-mock-adapter": "1.18.1", "codecov": "3.7.1", "es-check": "5.1.0", diff --git a/src/proctored-exam-settings/ProctoredExamSettings.jsx b/src/proctored-exam-settings/ProctoredExamSettings.jsx index 7a6a35fb0..d7eb93824 100644 --- a/src/proctored-exam-settings/ProctoredExamSettings.jsx +++ b/src/proctored-exam-settings/ProctoredExamSettings.jsx @@ -510,8 +510,8 @@ function ProctoredExamSettings({ courseId, intl }) { setCreateZendeskTickets(proctoredExamSettings.create_zendesk_tickets); }, ).catch( - errorDetails => { - if (errorDetails.customAttributes.httpErrorStatus === 403) { + error => { + if (error.response.status === 403) { setLoadingPermissionError(true); } else { setLoadingConnectionError(true); diff --git a/src/proctored-exam-settings/ProctoredExamSettings.test.jsx b/src/proctored-exam-settings/ProctoredExamSettings.test.jsx index 9f0c85ae8..0ebea741f 100644 --- a/src/proctored-exam-settings/ProctoredExamSettings.test.jsx +++ b/src/proctored-exam-settings/ProctoredExamSettings.test.jsx @@ -3,7 +3,10 @@ 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 * as auth from '@edx/frontend-platform/auth'; +import MockAdapter from 'axios-mock-adapter'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import ProctoredExamSettings from './ProctoredExamSettings'; import StudioApiService from '../data/services/StudioApiService'; @@ -18,282 +21,295 @@ const intlWrapper = children => ( {children} ); +let axiosMock; -describe('ProctoredExamSettings field dependency tests', () => { - beforeEach(async () => { - auth.getAuthenticatedHttpClient = jest.fn(() => ({ - get: async () => ({ - data: { - 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', - }, - }), - })); - - auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, administrator: true })); - await act(async () => render(intlWrapper())); - }); - +describe('ProctoredExamSettings', () => { afterEach(() => { cleanup(); }); - 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(); - auth.getAuthenticatedHttpClient = jest.fn(() => ({ - get: async () => ({ - data: { - 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', + describe('Field dependencies', () => { + beforeEach(async () => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], }, - }), - })); + }); - 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(); - }); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); - 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(); - }); -}); - -describe('ProctoredExamSettings validation with invalid escalation email', () => { - beforeEach(async () => { - auth.getAuthenticatedHttpClient = jest.fn(() => ({ - get: async () => ({ - data: { - 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', + 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, }, - }), - post: async () => {}, - })); + available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], + course_start_date: '2070-01-01T00:00:00Z', + }); - auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, administrator: false })); - await act(async () => render(intlWrapper())); + 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(); + }); }); - afterEach(() => { - cleanup(); + describe('Validation with invalid escalation email', () => { + beforeEach(async () => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: false, + roles: [], + }, + }); + + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + + 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', + }); + + axiosMock.onPost( + StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), + ).reply(200, {}); + + 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('Has no error when valid 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.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(); + }); }); - 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); - }); - 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); - }); - fireEvent.click(errorLink); - const escalationEmailInput = screen.getByTestId('escalationEmail'); - expect(document.activeElement).toEqual(escalationEmailInput); - }); - - it('has no error when valid 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.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('Disables proctoring provider options', () => { - const mockGetFutureCourseData = { - data: { + describe('Proctoring provider options', () => { + const mockGetFutureCourseData = { proctored_exam_settings: { enable_proctored_exams: true, allow_proctoring_opt_out: false, @@ -303,11 +319,9 @@ describe('Disables proctoring provider options', () => { }, available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], course_start_date: '2099-01-01T00:00:00Z', - }, - }; + }; - const mockGetPastCourseData = { - data: { + const mockGetPastCourseData = { proctored_exam_settings: { enable_proctored_exams: true, allow_proctoring_opt_out: false, @@ -317,207 +331,206 @@ describe('Disables proctoring provider options', () => { }, available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], course_start_date: '2013-01-01T00:00:00Z', - }, - }; + }; - function mockAPI(getData, isAdmin) { - const mockClientGet = jest.fn(async () => (getData)); - auth.getAuthenticatedHttpClient = jest.fn(() => ({ - get: mockClientGet, - })); - auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, administrator: isAdmin })); - } - - afterEach(() => { - cleanup(); - }); - - it('disables irrelevant Proctoring Provider fields when user is not an administrator and it is after start date', async () => { - mockAPI(mockGetPastCourseData, false); - 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 () => { - mockAPI(mockGetFutureCourseData, false); - 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 () => { - mockAPI(mockGetPastCourseData, true); - 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 () => { - mockAPI(mockGetFutureCourseData, true); - await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); - expect(providerOption.hasAttribute('disabled')).toEqual(false); - }); -}); - -describe('Hides fields based on user permissions', () => { - beforeEach(async () => { - auth.getAuthenticatedHttpClient = jest.fn(() => ({ - get: async () => ({ - data: { - 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', + function setup(data, isAdmin) { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: isAdmin, + roles: [], }, - catch: () => {}, - }), - })); - }); + }); - afterEach(() => { - cleanup(); - }); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock.onGet(StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId)).reply(200, data); + } - function mockAuthentication(isAdmin) { - auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, administrator: isAdmin })); - } - - it('hides opting out and zendesk tickets for non edX staff', async () => { - mockAuthentication(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 () => { - mockAuthentication(true); - await act(async () => render(intlWrapper())); - expect(screen.queryByTestId('allowOptingOutYes')).not.toBeNull(); - expect(screen.queryByTestId('createZendeskTicketsYes')).not.toBeNull(); - }); -}); - -describe('ProctoredExamSettings connection states tests', () => { - it('shows the spinner before the connection is complete', async () => { - auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, administrator: false })); - auth.getAuthenticatedHttpClient = jest.fn(() => ({ - get: jest.fn(() => new Promise(() => {})), - })); - render(); - const spinner = screen.getByTestId('spinnerContainer'); - expect(spinner.textContent).toEqual('Loading...'); - }); - - it('show connection error message when we suffer server side error', async () => { - const errorObject = { - customAttributes: { - httpErrorStatus: 500, - }, - }; - auth.getAuthenticatedHttpClient = jest.fn(() => ({ - get: async () => { - throw errorObject; - }, - })); - - await act(async () => render(intlWrapper())); - const connectionError = screen.getByTestId('connectionError'); - expect(connectionError.textContent).toEqual( - expect.stringContaining('We encountered a technical error'), - ); - }); - - it('show permission error message when user do not have enough permission', async () => { - const errorObject = { - customAttributes: { - httpErrorStatus: 403, - }, - }; - auth.getAuthenticatedHttpClient = jest.fn(() => ({ - get: async () => { - throw errorObject; - }, - })); - - await act(async () => render(intlWrapper())); - const connectionError = screen.getByTestId('permissionError'); - expect(connectionError.textContent).toEqual( - expect.stringContaining('You are not authorized to view this page'), - ); - }); - - afterEach(() => { - cleanup(); - }); -}); - -describe('ProctoredExamSettings save settings tests', () => { - const mockGetData = { - data: { - 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'], - }, - }; - - function mockAPI(getData, postResult) { - const mockClientGet = jest.fn(async () => (getData)); - const mockClientPost = postResult ? jest.fn(async () => (postResult)) : jest.fn(async () => { throw new Error(); }); - auth.getAuthenticatedHttpClient = jest.fn(() => ({ - get: mockClientGet, - post: mockClientPost, - })); - auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, administrator: true })); - return { mockClientGet, mockClientPost }; - } - - it('Show spinner while saving', async () => { - const mockedFunctions = mockAPI(mockGetData, { data: 'success' }); - await act(async () => render(intlWrapper())); - const submitButton = screen.getByTestId('submissionButton'); - expect(screen.queryByTestId('saveInProgress')).toBeFalsy(); - fireEvent.click(submitButton); - const submitSpinner = screen.getByTestId('saveInProgress'); - expect(submitSpinner).toBeDefined(); - expect(mockedFunctions.mockClientPost).toHaveBeenCalled(); - await waitForElementToBeRemoved(submitSpinner); - expect(screen.queryByTestId('saveInProgress')).toBeFalsy(); - }); - - it('Makes API call successfully with proctoring_escalation_email if proctortrack', async () => { - const mockedFunctions = mockAPI(mockGetData, { data: 'success' }); - 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' } }); + it('Disables irrelevant proctoring provider fields when user is not an administrator and it is after start date', async () => { + setup(mockGetPastCourseData, false); + await act(async () => render(intlWrapper())); + const providerOption = screen.getByTestId('proctortrack'); + expect(providerOption.hasAttribute('disabled')).toEqual(true); }); - const escalationEmail = screen.getByTestId('escalationEmail'); - expect(escalationEmail.value).toEqual('test@example.com'); - await act(async () => { - fireEvent.change(escalationEmail, { target: { value: 'proctortrack@example.com' } }); + + it('Enables all proctoring provider options if user is not an administrator and it is before start date', async () => { + setup(mockGetFutureCourseData, false); + await act(async () => render(intlWrapper())); + const providerOption = screen.getByTestId('proctortrack'); + expect(providerOption.hasAttribute('disabled')).toEqual(false); }); - expect(escalationEmail.value).toEqual('proctortrack@example.com'); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); + + it('Enables all proctoring provider options if user administrator and it is after start date', async () => { + setup(mockGetPastCourseData, true); + await act(async () => render(intlWrapper())); + const providerOption = screen.getByTestId('proctortrack'); + expect(providerOption.hasAttribute('disabled')).toEqual(false); }); - expect(mockedFunctions.mockClientPost).toHaveBeenCalled(); - expect(mockedFunctions.mockClientPost).toHaveBeenCalledWith( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - { + + it('Enables all proctoring provider options if user administrator and it is before start date', async () => { + setup(mockGetFutureCourseData, true); + await act(async () => render(intlWrapper())); + const providerOption = screen.getByTestId('proctortrack'); + expect(providerOption.hasAttribute('disabled')).toEqual(false); + }); + }); + + describe('Toggles field visibility based on user permissions', () => { + function setup(isAdmin) { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: isAdmin, + roles: [], + }, + }); + + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + 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', + }); + } + + it('Hides opting out and zendesk tickets for non edX staff', async () => { + setup(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 () => { + setup(true); + await act(async () => render(intlWrapper())); + expect(screen.queryByTestId('allowOptingOutYes')).not.toBeNull(); + expect(screen.queryByTestId('createZendeskTicketsYes')).not.toBeNull(); + }); + }); + + describe('Connection states', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + }); + + 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.getByTestId('spinnerContainer'); + expect(spinner.textContent).toEqual('Loading...'); + }); + }); + + it('Show connection error message when we suffer server side error', async () => { + axiosMock.onGet( + StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), + ).reply(500); + + await act(async () => render(intlWrapper())); + const connectionError = screen.getByTestId('connectionError'); + expect(connectionError.textContent).toEqual( + expect.stringContaining('We encountered a technical error'), + ); + }); + + 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 connectionError = screen.getByTestId('permissionError'); + expect(connectionError.textContent).toEqual( + expect.stringContaining('You are not authorized to view this page'), + ); + }); + }); + + describe('Save settings', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + + axiosMock = new MockAdapter(getAuthenticatedHttpClient(), { onNoMatch: 'throwException' }); + 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'], + }); + }); + + 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); + expect(axiosMock.history.get.length).toBe(1); + expect(axiosMock.history.post.length).toBe(1); + expect(screen.queryByTestId('saveInProgress')).toBeFalsy(); + }); + + it('Makes API call successfully with proctoring_escalation_email if proctortrack', async () => { + axiosMock.onPost( + StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), + ).reply(200, 'success'); + + 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, @@ -525,89 +538,96 @@ describe('ProctoredExamSettings save settings tests', () => { 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 () => { - const mockedFunctions = mockAPI(mockGetData, { data: 'success' }); - 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); + const errorAlert = screen.getByTestId('saveSuccess'); + expect(errorAlert.textContent).toEqual( + expect.stringContaining('Proctored exam settings saved successfully.'), + ); + expect(document.activeElement).toEqual(errorAlert); }); - expect(mockedFunctions.mockClientPost).toHaveBeenCalled(); - expect(mockedFunctions.mockClientPost).toHaveBeenCalledWith( - StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), - { + + it('Makes API call successfully without proctoring_escalation_email if not proctortrack', async () => { + axiosMock.onPost( + StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), + ).reply(200, 'success'); + + 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('Makes API call generated error', async () => { - const mockedFunctions = mockAPI(mockGetData, false); - await act(async () => render(intlWrapper())); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); + const errorAlert = screen.getByTestId('saveSuccess'); + expect(errorAlert.textContent).toEqual( + expect.stringContaining('Proctored exam settings saved successfully.'), + ); + expect(document.activeElement).toEqual(errorAlert); }); - expect(mockedFunctions.mockClientPost).toHaveBeenCalled(); - 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 - const mockedFunctionsBadCall = mockAPI(mockGetData, false); - await act(async () => render(intlWrapper())); - const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); + it('Makes 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); }); - expect(mockedFunctionsBadCall.mockClientPost).toHaveBeenCalled(); - 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 - const mockedFunctionsGoodCall = mockAPI(mockGetData, { data: 'success' }); - await act(async () => { - fireEvent.click(submitButton); + 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), + ).replyOnce(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), + ).replyOnce(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); }); - expect(mockedFunctionsGoodCall.mockClientPost).toHaveBeenCalled(); - const successAlert = screen.getByTestId('saveSuccess'); - expect(successAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(successAlert); - }); - - afterEach(() => { - cleanup(); }); }); diff --git a/src/setupTest.js b/src/setupTest.js index 9a11bbb28..36691d2c3 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -1,9 +1,5 @@ -import axios from 'axios'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import 'core-js/stable'; import 'regenerator-runtime/runtime'; import { configure } from '@testing-library/react'; configure({ testIdAttribute: 'data-test-id' }); -jest.mock('@edx/frontend-platform/auth'); -getAuthenticatedHttpClient.mockReturnValue(axios);