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);