diff --git a/README.rst b/README.rst index 6f1de194b4..d0680a32e5 100644 --- a/README.rst +++ b/README.rst @@ -267,8 +267,6 @@ Tagging/Taxonomy functionality. Feature: Libraries V2/Legacy Tabs ================================= -.. image:: ./docs/readme-images/feature-v2-and-legacy-libs.png - Configuration ------------- diff --git a/docs/readme-images/feature-v2-and-legacy-libs.png b/docs/readme-images/feature-v2-and-legacy-libs.png deleted file mode 100644 index c8fd363655..0000000000 Binary files a/docs/readme-images/feature-v2-and-legacy-libs.png and /dev/null differ diff --git a/plugins/course-apps/proctoring/Settings.test.jsx b/plugins/course-apps/proctoring/Settings.test.jsx index bd544ebb3b..c4455be4ed 100644 --- a/plugins/course-apps/proctoring/Settings.test.jsx +++ b/plugins/course-apps/proctoring/Settings.test.jsx @@ -103,9 +103,7 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('mockproc'); }); const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); - }); + fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); const zendeskTicketInput = screen.getByTestId('createZendeskTicketsNo'); expect(zendeskTicketInput.checked).toEqual(true); }); @@ -115,9 +113,7 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('mockproc'); }); const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'software_secure' } }); - }); + fireEvent.change(selectElement, { target: { value: 'software_secure' } }); const zendeskTicketInput = screen.getByTestId('createZendeskTicketsYes'); expect(zendeskTicketInput.checked).toEqual(true); }); @@ -127,9 +123,7 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('mockproc'); }); const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'mockproc' } }); - }); + fireEvent.change(selectElement, { target: { value: 'mockproc' } }); const zendeskTicketInput = screen.getByTestId('createZendeskTicketsYes'); expect(zendeskTicketInput.checked).toEqual(true); }); @@ -176,9 +170,7 @@ describe('ProctoredExamSettings', () => { let enabledProctoredExamCheck = screen.getAllByLabelText('Proctored exams', { exact: false })[0]; expect(enabledProctoredExamCheck.checked).toEqual(true); - await act(async () => { - fireEvent.click(enabledProctoredExamCheck, { target: { value: false } }); - }); + fireEvent.click(enabledProctoredExamCheck, { target: { value: false } }); enabledProctoredExamCheck = screen.getByLabelText('Proctored exams'); expect(enabledProctoredExamCheck.checked).toEqual(false); expect(screen.queryByText('Allow opting out of proctored exams')).toBeNull(); @@ -193,9 +185,7 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('mockproc'); }); const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'test_lti' } }); - }); + fireEvent.change(selectElement, { target: { value: 'test_lti' } }); expect(screen.queryByTestId('allowOptingOutRadio')).toBeNull(); expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull(); expect(screen.queryByTestId('createZendeskTicketsNo')).toBeNull(); @@ -237,13 +227,9 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('proctortrack'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); - }); + fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); const selectButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(selectButton); - }); + fireEvent.click(selectButton); // verify alert content and focus management const escalationEmailError = screen.getByTestId('escalationEmailError'); @@ -252,9 +238,7 @@ describe('ProctoredExamSettings', () => { // verify alert link links to offending input const errorLink = screen.getByTestId('escalationEmailErrorLink'); - await act(async () => { - fireEvent.click(errorLink); - }); + fireEvent.click(errorLink); const escalationEmailInput = screen.getByTestId('escalationEmail'); expect(document.activeElement).toEqual(escalationEmailInput); }); @@ -265,18 +249,12 @@ describe('ProctoredExamSettings', () => { }); const selectElement = screen.getByDisplayValue('proctortrack'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: provider } }); - }); + fireEvent.change(selectElement, { target: { value: provider } }); 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); - }); + fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } }); + const proctoringForm = screen.getByTestId('proctoringForm'); + fireEvent.submit(proctoringForm); // verify alert content and focus management const escalationEmailError = screen.getByTestId('escalationEmailError'); @@ -286,9 +264,7 @@ describe('ProctoredExamSettings', () => { // verify alert link links to offending input const errorLink = screen.getByTestId('escalationEmailErrorLink'); - await act(async () => { - fireEvent.click(errorLink); - }); + fireEvent.click(errorLink); const escalationEmailInput = screen.getByTestId('escalationEmail'); expect(document.activeElement).toEqual(escalationEmailInput); }); @@ -298,15 +274,11 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('proctortrack'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } }); - }); + fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } }); const enableProctoringElement = screen.getByText('Proctored exams'); - await act(async () => fireEvent.click(enableProctoringElement)); + fireEvent.click(enableProctoringElement); const selectButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(selectButton); - }); + fireEvent.click(selectButton); // verify alert content and focus management const escalationEmailError = screen.getByTestId('escalationEmailError'); @@ -320,24 +292,22 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('proctortrack'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); - }); + fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); const enableProctoringElement = screen.getByText('Proctored exams'); - await act(async () => fireEvent.click(enableProctoringElement)); + fireEvent.click(enableProctoringElement); const selectButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(selectButton); - }); + fireEvent.click(selectButton); // verify there is no escalation email alert, and focus has been set on save success alert expect(screen.queryByTestId('escalationEmailError')).toBeNull(); - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); + await waitFor(() => { + const errorAlert = screen.getByTestId('saveSuccess'); + expect(errorAlert.textContent).toEqual( + expect.stringContaining('Proctored exam settings saved successfully.'), + ); + expect(document.activeElement).toEqual(errorAlert); + }); }); it(`Has no error when valid proctoring escalation email is provided with ${provider} selected`, async () => { @@ -345,22 +315,20 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('proctortrack'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo@bar.com' } }); - }); + fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo@bar.com' } }); const selectButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(selectButton); - }); + fireEvent.click(selectButton); // verify there is no escalation email alert, and focus has been set on save success alert expect(screen.queryByTestId('escalationEmailError')).toBeNull(); - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); + await waitFor(() => { + 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 ${provider}`, async () => { @@ -370,9 +338,7 @@ describe('ProctoredExamSettings', () => { 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' } }); - }); + fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } }); expect(screen.queryByTestId('escalationEmail')).toBeNull(); }); @@ -382,13 +348,9 @@ describe('ProctoredExamSettings', () => { }); const proctoringBackendSelect = screen.getByDisplayValue('proctortrack'); let selectEscalationEmailElement = screen.getByTestId('escalationEmail'); - await act(async () => { - fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } }); - }); + fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } }); expect(screen.queryByTestId('escalationEmail')).toBeNull(); - await act(async () => { - fireEvent.change(proctoringBackendSelect, { target: { value: 'proctortrack' } }); - }); + fireEvent.change(proctoringBackendSelect, { target: { value: 'proctortrack' } }); expect(screen.queryByTestId('escalationEmail')).toBeDefined(); selectEscalationEmailElement = screen.getByTestId('escalationEmail'); expect(selectEscalationEmailElement.value).toEqual('test@example.com'); @@ -399,12 +361,8 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('proctortrack'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); - await act(async () => { - fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); - }); - await act(async () => { - fireEvent.submit(selectEscalationEmailElement); - }); + fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); + fireEvent.submit(selectEscalationEmailElement); // if the error appears, the form has been submitted expect(screen.getByTestId('escalationEmailError')).toBeDefined(); }); @@ -628,9 +586,7 @@ describe('ProctoredExamSettings', () => { await act(async () => render(intlWrapper())); let submitButton = screen.getByTestId('submissionButton'); expect(screen.queryByTestId('saveInProgress')).toBeFalsy(); - act(() => { - fireEvent.click(submitButton); - }); + fireEvent.click(submitButton); submitButton = screen.getByTestId('submissionButton'); expect(submitButton).toHaveAttribute('disabled'); @@ -640,19 +596,13 @@ describe('ProctoredExamSettings', () => { 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' } }); - }); + 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' } }); - }); + 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); - }); + fireEvent.click(submitButton); expect(axiosMock.history.post.length).toBe(1); expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ proctored_exam_settings: { @@ -664,11 +614,13 @@ describe('ProctoredExamSettings', () => { }, }); - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); + await waitFor(() => { + 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 () => { @@ -678,9 +630,7 @@ describe('ProctoredExamSettings', () => { expect(screen.getByDisplayValue('mockproc')).toBeDefined(); const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); + fireEvent.click(submitButton); expect(axiosMock.history.post.length).toBe(1); expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ proctored_exam_settings: { @@ -691,32 +641,28 @@ describe('ProctoredExamSettings', () => { }, }); - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); + await waitFor(() => { + const errorAlert = screen.getByTestId('saveSuccess'); + expect(errorAlert.textContent).toEqual( + expect.stringContaining('Proctored exam settings saved successfully.'), + ); + expect(document.activeElement).toEqual(errorAlert); + }); }); it('Successfully updates exam configuration and studio provider is set to "lti_external" for lti providers', async () => { await act(async () => render(intlWrapper())); // Make a change to the provider to test_lti and set the email const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'test_lti' } }); - }); + fireEvent.change(selectElement, { target: { value: 'test_lti' } }); const escalationEmail = screen.getByTestId('escalationEmail'); expect(escalationEmail.value).toEqual('test@example.com'); - await act(async () => { - fireEvent.change(escalationEmail, { target: { value: 'test_lti@example.com' } }); - }); + fireEvent.change(escalationEmail, { target: { value: 'test_lti@example.com' } }); expect(escalationEmail.value).toEqual('test_lti@example.com'); const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); + fireEvent.click(submitButton); // update exam service config expect(axiosMock.history.patch.length).toBe(1); @@ -736,19 +682,19 @@ describe('ProctoredExamSettings', () => { }, }); - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); + await waitFor(() => { + const errorAlert = screen.getByTestId('saveSuccess'); + expect(errorAlert.textContent).toEqual( + expect.stringContaining('Proctored exam settings saved successfully.'), + ); + expect(document.activeElement).toEqual(errorAlert); + }); }); it('Sets exam service provider to null if a non-lti provider is selected', async () => { await act(async () => render(intlWrapper())); const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); + fireEvent.click(submitButton); // update exam service config expect(axiosMock.history.patch.length).toBe(1); expect(JSON.parse(axiosMock.history.patch[0].data)).toEqual({ @@ -766,11 +712,13 @@ describe('ProctoredExamSettings', () => { }, }); - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); + await waitFor(() => { + const errorAlert = screen.getByTestId('saveSuccess'); + expect(errorAlert.textContent).toEqual( + expect.stringContaining('Proctored exam settings saved successfully.'), + ); + expect(document.activeElement).toEqual(errorAlert); + }); }); it('Does not update exam service if lti is not enabled in studio', async () => { @@ -790,9 +738,7 @@ describe('ProctoredExamSettings', () => { await act(async () => render(intlWrapper())); const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); + fireEvent.click(submitButton); // does not update exam service config expect(axiosMock.history.patch.length).toBe(0); // does update studio @@ -806,11 +752,13 @@ describe('ProctoredExamSettings', () => { }, }); - const errorAlert = screen.getByTestId('saveSuccess'); - expect(errorAlert.textContent).toEqual( - expect.stringContaining('Proctored exam settings saved successfully.'), - ); - expect(document.activeElement).toEqual(errorAlert); + await waitFor(() => { + const errorAlert = screen.getByTestId('saveSuccess'); + expect(errorAlert.textContent).toEqual( + expect.stringContaining('Proctored exam settings saved successfully.'), + ); + expect(document.activeElement).toEqual(errorAlert); + }); }); it('Makes studio API call generated error', async () => { @@ -820,15 +768,15 @@ describe('ProctoredExamSettings', () => { await act(async () => render(intlWrapper())); const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); + 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); + await waitFor(() => { + const errorAlert = screen.getByTestId('saveError'); + expect(errorAlert.textContent).toEqual( + expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'), + ); + expect(document.activeElement).toEqual(errorAlert); + }); }); it('Makes exams API call generated error', async () => { @@ -838,15 +786,15 @@ describe('ProctoredExamSettings', () => { await act(async () => render(intlWrapper())); const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); + 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); + await waitFor(() => { + 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 () => { @@ -857,30 +805,30 @@ describe('ProctoredExamSettings', () => { await act(async () => render(intlWrapper())); const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); + 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); + await waitFor(() => { + const errorAlert = screen.getByTestId('saveError'); + expect(errorAlert.textContent).toEqual( + expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'), + ); + expect(document.activeElement).toEqual(errorAlert); + }); // now make a call that will allow for a successful save axiosMock.onPost( StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), ).reply(200, 'success'); - await act(async () => { - fireEvent.click(submitButton); - }); + 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); + await waitFor(() => { + const successAlert = screen.getByTestId('saveSuccess'); + expect(successAlert.textContent).toEqual( + expect.stringContaining('Proctored exam settings saved successfully.'), + ); + expect(document.activeElement).toEqual(successAlert); + }); }); it('Include Zendesk ticket in post request if user is not an admin', async () => { @@ -891,13 +839,9 @@ describe('ProctoredExamSettings', () => { await act(async () => render(intlWrapper())); // Make a change to the proctoring provider const selectElement = screen.getByDisplayValue('mockproc'); - await act(async () => { - fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); - }); + fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); const submitButton = screen.getByTestId('submissionButton'); - await act(async () => { - fireEvent.click(submitButton); - }); + fireEvent.click(submitButton); expect(axiosMock.history.post.length).toBe(1); expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ proctored_exam_settings: { diff --git a/src/accessibility-page/messages.js b/src/accessibility-page/messages.js index 6b97fb96e9..b15a88832c 100644 --- a/src/accessibility-page/messages.js +++ b/src/accessibility-page/messages.js @@ -2,7 +2,7 @@ import { defineMessages } from '@edx/frontend-platform/i18n'; const messages = defineMessages({ pageTitle: { - id: 'course-authoring.import.page.title', + id: 'course-authoring.accessibility.page.title', defaultMessage: 'Studio Accessibility Policy| {siteName}', }, }); diff --git a/src/certificates/layout/header-buttons/HeaderButtons.test.jsx b/src/certificates/layout/header-buttons/HeaderButtons.test.jsx index e774774d76..7adf1072d1 100644 --- a/src/certificates/layout/header-buttons/HeaderButtons.test.jsx +++ b/src/certificates/layout/header-buttons/HeaderButtons.test.jsx @@ -59,10 +59,10 @@ describe('HeaderButtons Component', () => { expect(previewLink).toHaveAttribute('href', expect.stringContaining(certificatesDataMock.courseModes[0])); const dropdownButton = getByRole('button', { name: certificatesDataMock.courseModes[0] }); - await userEvent.click(dropdownButton); + userEvent.click(dropdownButton); const verifiedMode = await getByRole('button', { name: certificatesDataMock.courseModes[1] }); - await userEvent.click(verifiedMode); + userEvent.click(verifiedMode); await waitFor(() => { expect(previewLink).toHaveAttribute('href', expect.stringContaining(certificatesDataMock.courseModes[1])); @@ -78,7 +78,7 @@ describe('HeaderButtons Component', () => { const { getByRole, queryByRole } = renderComponent(); const activationButton = getByRole('button', { name: messages.headingActionsActivate.defaultMessage }); - await userEvent.click(activationButton); + userEvent.click(activationButton); axiosMock.onPost( getUpdateCertificateApiUrl(courseId, certificatesDataMock.certificates[0].id), @@ -110,7 +110,7 @@ describe('HeaderButtons Component', () => { const { getByRole, queryByRole } = renderComponent(); const deactivateButton = getByRole('button', { name: messages.headingActionsDeactivate.defaultMessage }); - await userEvent.click(deactivateButton); + userEvent.click(deactivateButton); axiosMock.onPost( getUpdateCertificateApiUrl(courseId, certificatesDataMock.certificates[0].id), diff --git a/src/content-tags-drawer/messages.js b/src/content-tags-drawer/messages.js index 4b69010e58..d461d32230 100644 --- a/src/content-tags-drawer/messages.js +++ b/src/content-tags-drawer/messages.js @@ -69,7 +69,7 @@ const messages = defineMessages({ defaultMessage: 'Add a tag', }, collapsibleNoTagsAddedText: { - id: 'course-authoring.content-tags-drawer.content-tags-collapsible.custom-menu.placeholder-text', + id: 'course-authoring.content-tags-drawer.content-tags-collapsible.custom-menu.no-tags-added-text', defaultMessage: 'No tags added yet.', }, collapsibleAddStagedTagsButtonText: { diff --git a/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx b/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx index b254a79c16..92fb83ea32 100644 --- a/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx +++ b/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx @@ -79,10 +79,9 @@ const ChecklistItemComment = ({
    {gradedAssignmentsOutsideDateRange.map(assignment => (
  • - + + {assignment.displayName} +
  • ))}
diff --git a/src/course-outline/card-header/messages.js b/src/course-outline/card-header/messages.js index 410443d695..0aeb34b20e 100644 --- a/src/course-outline/card-header/messages.js +++ b/src/course-outline/card-header/messages.js @@ -58,7 +58,7 @@ const messages = defineMessages({ defaultMessage: 'Delete', }, menuCopy: { - id: 'course-authoring.course-outline.card.menu.delete', + id: 'course-authoring.course-outline.card.menu.copy', defaultMessage: 'Copy to clipboard', }, menuProctoringLinkText: { diff --git a/src/course-outline/data/thunk.js b/src/course-outline/data/thunk.js index 3508fde0bf..315c5846c0 100644 --- a/src/course-outline/data/thunk.js +++ b/src/course-outline/data/thunk.js @@ -57,7 +57,10 @@ import { const getErrorDetails = (error, dismissible = true) => { const errorInfo = { dismissible }; if (error.response?.data) { - errorInfo.data = JSON.stringify(error.response.data); + const { data } = error.response; + if ((typeof data === 'string' && !data.includes('')) || typeof data === 'object') { + errorInfo.data = JSON.stringify(data); + } errorInfo.status = error.response.status; errorInfo.type = API_ERROR_TYPES.serverError; } else if (error.request) { diff --git a/src/course-outline/page-alerts/PageAlerts.jsx b/src/course-outline/page-alerts/PageAlerts.jsx index 4b0eb0dc78..04bae82ad1 100644 --- a/src/course-outline/page-alerts/PageAlerts.jsx +++ b/src/course-outline/page-alerts/PageAlerts.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import { uniqBy } from 'lodash'; import { getConfig } from '@edx/frontend-platform'; import { useDispatch, useSelector } from 'react-redux'; import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; @@ -336,15 +337,13 @@ const PageAlerts = ({ }; const renderApiErrors = () => { - const errorList = Object.entries(errors).filter(obj => obj[1] !== null).map(([k, v]) => { + let errorList = Object.entries(errors).filter(obj => obj[1] !== null).map(([k, v]) => { switch (v.type) { case API_ERROR_TYPES.serverError: return { key: k, - desc: v.data, - title: intl.formatMessage(messages.serverErrorAlert, { - status: v.status, - }), + desc: v.data || intl.formatMessage(messages.serverErrorAlertBody), + title: intl.formatMessage(messages.serverErrorAlert), dismissible: v.dismissible, }; case API_ERROR_TYPES.networkError: @@ -356,11 +355,12 @@ const PageAlerts = ({ default: return { key: k, - desc: v.data, + title: v.data, dismissible: v.dismissible, }; } }); + errorList = uniqBy(errorList, 'title'); if (!errorList?.length) { return null; } @@ -373,10 +373,7 @@ const PageAlerts = ({ key={msgObj.key} dismissError={() => dispatch(dismissError(msgObj.key))} > - {msgObj.title - && ( - {msgObj.title} - )} + {msgObj.title} {msgObj.desc && {msgObj.desc}} ) : ( @@ -385,10 +382,7 @@ const PageAlerts = ({ icon={ErrorIcon} key={msgObj.key} > - {msgObj.title - && ( - {msgObj.title} - )} + {msgObj.title} {msgObj.desc && {msgObj.desc}} ) diff --git a/src/course-outline/page-alerts/messages.js b/src/course-outline/page-alerts/messages.js index ea67c76ad9..f9638398d8 100644 --- a/src/course-outline/page-alerts/messages.js +++ b/src/course-outline/page-alerts/messages.js @@ -22,7 +22,7 @@ const messages = defineMessages({ description: 'Learn more link in upgraded discussion notification alert', }, discussionNotificationFeedback: { - id: 'course-authoring.course-outline.page-alerts.discussionNotificationLearnMore', + id: 'course-authoring.course-outline.page-alerts.discussionNotificationFeedback', defaultMessage: 'Share feedback', description: 'Share feedback link in upgraded discussion notification alert', }, @@ -108,7 +108,12 @@ const messages = defineMessages({ }, serverErrorAlert: { id: 'course-authoring.course-outline.page-alert.server-error.title', - defaultMessage: 'Request failed with status: {status}', + defaultMessage: 'The Studio servers encountered an error', + description: 'Generic server error alert title.', + }, + serverErrorAlertBody: { + id: 'course-authoring.course-outline.page-alert.server-error.body', + defaultMessage: ' An error occurred in Studio and the page could not be loaded. Please try again in a few moments. We\'ve logged the error and our staff is currently working to resolve this error as soon as possible.', description: 'Generic server error alert title.', }, networkErrorAlert: { diff --git a/src/course-outline/status-bar/messages.js b/src/course-outline/status-bar/messages.js index e7c752f92b..0f3f105737 100644 --- a/src/course-outline/status-bar/messages.js +++ b/src/course-outline/status-bar/messages.js @@ -56,7 +56,7 @@ const messages = defineMessages({ defaultMessage: 'Video Sharing', }, videoSharingLink: { - id: 'course-authoring.course-outline.status-bar.video-sharing.title', + id: 'course-authoring.course-outline.status-bar.video-sharing.link', defaultMessage: 'Learn more', }, videoSharingPerVideoText: { diff --git a/src/course-outline/subsection-card/messages.js b/src/course-outline/subsection-card/messages.js index b4a0b5661a..f741b02ecc 100644 --- a/src/course-outline/subsection-card/messages.js +++ b/src/course-outline/subsection-card/messages.js @@ -6,7 +6,7 @@ const messages = defineMessages({ defaultMessage: 'New unit', }, pasteButton: { - id: 'course-authoring.course-outline.subsection.button.new-unit', + id: 'course-authoring.course-outline.subsection.button.paste-unit', defaultMessage: 'Paste unit', }, }); diff --git a/src/course-team/course-team-member/messages.js b/src/course-team/course-team-member/messages.js index 87bb371682..548b733b13 100644 --- a/src/course-team/course-team-member/messages.js +++ b/src/course-team/course-team-member/messages.js @@ -22,7 +22,7 @@ const messages = defineMessages({ defaultMessage: 'Add admin access', }, removeButton: { - id: 'course-authoring.course-team.member.button.remove', + id: 'course-authoring.course-team.member.button.remove-admin-access', defaultMessage: 'Remove admin access', }, deleteUserButton: { diff --git a/src/course-unit/add-component/add-component-modals/ComponentModalView.jsx b/src/course-unit/add-component/add-component-modals/ComponentModalView.jsx index ae1464a2c5..4f387d4bd6 100644 --- a/src/course-unit/add-component/add-component-modals/ComponentModalView.jsx +++ b/src/course-unit/add-component/add-component-modals/ComponentModalView.jsx @@ -42,7 +42,7 @@ const ComponentModalView = ({ setModuleTitle('')} diff --git a/src/files-and-videos/files-page/FilesPage.test.jsx b/src/files-and-videos/files-page/FilesPage.test.jsx index 1a91280bc6..f64724cf92 100644 --- a/src/files-and-videos/files-page/FilesPage.test.jsx +++ b/src/files-and-videos/files-page/FilesPage.test.jsx @@ -1,6 +1,5 @@ import { render, - act, fireEvent, screen, waitFor, @@ -71,6 +70,15 @@ const mockStore = async ( } renderComponent(); await executeThunk(fetchAssets(courseId), store.dispatch); + + // Finish loading the expected files into the data table before returning, + // because loading new files can disrupt things like accessing file menus. + if (status === RequestStatus.SUCCESSFUL) { + const numFiles = skipNextPageFetch ? 13 : 15; + await waitFor(() => { + expect(screen.getByText(`Showing ${numFiles} of ${numFiles}`)).toBeInTheDocument(); + }); + } }; const emptyMockStore = async (status) => { @@ -126,15 +134,13 @@ describe('FilesAndUploads', () => { it('should upload a single file', async () => { await emptyMockStore(RequestStatus.SUCCESSFUL); const dropzone = screen.getByTestId('files-dropzone'); - await act(async () => { - axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] }); - axiosMock.onPost(getAssetsUrl(courseId)).reply(204, generateNewAssetApiResponse()); - Object.defineProperty(dropzone, 'files', { - value: [file], - }); - fireEvent.drop(dropzone); - await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); + axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] }); + axiosMock.onPost(getAssetsUrl(courseId)).reply(204, generateNewAssetApiResponse()); + Object.defineProperty(dropzone, 'files', { + value: [file], }); + fireEvent.drop(dropzone); + await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); const addStatus = store.getState().assets.addingStatus; expect(addStatus).toEqual(RequestStatus.SUCCESSFUL); @@ -185,9 +191,7 @@ describe('FilesAndUploads', () => { expect(screen.queryByRole('table')).toBeNull(); const listButton = screen.getByLabelText('List'); - await act(async () => { - fireEvent.click(listButton); - }); + fireEvent.click(listButton); expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull(); expect(screen.getByRole('table')).toBeVisible(); @@ -200,16 +204,13 @@ describe('FilesAndUploads', () => { await mockStore(RequestStatus.SUCCESSFUL); axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] }); axiosMock.onPost(getAssetsUrl(courseId)).reply(200, generateNewAssetApiResponse()); - let addFilesButton; + const addFilesButton = screen.getByLabelText('file-input'); + userEvent.upload(addFilesButton, file); + await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); await waitFor(() => { - addFilesButton = screen.getByLabelText('file-input'); - }); - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); + const addStatus = store.getState().assets.addingStatus; + expect(addStatus).toEqual(RequestStatus.SUCCESSFUL); }); - const addStatus = store.getState().assets.addingStatus; - expect(addStatus).toEqual(RequestStatus.SUCCESSFUL); }); it('should show duplicate file modal', async () => { @@ -219,14 +220,9 @@ describe('FilesAndUploads', () => { axiosMock.onGet( `${getAssetsUrl(courseId)}?display_name=mOckID6&page_size=1`, ).reply(200, { assets: [{ display_name: 'mOckID6' }] }); - let addFilesButton; - await waitFor(() => { - addFilesButton = screen.getByLabelText('file-input'); - }); - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); - }); + const addFilesButton = screen.getByLabelText('file-input'); + userEvent.upload(addFilesButton, file); + await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); expect(screen.getByText(filesPageMessages.overwriteConfirmMessage.defaultMessage)).toBeVisible(); }); @@ -245,26 +241,21 @@ describe('FilesAndUploads', () => { }; axiosMock.onPost(getAssetsUrl(courseId)).reply(200, responseData); - let addFilesButton; - await waitFor(() => { - addFilesButton = screen.getByLabelText('file-input'); - }); - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); - }); + const addFilesButton = screen.getByLabelText('file-input'); + userEvent.upload(addFilesButton, file); + await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); const overwriteButton = screen.getByText(filesPageMessages.confirmOverwriteButtonLabel.defaultMessage); - await act(async () => { - fireEvent.click(overwriteButton); - }); - - const assetData = store.getState().models.assets.mOckID6; - const { asset: responseAssetData } = responseData; - const [defaultData] = updateFileValues([camelCaseObject(responseAssetData)]); + fireEvent.click(overwriteButton); expect(screen.queryByText(filesPageMessages.overwriteConfirmMessage.defaultMessage)).toBeNull(); - expect(assetData).toEqual(defaultData); + await waitFor(() => { + const assetData = store.getState().models.assets.mOckID6; + const { asset: responseAssetData } = responseData; + const [defaultData] = updateFileValues([camelCaseObject(responseAssetData)]); + + expect(assetData).toEqual(defaultData); + }); }); it('should keep original file', async () => { @@ -274,19 +265,12 @@ describe('FilesAndUploads', () => { axiosMock.onGet( `${getAssetsUrl(courseId)}?display_name=mOckID6&page_size=1`, ).reply(200, { assets: [{ display_name: 'mOckID6' }] }); - let addFilesButton; - await waitFor(() => { - addFilesButton = screen.getByLabelText('file-input'); - }); - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); - }); + const addFilesButton = screen.getByLabelText('file-input'); + userEvent.upload(addFilesButton, file); + await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch); const cancelButton = screen.getByText(filesPageMessages.cancelOverwriteButtonLabel.defaultMessage); - await act(async () => { - fireEvent.click(cancelButton); - }); + fireEvent.click(cancelButton); const assetData = store.getState().models.assets.mOckID6; const defaultAssets = generateFetchAssetApiResponse().assets; @@ -299,12 +283,9 @@ describe('FilesAndUploads', () => { it('should have disabled action buttons', async () => { await mockStore(RequestStatus.SUCCESSFUL); - let actionsButton; - await waitFor(() => { - actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); - fireEvent.click(actionsButton); - }); + const actionsButton = await screen.getByText(messages.actionsButtonLabel.defaultMessage); + fireEvent.click(actionsButton); expect(screen.getByText(messages.downloadTitle.defaultMessage).closest('a')).toHaveClass('disabled'); expect(screen.getByText(messages.deleteTitle.defaultMessage).closest('a')).toHaveClass('disabled'); @@ -312,19 +293,14 @@ describe('FilesAndUploads', () => { it('delete button should be enabled and delete selected file', async () => { await mockStore(RequestStatus.SUCCESSFUL); - let selectCardButton; - await waitFor(() => { - [selectCardButton] = screen.getAllByTestId('datatable-select-column-checkbox-cell'); - fireEvent.click(selectCardButton); - }); + const [selectCardButton] = await screen.findAllByTestId('datatable-select-column-checkbox-cell'); + fireEvent.click(selectCardButton); const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); expect(actionsButton).toBeVisible(); + fireEvent.click(actionsButton); - await waitFor(() => { - fireEvent.click(actionsButton); - }); const deleteButton = screen.getByText(messages.deleteTitle.defaultMessage).closest('a'); expect(deleteButton).not.toHaveClass('disabled'); @@ -332,24 +308,21 @@ describe('FilesAndUploads', () => { fireEvent.click(deleteButton); expect(screen.getByText('Delete mOckID1')).toBeVisible(); - await act(async () => { - userEvent.click(deleteButton); - }); + fireEvent.click(deleteButton); // Wait for the delete confirmation button to appear const confirmDeleteButton = await screen.findByRole('button', { name: messages.deleteFileButtonLabel.defaultMessage, }); - - await act(async () => { - userEvent.click(confirmDeleteButton); - }); + fireEvent.click(confirmDeleteButton); expect(screen.queryByText('Delete mOckID1')).toBeNull(); // Check if the asset is deleted in the store and UI - const deleteStatus = store.getState().assets.deletingStatus; - expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL); + await waitFor(() => { + const deleteStatus = store.getState().assets.deletingStatus; + expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL); + }); expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull(); }); @@ -360,9 +333,7 @@ describe('FilesAndUploads', () => { const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); expect(actionsButton).toBeVisible(); - await waitFor(() => { - fireEvent.click(actionsButton); - }); + fireEvent.click(actionsButton); const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a'); expect(downloadButton).not.toHaveClass('disabled'); @@ -378,9 +349,7 @@ describe('FilesAndUploads', () => { const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); expect(actionsButton).toBeVisible(); - await waitFor(() => { - fireEvent.click(actionsButton); - }); + fireEvent.click(actionsButton); const mockResponseData = { ok: true, blob: () => 'Data' }; const mockFetchResponse = Promise.resolve(mockResponseData); const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a'); @@ -396,19 +365,18 @@ describe('FilesAndUploads', () => { const sortsButton = screen.getByText(messages.sortButtonLabel.defaultMessage); expect(sortsButton).toBeVisible(); + fireEvent.click(sortsButton); await waitFor(() => { - fireEvent.click(sortsButton); expect(screen.getByText(messages.sortModalTitleLabel.defaultMessage)).toBeVisible(); }); const sortNameAscendingButton = screen.getByText(messages.sortByNameAscending.defaultMessage); fireEvent.click(sortNameAscendingButton); + fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); await waitFor(() => { - fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); + expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull(); }); - - expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull(); }); it('sort button should be enabled and sort files by file size', async () => { @@ -416,30 +384,26 @@ describe('FilesAndUploads', () => { const sortsButton = screen.getByText(messages.sortButtonLabel.defaultMessage); expect(sortsButton).toBeVisible(); + fireEvent.click(sortsButton); await waitFor(() => { - fireEvent.click(sortsButton); expect(screen.getByText(messages.sortModalTitleLabel.defaultMessage)).toBeVisible(); }); const sortBySizeDescendingButton = screen.getByText(messages.sortBySizeDescending.defaultMessage); fireEvent.click(sortBySizeDescendingButton); + fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); await waitFor(() => { - fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); + expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull(); }); - - expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull(); }); }); describe('card menu actions', () => { it('should open asset info', async () => { await mockStore(RequestStatus.SUCCESSFUL); - let assetMenuButton; - await waitFor(() => { - [assetMenuButton] = screen.getAllByTestId('file-menu-dropdown-mOckID1'); - }); + const [assetMenuButton] = await screen.getAllByTestId('file-menu-dropdown-mOckID1'); axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID1/usage`) .reply(201, { @@ -450,14 +414,15 @@ describe('FilesAndUploads', () => { }], }, }); + fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Info')); + + await executeThunk(getUsagePaths({ + courseId, + asset: { id: 'mOckID1', displayName: 'mOckID1' }, + setSelectedRows: jest.fn(), + }), store.dispatch); await waitFor(() => { - fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Info')); - executeThunk(getUsagePaths({ - courseId, - asset: { id: 'mOckID1', displayName: 'mOckID1' }, - setSelectedRows: jest.fn(), - }), store.dispatch); expect(screen.getAllByLabelText('mOckID1')[0]).toBeVisible(); }); @@ -472,23 +437,23 @@ describe('FilesAndUploads', () => { axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false }); axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID1/usage`).reply(201, { usage_locations: { mOckID1: [] } }); + fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Info')); + await executeThunk(getUsagePaths({ + courseId, + asset: { id: 'mOckID1', displayName: 'mOckID1' }, + setSelectedRows: jest.fn(), + }), store.dispatch); await waitFor(() => { - fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Info')); - executeThunk(getUsagePaths({ - courseId, - asset: { id: 'mOckID1', displayName: 'mOckID1' }, - setSelectedRows: jest.fn(), - }), store.dispatch); expect(screen.getAllByLabelText('mOckID1')[0]).toBeVisible(); - - fireEvent.click(screen.getByLabelText('Checkbox')); - executeThunk(updateAssetLock({ - courseId, - assetId: 'mOckID1', - locked: false, - }), store.dispatch); }); + + fireEvent.click(screen.getByLabelText('Checkbox')); + await executeThunk(updateAssetLock({ + courseId, + assetId: 'mOckID1', + locked: false, + }), store.dispatch); expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible(); const updateStatus = store.getState().assets.updatingStatus; @@ -500,18 +465,18 @@ describe('FilesAndUploads', () => { const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0]; + axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false }); + fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Unlock')); + await executeThunk(updateAssetLock({ + courseId, + assetId: 'mOckID1', + locked: false, + }), store.dispatch); await waitFor(() => { - axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false }); - fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Unlock')); - executeThunk(updateAssetLock({ - courseId, - assetId: 'mOckID1', - locked: false, - }), store.dispatch); + const updateStatus = store.getState().assets.updatingStatus; + expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); - const updateStatus = store.getState().assets.updatingStatus; - expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); it('should lock asset', async () => { @@ -519,18 +484,18 @@ describe('FilesAndUploads', () => { const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0]; + axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(201, { locked: true }); + fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Lock')); + await executeThunk(updateAssetLock({ + courseId, + assetId: 'mOckID3', + locked: true, + }), store.dispatch); await waitFor(() => { - axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(201, { locked: true }); - fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Lock')); - executeThunk(updateAssetLock({ - courseId, - assetId: 'mOckID3', - locked: true, - }), store.dispatch); + const updateStatus = store.getState().assets.updatingStatus; + expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); - const updateStatus = store.getState().assets.updatingStatus; - expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); it('download button should download file', async () => { @@ -538,10 +503,8 @@ describe('FilesAndUploads', () => { const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0]; - await waitFor(() => { - fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Download')); - }); + fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Download')); expect(saveAs).toHaveBeenCalled(); }); @@ -550,17 +513,18 @@ describe('FilesAndUploads', () => { const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0]; + axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID1`).reply(204); + fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByTestId('open-delete-confirmation-button')); await waitFor(() => { - axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID1`).reply(204); - fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByTestId('open-delete-confirmation-button')); expect(screen.getByText('Delete mOckID1')).toBeVisible(); + }); - fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage)); + fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage)); + await waitFor(() => { expect(screen.queryByText('Delete mOckID1')).toBeNull(); - - executeThunk(deleteAssetFile(courseId, 'mOckID1', 5), store.dispatch); }); + await executeThunk(deleteAssetFile(courseId, 'mOckID1', 5), store.dispatch); const deleteStatus = store.getState().assets.deletingStatus; expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL); @@ -572,9 +536,7 @@ describe('FilesAndUploads', () => { it('404 intitial fetch should show error', async () => { await mockStore(RequestStatus.FAILED); const { loadingStatus } = store.getState().assets; - await waitFor(() => { - expect(screen.getByText('Error')).toBeVisible(); - }); + expect(screen.getByText('Error')).toBeVisible(); expect(loadingStatus).toEqual(RequestStatus.FAILED); expect(screen.getByText('Failed to load all files.')).toBeVisible(); @@ -583,9 +545,7 @@ describe('FilesAndUploads', () => { it('404 intitial fetch should show error', async () => { await mockStore(RequestStatus.SUCCESSFUL, true); const { loadingStatus } = store.getState().assets; - await waitFor(() => { - expect(screen.getByText('Error')).toBeVisible(); - }); + expect(screen.getByText('Error')).toBeVisible(); expect(loadingStatus).toEqual(RequestStatus.PARTIAL_FAILURE); expect(screen.getByText('Failed to load remaining files.')).toBeVisible(); @@ -597,12 +557,11 @@ describe('FilesAndUploads', () => { axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] }); axiosMock.onPost(getAssetsUrl(courseId)).reply(413, { error: errorMessage }); const addFilesButton = screen.getByLabelText('file-input'); - await act(async () => { - userEvent.upload(addFilesButton, file); + userEvent.upload(addFilesButton, file); + await waitFor(() => { + const addStatus = store.getState().assets.addingStatus; + expect(addStatus).toEqual(RequestStatus.FAILED); }); - const addStatus = store.getState().assets.addingStatus; - expect(addStatus).toEqual(RequestStatus.FAILED); - expect(screen.getByText('Error')).toBeVisible(); }); @@ -610,10 +569,8 @@ describe('FilesAndUploads', () => { await mockStore(RequestStatus.SUCCESSFUL); axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(404); const addFilesButton = screen.getByLabelText('file-input'); - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(addAssetFile(courseId, file, 1), store.dispatch); - }); + userEvent.upload(addFilesButton, file); + await executeThunk(addAssetFile(courseId, file, 1), store.dispatch); const addStatus = store.getState().assets.addingStatus; expect(addStatus).toEqual(RequestStatus.FAILED); @@ -625,10 +582,8 @@ describe('FilesAndUploads', () => { axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] }); axiosMock.onPost(getAssetsUrl(courseId)).reply(404); const addFilesButton = screen.getByLabelText('file-input'); - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(addAssetFile(courseId, file, 1), store.dispatch); - }); + userEvent.upload(addFilesButton, file); + await executeThunk(addAssetFile(courseId, file, 1), store.dispatch); const addStatus = store.getState().assets.addingStatus; expect(addStatus).toEqual(RequestStatus.FAILED); @@ -640,17 +595,18 @@ describe('FilesAndUploads', () => { const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0]; + axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID3`).reply(404); + fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByTestId('open-delete-confirmation-button')); await waitFor(() => { - axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID3`).reply(404); - fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByTestId('open-delete-confirmation-button')); expect(screen.getByText('Delete mOckID3')).toBeVisible(); + }); - fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage)); + fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage)); + await waitFor(() => { expect(screen.queryByText('Delete mOckID3')).toBeNull(); - - executeThunk(deleteAssetFile(courseId, 'mOckID3', 5), store.dispatch); }); + await executeThunk(deleteAssetFile(courseId, 'mOckID3', 5), store.dispatch); const deleteStatus = store.getState().assets.deletingStatus; expect(deleteStatus).toEqual(RequestStatus.FAILED); @@ -665,17 +621,17 @@ describe('FilesAndUploads', () => { const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0]; axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID3/usage`).reply(404); + fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Info')); + await executeThunk(getUsagePaths({ + courseId, + asset: { id: 'mOckID3', displayName: 'mOckID3' }, + setSelectedRows: jest.fn(), + }), store.dispatch); await waitFor(() => { - fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Info')); - executeThunk(getUsagePaths({ - courseId, - asset: { id: 'mOckID3', displayName: 'mOckID3' }, - setSelectedRows: jest.fn(), - }), store.dispatch); + const { usageStatus } = store.getState().assets; + expect(usageStatus).toEqual(RequestStatus.FAILED); }); - const { usageStatus } = store.getState().assets; - expect(usageStatus).toEqual(RequestStatus.FAILED); }); it('404 lock update should show error', async () => { @@ -683,19 +639,18 @@ describe('FilesAndUploads', () => { const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0]; + axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(404); + fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Lock')); + await executeThunk(updateAssetLock({ + courseId, + assetId: 'mOckID3', + locked: true, + }), store.dispatch); await waitFor(() => { - axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(404); - fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Lock')); - executeThunk(updateAssetLock({ - courseId, - assetId: 'mOckID3', - locked: true, - }), store.dispatch); + const updateStatus = store.getState().assets.updatingStatus; + expect(updateStatus).toEqual(RequestStatus.FAILED); }); - const updateStatus = store.getState().assets.updatingStatus; - expect(updateStatus).toEqual(RequestStatus.FAILED); - expect(screen.getByText('Error')).toBeVisible(); }); @@ -707,17 +662,15 @@ describe('FilesAndUploads', () => { const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); expect(actionsButton).toBeVisible(); - await waitFor(() => { - fireEvent.click(actionsButton); - }); + fireEvent.click(actionsButton); const mockResponseData = { ok: false }; const mockFetchResponse = Promise.resolve(mockResponseData); const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a'); expect(downloadButton).not.toHaveClass('disabled'); global.fetch = jest.fn().mockImplementation(() => mockFetchResponse); + fireEvent.click(downloadButton); await waitFor(() => { - fireEvent.click(downloadButton); expect(fetch).toHaveBeenCalledTimes(2); }); diff --git a/src/files-and-videos/files-page/messages.js b/src/files-and-videos/files-page/messages.js index 2f827e5c01..d2c3bf3d78 100644 --- a/src/files-and-videos/files-page/messages.js +++ b/src/files-and-videos/files-page/messages.js @@ -47,12 +47,12 @@ const messages = defineMessages({ description: 'Label for lock file checkbox in info modal', }, activeCheckboxLabel: { - id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.activeCheckbox.label', + id: 'course-authoring.files-and-videos.file-info.activeCheckbox.label', defaultMessage: 'Active', description: 'Label for active checkbox in filter section of sort and filter modal', }, inactiveCheckboxLabel: { - id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.inactiveCheckbox.label', + id: 'course-authoring.files-and-videos.file-info.inactiveCheckbox.label', defaultMessage: 'Inactive', description: 'Label for inactive checkbox in filter section of sort and filter modal', }, diff --git a/src/files-and-videos/generic/messages.js b/src/files-and-videos/generic/messages.js index 1906f1ba78..6bb73e6fd9 100644 --- a/src/files-and-videos/generic/messages.js +++ b/src/files-and-videos/generic/messages.js @@ -126,12 +126,12 @@ const messages = defineMessages({ description: 'Label for delete button in card menu dropdown', }, deleteConfirmationTitle: { - id: 'course-authoring.files-and-uploads..deleteConfirmation.title', + id: 'course-authoring.files-and-uploads.deleteConfirmation.title', defaultMessage: 'Delete {fileNumber, plural, one {{fileName}} other {{fileNumber} {fileType}s}}', description: 'Title for delete confirmation modal', }, deleteConfirmationMessage: { - id: 'course-authoring.files-and-uploads..deleteConfirmation.message', + id: 'course-authoring.files-and-uploads.deleteConfirmation.message', defaultMessage: ` Are you sure you want to delete {fileNumber, plural, one {{fileName}} other {{fileNumber} {fileType}s}}? This action cannot be undone and may break your course if the {fileNumber, plural, one {{fileType} is} other {{fileType}s are}} @@ -140,7 +140,7 @@ const messages = defineMessages({ description: 'Message presented to user listing the number of files they are attempting to delete in the delete confirmation modal', }, deleteConfirmationUsageMessage: { - id: 'course-authoring.files-and-uploads..deleteConfirmation.message', + id: 'course-authoring.files-and-uploads.deleteConfirmation.usage-message', defaultMessage: 'The following {fileNumber, plural, one {{fileType} is} other {{fileType}s are}} used in course content. Consider updating the content before deleting.', description: 'Message listing where the files the user is attempting to delete are used in the course', }, diff --git a/src/files-and-videos/generic/table-components/GalleryCard.jsx b/src/files-and-videos/generic/table-components/GalleryCard.jsx index 983a533f6d..fa21a182cf 100644 --- a/src/files-and-videos/generic/table-components/GalleryCard.jsx +++ b/src/files-and-videos/generic/table-components/GalleryCard.jsx @@ -104,7 +104,7 @@ GalleryCard.propTypes = { handleOpenDeleteConfirmation: PropTypes.func.isRequired, handleOpenFileInfo: PropTypes.func.isRequired, thumbnailPreview: PropTypes.func.isRequired, - fileType: PropTypes.func.isRequired, + fileType: PropTypes.string.isRequired, }; export default GalleryCard; diff --git a/src/files-and-videos/generic/table-components/table-custom-columns/ActiveColumn.jsx b/src/files-and-videos/generic/table-components/table-custom-columns/ActiveColumn.jsx index f0f143a1fb..36b153c75c 100644 --- a/src/files-and-videos/generic/table-components/table-custom-columns/ActiveColumn.jsx +++ b/src/files-and-videos/generic/table-components/table-custom-columns/ActiveColumn.jsx @@ -1,29 +1,16 @@ import React from 'react'; import { PropTypes } from 'prop-types'; import { isNil } from 'lodash'; -import { injectIntl, FormattedMessage } from '@edx/frontend-platform/i18n'; -import { Icon, Spinner } from '@openedx/paragon'; +import { injectIntl } from '@edx/frontend-platform/i18n'; +import { Icon } from '@openedx/paragon'; import { Check } from '@openedx/paragon/icons'; import { RequestStatus } from '../../../../data/constants'; +import { LoadingSpinner } from '../../../../generic/Loading'; const ActiveColumn = ({ row, pageLoadStatus }) => { const { usageLocations } = row.original; if (isNil(usageLocations) || pageLoadStatus !== RequestStatus.SUCCESSFUL) { - return ( - - )} - /> - ); + return ; } const numOfUsageLocations = usageLocations.length; return numOfUsageLocations > 0 ? : null; diff --git a/src/files-and-videos/videos-page/VideosPage.test.jsx b/src/files-and-videos/videos-page/VideosPage.test.jsx index cc1b1f52af..4fc7923c89 100644 --- a/src/files-and-videos/videos-page/VideosPage.test.jsx +++ b/src/files-and-videos/videos-page/VideosPage.test.jsx @@ -70,6 +70,15 @@ const mockStore = async ( renderComponent(); await executeThunk(fetchVideos(courseId), store.dispatch); + + // Finish loading the expected files into the data table before returning, + // because loading new files can disrupt things like accessing file menus. + if (status === RequestStatus.SUCCESSFUL) { + const numFiles = 3; + await waitFor(() => { + expect(screen.getByText(`Showing ${numFiles} of ${numFiles}`)).toBeInTheDocument(); + }); + } }; const emptyMockStore = async (status) => { @@ -127,24 +136,24 @@ describe('Videos page', () => { it('should upload a single file', async () => { await emptyMockStore(RequestStatus.SUCCESSFUL); const dropzone = screen.getByTestId('files-dropzone'); - await act(async () => { - const mockResponseData = { status: '200', ok: true, blob: () => 'Data' }; - const mockFetchResponse = Promise.resolve(mockResponseData); - global.fetch = jest.fn().mockImplementation(() => mockFetchResponse); + const mockResponseData = { status: '200', ok: true, blob: () => 'Data' }; + const mockFetchResponse = Promise.resolve(mockResponseData); + global.fetch = jest.fn().mockImplementation(() => mockFetchResponse); + + axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(204, generateNewVideoApiResponse()); + axiosMock.onGet(getCourseVideosApiUrl(courseId)).reply(200, generateAddVideoApiResponse()); + Object.defineProperty(dropzone, 'files', { + value: [file], + }); + fireEvent.drop(dropzone); + await executeThunk(addVideoFile(courseId, file, [], { current: [] }), store.dispatch); - axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(204, generateNewVideoApiResponse()); - axiosMock.onGet(getCourseVideosApiUrl(courseId)).reply(200, generateAddVideoApiResponse()); - Object.defineProperty(dropzone, 'files', { - value: [file], - }); - fireEvent.drop(dropzone); - await executeThunk(addVideoFile(courseId, file, [], { current: [] }), store.dispatch); + await waitFor(() => { + const addStatus = store.getState().videos.addingStatus; + expect(addStatus).toEqual(RequestStatus.SUCCESSFUL); }); - const addStatus = store.getState().videos.addingStatus; - expect(addStatus).toEqual(RequestStatus.SUCCESSFUL); expect(screen.queryByTestId('files-dropzone')).toBeNull(); - expect(screen.getByTestId('files-data-table')).toBeVisible(); }); }); @@ -170,9 +179,7 @@ describe('Videos page', () => { const transcriptSettingsButton = screen.getByText(videoMessages.transcriptSettingsButtonLabel.defaultMessage); expect(transcriptSettingsButton).toBeVisible(); - await act(async () => { - fireEvent.click(transcriptSettingsButton); - }); + fireEvent.click(transcriptSettingsButton); expect(screen.getByLabelText('close settings')).toBeVisible(); }); @@ -200,9 +207,7 @@ describe('Videos page', () => { expect(screen.queryByRole('table')).toBeNull(); const listButton = screen.getByLabelText('List'); - await act(async () => { - fireEvent.click(listButton); - }); + fireEvent.click(listButton); expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull(); expect(screen.getByRole('table')).toBeVisible(); @@ -213,10 +218,8 @@ describe('Videos page', () => { axiosMock.onPost(`${getApiBaseUrl()}/video_images/${courseId}/mOckID1`).reply(200, { image_url: 'url' }); const addThumbnailButton = screen.getByTestId('video-thumbnail-mOckID1'); const thumbnail = new File(['test'], 'sOMEUrl.jpg', { type: 'image/jpg' }); - await act(async () => { - fireEvent.click(addThumbnailButton); - await executeThunk(addVideoThumbnail({ file: thumbnail, videoId: 'mOckID1', courseId }), store.dispatch); - }); + fireEvent.click(addThumbnailButton); + await executeThunk(addVideoThumbnail({ file: thumbnail, videoId: 'mOckID1', courseId }), store.dispatch); const updateStatus = store.getState().videos.updatingStatus; expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); @@ -247,10 +250,8 @@ describe('Videos page', () => { const addFilesButton = screen.getAllByLabelText('file-input')[3]; const { videoIds } = store.getState().videos; - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(addVideoFile(courseId, file, videoIds, { current: [] }), store.dispatch); - }); + userEvent.upload(addFilesButton, file); + await executeThunk(addVideoFile(courseId, file, videoIds, { current: [] }), store.dispatch); const addStatus = store.getState().videos.addingStatus; expect(addStatus).toEqual(RequestStatus.SUCCESSFUL); }); @@ -270,9 +271,7 @@ describe('Videos page', () => { uploadSpy.mockResolvedValue(new Promise(() => {})); const addFilesButton = screen.getAllByLabelText('file-input')[3]; - act(async () => { - userEvent.upload(addFilesButton, file); - }); + userEvent.upload(addFilesButton, file); await waitFor(() => { const addStatus = store.getState().videos.addingStatus; expect(addStatus).toEqual(RequestStatus.IN_PROGRESS); @@ -293,23 +292,24 @@ describe('Videos page', () => { await mockStore(RequestStatus.SUCCESSFUL); const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); + fireEvent.click(actionsButton); await waitFor(() => { - fireEvent.click(actionsButton); - }); - expect(screen.getByText(messages.downloadTitle.defaultMessage).closest('a')).toHaveClass('disabled'); + expect(screen.getByText(messages.downloadTitle.defaultMessage).closest('a')).toHaveClass('disabled'); - expect(screen.getByText(messages.deleteTitle.defaultMessage).closest('a')).toHaveClass('disabled'); + expect(screen.getByText(messages.deleteTitle.defaultMessage).closest('a')).toHaveClass('disabled'); + }); }); it('delete button should be enabled and delete selected file', async () => { await mockStore(RequestStatus.SUCCESSFUL); - const selectCardButton = screen.getAllByTestId('datatable-select-column-checkbox-cell')[0]; + + const [selectCardButton] = await screen.findAllByTestId('datatable-select-column-checkbox-cell'); fireEvent.click(selectCardButton); + const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); + expect(actionsButton).toBeVisible(); + fireEvent.click(actionsButton); - await waitFor(() => { - fireEvent.click(actionsButton); - }); const deleteButton = screen.getByText(messages.deleteTitle.defaultMessage).closest('a'); expect(deleteButton).not.toHaveClass('disabled'); @@ -317,45 +317,22 @@ describe('Videos page', () => { fireEvent.click(deleteButton); expect(screen.getByText('Delete mOckID1.mp4')).toBeVisible(); - await act(async () => { - userEvent.click(deleteButton); - }); + fireEvent.click(deleteButton); // Wait for the delete confirmation button to appear const confirmDeleteButton = await screen.findByRole('button', { name: messages.deleteFileButtonLabel.defaultMessage, }); - - await act(async () => { - userEvent.click(confirmDeleteButton); - }); + fireEvent.click(confirmDeleteButton); expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull(); // Check if the video is deleted in the store and UI - const deleteStatus = store.getState().videos.deletingStatus; - expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL); - expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull(); - }); - - it('download button should be enabled and download single selected file', async () => { - await mockStore(RequestStatus.SUCCESSFUL); - const selectCardButton = screen.getAllByTestId('datatable-select-column-checkbox-cell')[0]; - fireEvent.click(selectCardButton); - const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); - await waitFor(() => { - fireEvent.click(actionsButton); - }); - const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a'); - expect(downloadButton).not.toHaveClass('disabled'); - - await act(async () => { - fireEvent.click(downloadButton); + const deleteStatus = store.getState().videos.deletingStatus; + expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL); }); - - const updateStatus = store.getState().videos.updatingStatus; - expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); + expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull(); }); it('download button should be enabled and download multiple selected files', async () => { @@ -365,20 +342,18 @@ describe('Videos page', () => { fireEvent.click(selectCardButtons[1]); const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); - await waitFor(() => { - fireEvent.click(actionsButton); - }); + fireEvent.click(actionsButton); axiosMock.onPut(`${getVideosUrl(courseId)}/download`).reply(200, null); const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a'); expect(downloadButton).not.toHaveClass('disabled'); - await act(async () => { - fireEvent.click(downloadButton); - }); + fireEvent.click(downloadButton); - const updateStatus = store.getState().videos.updatingStatus; - expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); + await waitFor(() => { + const updateStatus = store.getState().videos.updatingStatus; + expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); + }); }); describe('Sort and filter button', () => { @@ -386,9 +361,7 @@ describe('Videos page', () => { await mockStore(RequestStatus.SUCCESSFUL); const sortAndFilterButton = screen.getByText(messages.sortButtonLabel.defaultMessage); - await waitFor(() => { - fireEvent.click(sortAndFilterButton); - }); + fireEvent.click(sortAndFilterButton); }); describe('sort function', () => { @@ -396,22 +369,22 @@ describe('Videos page', () => { const sortNameAscendingButton = screen.getByText(messages.sortByNameAscending.defaultMessage); fireEvent.click(sortNameAscendingButton); + fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); + await waitFor(() => { - fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); + expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull(); }); - - expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull(); }); it('sort button should be enabled and sort files by file size', async () => { const sortBySizeDescendingButton = screen.getByText(messages.sortBySizeDescending.defaultMessage); fireEvent.click(sortBySizeDescendingButton); + fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); + await waitFor(() => { - fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); + expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull(); }); - - expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull(); }); }); @@ -425,13 +398,12 @@ describe('Videos page', () => { fireEvent.click(notTranscribedCheckboxFilter); fireEvent.click(transcribedCheckboxFilter); + fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); + await waitFor(() => { - fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); + const galleryCards = screen.getAllByTestId('grid-card', { exact: false }); + expect(galleryCards).toHaveLength(1); }); - - const galleryCards = screen.getAllByTestId('grid-card', { exact: false }); - - expect(galleryCards).toHaveLength(1); }); it('should clearAll selections', async () => { @@ -445,7 +417,7 @@ describe('Videos page', () => { fireEvent.click(transcribedCheckboxFilter); const clearAllButton = screen.getByText('Clear all'); - await waitFor(() => fireEvent.click(clearAllButton)); + fireEvent.click(clearAllButton); expect(transcribedCheckboxFilter).toHaveProperty('checked', false); @@ -460,11 +432,9 @@ describe('Videos page', () => { const transcribedCheckboxFilter = screen.getByText(videoMessages.transcribedCheckboxLabel.defaultMessage); fireEvent.click(transcribedCheckboxFilter); - await waitFor(() => { - fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); - }); + fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage)); - const imageFilterChip = screen.getByRole('button', { name: 'Remove this filter' }); + const imageFilterChip = await screen.findByRole('button', { name: 'Remove this filter' }); fireEvent.click(imageFilterChip); expect(screen.queryByText(videoMessages.transcribedCheckboxLabel.defaultMessage)).toBeNull(); @@ -488,13 +458,13 @@ describe('Videos page', () => { }], }); + fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Info')); + await waitFor(() => { - fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Info')); + expect(screen.getByText(messages.infoTitle.defaultMessage)).toBeVisible(); }); - expect(screen.getByText(messages.infoTitle.defaultMessage)).toBeVisible(); - const { usageStatus } = store.getState().videos; expect(usageStatus).toEqual(RequestStatus.SUCCESSFUL); @@ -507,13 +477,12 @@ describe('Videos page', () => { const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1'); axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID1/usage`).reply(201, { usageLocations: [] }); + fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Info')); await waitFor(() => { - fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Info')); + expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible(); }); - expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible(); - const infoTab = screen.getAllByRole('tab')[0]; expect(infoTab).toBeVisible(); @@ -525,17 +494,14 @@ describe('Videos page', () => { const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1'); axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID1/usage`).reply(201, { usageLocations: [] }); + fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Info')); await waitFor(() => { - fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Info')); + expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible(); }); - expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible(); - const transcriptTab = screen.getAllByRole('tab')[1]; - await act(async () => { - fireEvent.click(transcriptTab); - }); + fireEvent.click(transcriptTab); expect(transcriptTab).toBeVisible(); expect(transcriptTab).toHaveClass('active'); @@ -546,15 +512,11 @@ describe('Videos page', () => { const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3'); axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID3/usage`).reply(201, { usageLocations: [] }); - await waitFor(() => { - fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Info')); - }); + fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Info')); - const transcriptTab = screen.getAllByRole('tab')[1]; - await act(async () => { - fireEvent.click(transcriptTab); - }); + const transcriptTab = await screen.getAllByRole('tab')[1]; + fireEvent.click(transcriptTab); expect(screen.getByText('Transcript (1)')).toBeVisible(); }); @@ -565,14 +527,13 @@ describe('Videos page', () => { const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1'); + fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Download')); + await waitFor(() => { - fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Download')); + const updateStatus = store.getState().videos.updatingStatus; + expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); - - const updateStatus = store.getState().videos.updatingStatus; - - expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); it('delete button should delete file', async () => { @@ -580,17 +541,18 @@ describe('Videos page', () => { const fileMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1'); + axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(204); + fireEvent.click(within(fileMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByTestId('open-delete-confirmation-button')); await waitFor(() => { - axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(204); - fireEvent.click(within(fileMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByTestId('open-delete-confirmation-button')); expect(screen.getByText('Delete mOckID1.mp4')).toBeVisible(); + }); - fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage)); + fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage)); + await waitFor(() => { expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull(); - - executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch); }); + await executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch); const deleteStatus = store.getState().videos.deletingStatus; expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL); @@ -614,10 +576,8 @@ describe('Videos page', () => { axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(413, { error: errorMessage }); const addFilesButton = screen.getAllByLabelText('file-input')[3]; - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch); - }); + userEvent.upload(addFilesButton, file); + await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch); const addStatus = store.getState().videos.addingStatus; expect(addStatus).toEqual(RequestStatus.FAILED); @@ -629,10 +589,8 @@ describe('Videos page', () => { axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(404); const addFilesButton = screen.getAllByLabelText('file-input')[3]; - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch); - }); + userEvent.upload(addFilesButton, file); + await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch); const addStatus = store.getState().videos.addingStatus; expect(addStatus).toEqual(RequestStatus.FAILED); @@ -645,10 +603,8 @@ describe('Videos page', () => { const addThumbnailButton = screen.getByTestId('video-thumbnail-mOckID1'); const thumbnail = new File(['test'], 'sOMEUrl.jpg', { type: 'image/jpg' }); - await act(async () => { - fireEvent.click(addThumbnailButton); - await executeThunk(addVideoThumbnail({ file: thumbnail, videoId: 'mOckID1', courseId }), store.dispatch); - }); + fireEvent.click(addThumbnailButton); + await executeThunk(addVideoThumbnail({ file: thumbnail, videoId: 'mOckID1', courseId }), store.dispatch); const updateStatus = store.getState().videos.updatingStatus; expect(updateStatus).toEqual(RequestStatus.FAILED); @@ -664,10 +620,8 @@ describe('Videos page', () => { axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(204, generateNewVideoApiResponse()); axiosMock.onGet(getCourseVideosApiUrl(courseId)).reply(200, generateAddVideoApiResponse()); const addFilesButton = screen.getAllByLabelText('file-input')[3]; - await act(async () => { - userEvent.upload(addFilesButton, file); - await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch); - }); + userEvent.upload(addFilesButton, file); + await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch); const addStatus = store.getState().videos.addingStatus; expect(addStatus).toEqual(RequestStatus.FAILED); @@ -679,16 +633,19 @@ describe('Videos page', () => { const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1'); + axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(404); + fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByTestId('open-delete-confirmation-button')); await waitFor(async () => { - axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(404); - fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByTestId('open-delete-confirmation-button')); expect(screen.getByText('Delete mOckID1.mp4')).toBeVisible(); + }); - fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage)); + fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage)); + await waitFor(async () => { expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull(); }); - executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch); + + await executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch); await waitFor(() => { const deleteStatus = store.getState().videos.deletingStatus; expect(deleteStatus).toEqual(RequestStatus.FAILED); @@ -705,14 +662,12 @@ describe('Videos page', () => { const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3'); axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID3/usage`).reply(404); - await waitFor(() => { - fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); - fireEvent.click(screen.getByText('Info')); - executeThunk(getUsagePaths({ - courseId, - video: { id: 'mOckID3', displayName: 'mOckID3' }, - }), store.dispatch); - }); + fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle')); + fireEvent.click(screen.getByText('Info')); + await executeThunk(getUsagePaths({ + courseId, + video: { id: 'mOckID3', displayName: 'mOckID3' }, + }), store.dispatch); await waitFor(() => { const { usageStatus } = store.getState().videos; expect(usageStatus).toEqual(RequestStatus.FAILED); @@ -728,18 +683,13 @@ describe('Videos page', () => { const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage); expect(actionsButton).toBeVisible(); - await waitFor(() => { - fireEvent.click(actionsButton); - }); - const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a'); + fireEvent.click(actionsButton); + const downloadButton = await screen.getByText(messages.downloadTitle.defaultMessage).closest('a'); expect(downloadButton).not.toHaveClass('disabled'); axiosMock.onPut(`${getVideosUrl(courseId)}/download`).reply(404); - - await waitFor(() => { - fireEvent.click(downloadButton); - executeThunk(fetchVideoDownload([{ original: { displayName: 'mOckID1', id: '2', downloadLink: 'test' } }]), store.dispatch); - }); + fireEvent.click(downloadButton); + await executeThunk(fetchVideoDownload([{ original: { displayName: 'mOckID1', id: '2', downloadLink: 'test' } }]), store.dispatch); const updateStatus = store.getState().videos.updatingStatus; expect(updateStatus).toEqual(RequestStatus.FAILED); diff --git a/src/files-and-videos/videos-page/transcript-settings/TranscriptSettings.test.jsx b/src/files-and-videos/videos-page/transcript-settings/TranscriptSettings.test.jsx index 65bb272044..55a97747a4 100644 --- a/src/files-and-videos/videos-page/transcript-settings/TranscriptSettings.test.jsx +++ b/src/files-and-videos/videos-page/transcript-settings/TranscriptSettings.test.jsx @@ -1,6 +1,6 @@ import { render, - act, + fireEvent, screen, waitFor, within, @@ -70,9 +70,7 @@ describe('TranscriptSettings', () => { it('should change view to order form', async () => { renderComponent(defaultProps); const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage); - await act(async () => { - userEvent.click(orderButton); - }); + userEvent.click(orderButton); const selectableButtons = screen.getAllByLabelText('none radio')[0]; expect(selectableButtons).toBeVisible(); @@ -81,17 +79,14 @@ describe('TranscriptSettings', () => { it('should return to order transcript collapsible', async () => { renderComponent(defaultProps); const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage); - await act(async () => { - userEvent.click(orderButton); - }); + userEvent.click(orderButton); const selectableButtons = screen.getAllByLabelText('none radio')[0]; expect(selectableButtons).toBeVisible(); const backButton = screen.getByLabelText('back button to main transcript settings view'); + userEvent.click(backButton); await waitFor(() => { - userEvent.click(backButton); - expect(screen.queryByLabelText('back button to main transcript settings view')).toBeNull(); }); }); @@ -99,13 +94,9 @@ describe('TranscriptSettings', () => { it('discard changes should call closeTranscriptSettings', async () => { renderComponent(defaultProps); const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage); - await act(async () => { - userEvent.click(orderButton); - }); + userEvent.click(orderButton); const discardButton = screen.getByText(messages.discardSettingsLabel.defaultMessage); - await act(async () => { - userEvent.click(discardButton); - }); + userEvent.click(discardButton); expect(defaultProps.closeTranscriptSettings).toHaveBeenCalled(); }); @@ -145,9 +136,7 @@ describe('TranscriptSettings', () => { it('should load page with Cielo24 selected', async () => { const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage); - await act(async () => { - userEvent.click(orderButton); - }); + userEvent.click(orderButton); const cielo24Button = screen.getByText(messages.cieloLabel.defaultMessage); expect(within(cielo24Button).getByLabelText('Cielo24 radio')).toHaveProperty('checked', true); @@ -185,38 +174,32 @@ describe('TranscriptSettings', () => { renderComponent(defaultProps); const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage); - await act(async () => { - userEvent.click(orderButton); - }); + userEvent.click(orderButton); const noneButton = screen.getAllByLabelText('none radio')[0]; - await act(async () => { - userEvent.click(noneButton); - }); + userEvent.click(noneButton); }); it('api should succeed', async () => { const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); axiosMock.onDelete(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(204); + fireEvent.click(updateButton); await waitFor(() => { - userEvent.click(updateButton); + const { transcriptStatus } = store.getState().videos; + expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); }); - const { transcriptStatus } = store.getState().videos; - - expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); }); it('should show error alert', async () => { const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); axiosMock.onDelete(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(404); + fireEvent.click(updateButton); await waitFor(() => { - userEvent.click(updateButton); + const { transcriptStatus } = store.getState().videos; + expect(transcriptStatus).toEqual(RequestStatus.FAILED); }); - const { transcriptStatus } = store.getState().videos; - - expect(transcriptStatus).toEqual(RequestStatus.FAILED); expect(screen.getByText('Failed to update order transcripts settings.')).toBeVisible(); }); @@ -237,24 +220,18 @@ describe('TranscriptSettings', () => { renderComponent(defaultProps); const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage); - await act(async () => { - userEvent.click(orderButton); - }); + userEvent.click(orderButton); }); it('should ask for Cielo24 or 3Play Media credentials', async () => { const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0]; - await act(async () => { - userEvent.click(cielo24Button); - }); + userEvent.click(cielo24Button); const cieloCredentialMessage = screen.getByTestId('cieloCredentialMessage'); expect(cieloCredentialMessage).toBeVisible(); const threePlayMediaButton = screen.getAllByLabelText('3PlayMedia radio')[0]; - await act(async () => { - userEvent.click(threePlayMediaButton); - }); + userEvent.click(threePlayMediaButton); const threePlayMediaCredentialMessage = screen.getByTestId('threePlayMediaCredentialMessage'); expect(threePlayMediaCredentialMessage).toBeVisible(); @@ -263,9 +240,7 @@ describe('TranscriptSettings', () => { describe('api succeeds', () => { it('should update cielo24 credentials ', async () => { const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0]; - await act(async () => { - userEvent.click(cielo24Button); - }); + userEvent.click(cielo24Button); const firstInput = screen.getByLabelText(messages.cieloApiKeyLabel.defaultMessage); const secondInput = screen.getByLabelText(messages.cieloUsernameLabel.defaultMessage); @@ -279,14 +254,12 @@ describe('TranscriptSettings', () => { }); axiosMock.onPost(`${getApiBaseUrl()}/transcript_credentials/${courseId}`).reply(200); + fireEvent.click(updateButton); await waitFor(() => { - userEvent.click(updateButton); + const { transcriptStatus } = store.getState().videos; + expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); }); - const { transcriptStatus } = store.getState().videos; - - expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); - expect(screen.queryByTestId('cieloCredentialMessage')).toBeNull(); expect(screen.getByText(messages.cieloFidelityLabel.defaultMessage)).toBeVisible(); @@ -294,9 +267,7 @@ describe('TranscriptSettings', () => { it('should update 3Play Media credentials', async () => { const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0]; - await act(async () => { - userEvent.click(threePlayButton); - }); + userEvent.click(threePlayButton); const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); const firstInput = screen.getByLabelText(messages.threePlayMediaApiKeyLabel.defaultMessage); @@ -310,12 +281,12 @@ describe('TranscriptSettings', () => { }); axiosMock.onPost(`${getApiBaseUrl()}/transcript_credentials/${courseId}`).reply(200); + fireEvent.click(updateButton); + await waitFor(() => { - userEvent.click(updateButton); + const { transcriptStatus } = store.getState().videos; + expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); }); - const { transcriptStatus } = store.getState().videos; - - expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); expect(screen.queryByTestId('threePlayCredentialMessage')).toBeNull(); @@ -326,9 +297,7 @@ describe('TranscriptSettings', () => { describe('api fails', () => { it('should show error alert on Cielo24 credentials update', async () => { const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0]; - await act(async () => { - userEvent.click(cielo24Button); - }); + userEvent.click(cielo24Button); const firstInput = screen.getByLabelText(messages.cieloApiKeyLabel.defaultMessage); const secondInput = screen.getByLabelText(messages.cieloUsernameLabel.defaultMessage); @@ -342,21 +311,19 @@ describe('TranscriptSettings', () => { }); axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(503); + fireEvent.click(updateButton); + await waitFor(() => { - userEvent.click(updateButton); + const { transcriptStatus } = store.getState().videos; + expect(transcriptStatus).toEqual(RequestStatus.FAILED); }); - const { transcriptStatus } = store.getState().videos; - - expect(transcriptStatus).toEqual(RequestStatus.FAILED); expect(screen.getByText('Failed to update Cielo24 credentials.')).toBeVisible(); }); it('should show error alert on 3PlayMedia credentials update', async () => { const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0]; - await act(async () => { - userEvent.click(threePlayButton); - }); + userEvent.click(threePlayButton); const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); const firstInput = screen.getByLabelText(messages.threePlayMediaApiKeyLabel.defaultMessage); @@ -370,12 +337,12 @@ describe('TranscriptSettings', () => { }); axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(404); + fireEvent.click(updateButton); + await waitFor(() => { - userEvent.click(updateButton); + const { transcriptStatus } = store.getState().videos; + expect(transcriptStatus).toEqual(RequestStatus.FAILED); }); - const { transcriptStatus } = store.getState().videos; - - expect(transcriptStatus).toEqual(RequestStatus.FAILED); expect(screen.getByText('Failed to update 3PlayMedia credentials.')).toBeVisible(); }); @@ -408,24 +375,18 @@ describe('TranscriptSettings', () => { axiosMock = new MockAdapter(getAuthenticatedHttpClient()); renderComponent(defaultProps); const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage); - await act(async () => { - userEvent.click(orderButton); - }); + userEvent.click(orderButton); }); it('should not show credentials request for Cielo24 and 3Play Media', async () => { const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0]; - await act(async () => { - userEvent.click(cielo24Button); - }); + userEvent.click(cielo24Button); const cieloCredentialMessage = screen.queryByTestId('cieloCredentialMessage'); expect(cieloCredentialMessage).toBeNull(); const threePlayMediaButton = screen.getAllByLabelText('3PlayMedia radio')[0]; - await act(async () => { - userEvent.click(threePlayMediaButton); - }); + userEvent.click(threePlayMediaButton); const threePlayMediaCredentialMessage = screen.queryByTestId('threePlayMediaCredentialMessage'); expect(threePlayMediaCredentialMessage).toBeNull(); @@ -443,9 +404,7 @@ describe('TranscriptSettings', () => { }; const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0]; - await act(async () => { - userEvent.click(cielo24Button); - }); + userEvent.click(cielo24Button); const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); const turnaround = screen.getByText(messages.cieloTurnaroundPlaceholder.defaultMessage); const fidelity = screen.getByText(messages.cieloFidelityPlaceholder.defaultMessage); @@ -469,12 +428,13 @@ describe('TranscriptSettings', () => { expect(updateButton).not.toHaveAttribute('disabled'); axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(200, apiResponse); + fireEvent.click(updateButton); + await waitFor(() => { - userEvent.click(updateButton); - }); - const { transcriptStatus } = store.getState().videos; + const { transcriptStatus } = store.getState().videos; - expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); + expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); + }); expect(screen.getByText(messages.cieloFidelityLabel.defaultMessage)).toBeVisible(); }); @@ -488,9 +448,7 @@ describe('TranscriptSettings', () => { global: false, }; const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0]; - await act(async () => { - userEvent.click(threePlayButton); - }); + userEvent.click(threePlayButton); const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); const turnaround = screen.getByText(messages.threePlayMediaTurnaroundPlaceholder.defaultMessage); const source = screen.getByText(messages.threePlayMediaSourceLanguagePlaceholder.defaultMessage); @@ -512,12 +470,12 @@ describe('TranscriptSettings', () => { }); axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(200, apiResponse); + fireEvent.click(updateButton); await waitFor(() => { - userEvent.click(updateButton); - }); - const { transcriptStatus } = store.getState().videos; + const { transcriptStatus } = store.getState().videos; - expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); + expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); + }); }); it('should update 3Play Media preferences with spanish as source language', async () => { @@ -529,9 +487,7 @@ describe('TranscriptSettings', () => { global: false, }; const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0]; - await act(async () => { - userEvent.click(threePlayButton); - }); + userEvent.click(threePlayButton); const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); const turnaround = screen.getByText(messages.threePlayMediaTurnaroundPlaceholder.defaultMessage); const source = screen.getByText(messages.threePlayMediaSourceLanguagePlaceholder.defaultMessage); @@ -550,21 +506,19 @@ describe('TranscriptSettings', () => { expect(updateButton).not.toHaveAttribute('disabled'); axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(200, apiResponse); + fireEvent.click(updateButton); await waitFor(() => { - userEvent.click(updateButton); - }); - const { transcriptStatus } = store.getState().videos; + const { transcriptStatus } = store.getState().videos; - expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); + expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL); + }); }); }); describe('api fails', () => { it('should show error alert on Cielo24 preferences update', async () => { const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0]; - await act(async () => { - userEvent.click(cielo24Button); - }); + userEvent.click(cielo24Button); const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); const turnaround = screen.getByText(messages.cieloTurnaroundPlaceholder.defaultMessage); const fidelity = screen.getByText(messages.cieloFidelityPlaceholder.defaultMessage); @@ -588,21 +542,19 @@ describe('TranscriptSettings', () => { expect(updateButton).not.toHaveAttribute('disabled'); axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(503); + fireEvent.click(updateButton); await waitFor(() => { - userEvent.click(updateButton); - }); - const { transcriptStatus } = store.getState().videos; + const { transcriptStatus } = store.getState().videos; - expect(transcriptStatus).toEqual(RequestStatus.FAILED); + expect(transcriptStatus).toEqual(RequestStatus.FAILED); + }); expect(screen.getByText('Failed to update Cielo24 transcripts settings.')).toBeVisible(); }); it('should show error alert with default message on 3PlayMedia preferences update', async () => { const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0]; - await act(async () => { - userEvent.click(threePlayButton); - }); + userEvent.click(threePlayButton); const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); const turnaround = screen.getByText(messages.threePlayMediaTurnaroundPlaceholder.defaultMessage); const source = screen.getByText(messages.threePlayMediaSourceLanguagePlaceholder.defaultMessage); @@ -621,21 +573,19 @@ describe('TranscriptSettings', () => { expect(updateButton).not.toHaveAttribute('disabled'); axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(404); + fireEvent.click(updateButton); await waitFor(() => { - userEvent.click(updateButton); - }); - const { transcriptStatus } = store.getState().videos; + const { transcriptStatus } = store.getState().videos; - expect(transcriptStatus).toEqual(RequestStatus.FAILED); + expect(transcriptStatus).toEqual(RequestStatus.FAILED); + }); expect(screen.getByText('Failed to update 3PlayMedia transcripts settings.')).toBeVisible(); }); it('should show error alert with default message on 3PlayMedia preferences update', async () => { const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0]; - await act(async () => { - userEvent.click(threePlayButton); - }); + userEvent.click(threePlayButton); const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage); const turnaround = screen.getByText(messages.threePlayMediaTurnaroundPlaceholder.defaultMessage); const source = screen.getByText(messages.threePlayMediaSourceLanguagePlaceholder.defaultMessage); @@ -654,12 +604,12 @@ describe('TranscriptSettings', () => { expect(updateButton).not.toHaveAttribute('disabled'); axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(404, { error: 'Invalid turnaround.' }); + fireEvent.click(updateButton); await waitFor(() => { - userEvent.click(updateButton); - }); - const { transcriptStatus } = store.getState().videos; + const { transcriptStatus } = store.getState().videos; - expect(transcriptStatus).toEqual(RequestStatus.FAILED); + expect(transcriptStatus).toEqual(RequestStatus.FAILED); + }); expect(screen.getByText('Invalid turnaround.')).toBeVisible(); }); diff --git a/src/generic/ConnectionErrorAlert.jsx b/src/generic/ConnectionErrorAlert.jsx index 3fd9989df4..44d088a66b 100644 --- a/src/generic/ConnectionErrorAlert.jsx +++ b/src/generic/ConnectionErrorAlert.jsx @@ -8,8 +8,7 @@ import messages from '../messages'; const ConnectionErrorAlert = ({ intl }) => ( diff --git a/src/generic/configure-modal/messages.js b/src/generic/configure-modal/messages.js index 5d785da574..41ef703bd8 100644 --- a/src/generic/configure-modal/messages.js +++ b/src/generic/configure-modal/messages.js @@ -196,7 +196,7 @@ const messages = defineMessages({ defaultMessage: 'Proctored', }, proctoredExamDescription: { - id: 'course-authoring.course-outline.configure-modal.advanced-tab.timed-description', + id: 'course-authoring.course-outline.configure-modal.advanced-tab.proctored-exam-description', defaultMessage: 'Proctored exams are timed and they record video of each learner taking the exam. The videos are then reviewed to ensure that learners follow all examination rules. Please note that setting this exam as proctored will change the visibility settings to "Hide content after due date."', }, onboardingExam: { @@ -204,7 +204,7 @@ const messages = defineMessages({ defaultMessage: 'Onboarding', }, onboardingExamDescription: { - id: 'course-authoring.course-outline.configure-modal.advanced-tab.timed-description', + id: 'course-authoring.course-outline.configure-modal.advanced-tab.onboarding-exam-description', defaultMessage: 'Use Onboarding to introduce learners to proctoring, verify their identity, and create an onboarding profile. Learners must complete the onboarding profile step prior to taking a proctored exam. Profile reviews take 2+ business days.', }, practiceExam: { @@ -212,7 +212,7 @@ const messages = defineMessages({ defaultMessage: 'Practice proctored', }, practiceExamDescription: { - id: 'course-authoring.course-outline.configure-modal.advanced-tab.timed-description', + id: 'course-authoring.course-outline.configure-modal.advanced-tab.practice-exam-description', defaultMessage: 'Use a practice proctored exam to introduce learners to the proctoring tools and processes. Results of a practice exam do not affect a learner\'s grade.', }, advancedTabTitle: { diff --git a/src/generic/create-or-rerun-course/CreateOrRerunCourseForm.test.jsx b/src/generic/create-or-rerun-course/CreateOrRerunCourseForm.test.jsx index b7a2127e3f..6b7ab0d7dd 100644 --- a/src/generic/create-or-rerun-course/CreateOrRerunCourseForm.test.jsx +++ b/src/generic/create-or-rerun-course/CreateOrRerunCourseForm.test.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { - act, fireEvent, screen, render, @@ -129,9 +128,7 @@ describe('', () => { render(); await mockStore(); const cancelBtn = screen.getByRole('button', { name: messages.cancelButton.defaultMessage }); - await act(async () => { - fireEvent.click(cancelBtn); - }); + fireEvent.click(cancelBtn); expect(onClickCancelMock).toHaveBeenCalled(); }); @@ -147,13 +144,11 @@ describe('', () => { const runInput = screen.getByPlaceholderText(messages.courseRunPlaceholder.defaultMessage); const createBtn = screen.getByRole('button', { name: messages.createButton.defaultMessage }); - await act(async () => { - userEvent.type(displayNameInput, 'foo course name'); - fireEvent.click(orgInput); - userEvent.type(numberInput, '777'); - userEvent.type(runInput, '1'); - userEvent.click(createBtn); - }); + userEvent.type(displayNameInput, 'foo course name'); + fireEvent.click(orgInput); + userEvent.type(numberInput, '777'); + userEvent.type(runInput, '1'); + userEvent.click(createBtn); await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { url }); await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch); @@ -171,13 +166,11 @@ describe('', () => { const createBtn = screen.getByRole('button', { name: messages.createButton.defaultMessage }); await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { url, destinationCourseKey }); - await act(async () => { - userEvent.type(displayNameInput, 'foo course name'); - fireEvent.click(orgInput); - userEvent.type(numberInput, '777'); - userEvent.type(runInput, '1'); - userEvent.click(createBtn); - }); + userEvent.type(displayNameInput, 'foo course name'); + fireEvent.click(orgInput); + userEvent.type(numberInput, '777'); + userEvent.type(runInput, '1'); + userEvent.click(createBtn); await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch); expect(mockedUsedNavigate).toHaveBeenCalledWith(`${url}${destinationCourseKey}`); @@ -208,12 +201,10 @@ describe('', () => { const numberInput = screen.getByPlaceholderText(messages.courseNumberPlaceholder.defaultMessage); const runInput = screen.getByPlaceholderText(messages.courseRunPlaceholder.defaultMessage); - await act(async () => { - fireEvent.change(displayNameInput, { target: { value: 'foo course name' } }); - fireEvent.click(orgInput); - fireEvent.change(numberInput, { target: { value: 'number with invalid (+) symbol' } }); - fireEvent.change(runInput, { target: { value: 'number with invalid (=) symbol' } }); - }); + fireEvent.change(displayNameInput, { target: { value: 'foo course name' } }); + fireEvent.click(orgInput); + fireEvent.change(numberInput, { target: { value: 'number with invalid (+) symbol' } }); + fireEvent.change(runInput, { target: { value: 'number with invalid (=) symbol' } }); waitFor(() => { expect(createBtn).toBeDisabled(); @@ -262,9 +253,7 @@ describe('', () => { await mockStore(); const numberInput = screen.getByPlaceholderText(messages.courseNumberPlaceholder.defaultMessage); - await act(async () => { - fireEvent.change(numberInput, { target: { value: 'number with invalid (+) symbol' } }); - }); + fireEvent.change(numberInput, { target: { value: 'number with invalid (+) symbol' } }); waitFor(() => { expect(screen.getByText(messages.noSpaceError)).toBeInTheDocument(); diff --git a/src/generic/modal-dropzone/ModalDropzone.test.jsx b/src/generic/modal-dropzone/ModalDropzone.test.jsx index 030c171518..71d1013cf7 100644 --- a/src/generic/modal-dropzone/ModalDropzone.test.jsx +++ b/src/generic/modal-dropzone/ModalDropzone.test.jsx @@ -116,7 +116,7 @@ describe('', () => { const dropzoneInput = getByRole('presentation', { hidden: true }).firstChild; const uploadButton = getByRole('button', { name: messages.uploadModal.defaultMessage }); - await userEvent.upload(dropzoneInput, file); + userEvent.upload(dropzoneInput, file); await waitFor(() => { expect(uploadButton).not.toBeDisabled(); diff --git a/src/header/Header.jsx b/src/header/Header.jsx index 6865a3db96..e5ba1a4b3c 100644 --- a/src/header/Header.jsx +++ b/src/header/Header.jsx @@ -41,7 +41,7 @@ const Header = ({ items: getToolsMenuItems({ studioBaseUrl, courseId: contentId, intl }), }, ] : []; - const outlineLink = !isLibrary ? `${studioBaseUrl}/course/${contentId}` : `${studioBaseUrl}/library/${contentId}`; + const outlineLink = !isLibrary ? `${studioBaseUrl}/course/${contentId}` : `/course-authoring/library/${contentId}`; return ( <> diff --git a/src/import-page/import-stepper/messages.js b/src/import-page/import-stepper/messages.js index 83d720600e..172b4a7338 100644 --- a/src/import-page/import-stepper/messages.js +++ b/src/import-page/import-stepper/messages.js @@ -50,7 +50,7 @@ const messages = defineMessages({ defaultMessage: 'Error importing course', }, stepperHeaderTitle: { - id: 'course-authoring.export.stepper.header.title', + id: 'course-authoring.import.stepper.header.title', defaultMessage: 'Course import status', }, }); diff --git a/src/library-authoring/LibraryAuthoringPage.jsx b/src/library-authoring/LibraryAuthoringPage.jsx index 782b8dd664..15e846b3b6 100644 --- a/src/library-authoring/LibraryAuthoringPage.jsx +++ b/src/library-authoring/LibraryAuthoringPage.jsx @@ -84,7 +84,7 @@ const LibraryAuthoringPage = () => { return ( <>
node); +const LocationDisplay = () => { + const location = useLocation(); + return
{location.pathname}
; +}; + function renderComponent(route) { const wrapper = render( @@ -52,6 +62,7 @@ function renderComponent(route) { element={} /> + , @@ -94,7 +105,7 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); expect(queryByTestId(container, 'appList')).toBeInTheDocument(); expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument(); @@ -104,7 +115,7 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); expect(queryByTestId(container, 'appList')).not.toBeInTheDocument(); expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument(); @@ -114,7 +125,7 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); await waitFor(() => { userEvent.click(queryByLabelText(container, 'Select Piazza')); @@ -134,7 +145,7 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); await waitFor(() => { userEvent.click(queryByLabelText(container, 'Select edX')); @@ -151,11 +162,11 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument(); - await act(() => userEvent.click(queryByText(container, appMessages.backButton.defaultMessage))); + await waitFor(() => userEvent.click(queryByText(container, appMessages.backButton.defaultMessage))); await waitFor(() => { expect(queryByTestId(container, 'appList')).toBeInTheDocument(); @@ -167,7 +178,7 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); expect(queryByTestId(container, 'appList')).toBeInTheDocument(); @@ -183,22 +194,27 @@ describe('DiscussionsSettings', () => { // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); - - act(async () => { - userEvent.click(queryByLabelText(container, 'Select Piazza')); + await waitForElementToBeRemoved(screen.queryByRole('status')); - userEvent.click(getByRole(container, 'button', { name: 'Next' })); + userEvent.click(screen.getByLabelText('Select Piazza')); - userEvent.click(await findByRole(container, 'button', { name: 'Save' })); + // Have to use fireEvent.click with these Stepper buttons so that the + // onClick handler is triggered. (userEvent.click doesn't trigger onClick). + await act(async () => { + fireEvent.click(getByRole(container, 'button', { name: 'Next' })); + }); + await act(async () => { + fireEvent.click(getByRole(container, 'button', { name: 'Save' })); + }); - // This is an important line that ensures the Close button has been removed, which implies that - // the full screen modal has been closed following our click of Apply. Once this has happened, - // then it's safe to proceed with our expectations. - await waitFor(() => expect(screen.queryByRole(container, 'button', { name: 'Close' })).toBeNull()); + // This is an important line that ensures the Close button has been removed, which implies that + // the full screen modal has been closed following our click of Apply. Once this has happened, + // then it's safe to proceed with our expectations. + await waitFor(() => expect(screen.queryByRole(container, 'button', { name: 'Close' })).toBeNull()); - await waitFor(() => expect(window.location.pathname).toEqual(`/course/${courseId}/pages-and-resources`)); - }); + // Confirm route is correct + const locationDisplay = await screen.findByTestId('location-display'); + await waitFor(() => expect(locationDisplay.textContent).toEqual(`/course/${courseId}/pages-and-resources`)); }); test('requires confirmation if changing provider', async () => { @@ -208,20 +224,18 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); - act(async () => { - userEvent.click(getByRole(container, 'checkbox', { name: 'Select Discourse' })); - userEvent.click(getByRole(container, 'button', { name: 'Next' })); + userEvent.click(getByRole(container, 'checkbox', { name: 'Select Discourse' })); + userEvent.click(getByRole(container, 'button', { name: 'Next' })); - await findByRole(container, 'button', { name: 'Save' }); - userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'key'); - userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret'); - userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test'); - userEvent.click(getByRole(container, 'button', { name: 'Save' })); + await findByRole(container, 'button', { name: 'Save' }); + userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'key'); + userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret'); + userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test'); + userEvent.click(getByRole(container, 'button', { name: 'Save' })); - await waitFor(() => expect(queryByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument()); - }); + await waitFor(() => expect(queryByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument()); }); test('can cancel confirmation', async () => { @@ -231,7 +245,7 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); const discourseBox = getByRole(container, 'checkbox', { name: 'Select Discourse' }); expect(discourseBox).not.toBeDisabled(); @@ -241,20 +255,18 @@ describe('DiscussionsSettings', () => { await waitFor(() => expect(screen.queryByRole('status')).toBeNull()); - act(async () => { - expect(await findByRole(container, 'heading', { name: 'Discourse' })).toBeInTheDocument(); + expect(await findByRole(container, 'heading', { name: 'Discourse' })).toBeInTheDocument(); - userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'a'); - userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret'); - userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test'); - userEvent.click(getByRole(container, 'button', { name: 'Save' })); + userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'a'); + userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret'); + userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test'); + userEvent.click(getByRole(container, 'button', { name: 'Save' })); - waitFor(() => expect(getByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument()); - userEvent.click(getByRole(container, 'button', { name: 'Cancel' })); + await waitFor(() => expect(getByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument()); + userEvent.click(getByRole(container, 'button', { name: 'Cancel' })); - expect(queryByRole(container, 'dialog', { name: 'Confirm' })).not.toBeInTheDocument(); - expect(queryByRole(container, 'dialog', { name: 'Configure discussion' })); - }); + expect(queryByRole(container, 'dialog', { name: 'Confirm' })).not.toBeInTheDocument(); + expect(queryByRole(container, 'dialog', { name: 'Configure discussion' })); }); }); @@ -274,7 +286,7 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); const alert = queryByRole(container, 'alert'); expect(alert).toBeInTheDocument(); @@ -302,19 +314,17 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); // Apply causes an async action to take place - act(async () => { - userEvent.click(queryByText(container, appMessages.saveButton.defaultMessage)); - await waitFor(() => expect(axiosMock.history.post.length).toBe(1)); - - expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument(); - const alert = await findByRole(container, 'alert'); - expect(alert).toBeInTheDocument(); - expect(alert.textContent).toEqual(expect.stringContaining('We encountered a technical error when applying changes.')); - expect(alert.innerHTML).toEqual(expect.stringContaining(getConfig().SUPPORT_URL)); - }); + userEvent.click(queryByText(container, appMessages.saveButton.defaultMessage)); + await waitFor(() => expect(axiosMock.history.post.length).toBe(1)); + + expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument(); + const alert = await findByRole(container, 'alert'); + expect(alert).toBeInTheDocument(); + expect(alert.textContent).toEqual(expect.stringContaining('We encountered a technical error when applying changes.')); + expect(alert.innerHTML).toEqual(expect.stringContaining(getConfig().SUPPORT_URL)); }); }); @@ -328,7 +338,7 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); const alert = queryByRole(container, 'alert'); expect(alert).toBeInTheDocument(); @@ -348,23 +358,23 @@ describe('DiscussionsSettings', () => { renderComponent(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - await waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); - act(async () => { - userEvent.click(getByRole(container, 'button', { name: 'Save' })); + userEvent.click(getByRole(container, 'button', { name: 'Save' })); - await waitFor(() => expect(axiosMock.history.post.length).toBe(1)); + await waitFor(() => expect(axiosMock.history.post.length).toBe(1)); - expect(queryByTestId(container, 'appList')).not.toBeInTheDocument(); - expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument(); + expect(queryByTestId(container, 'appList')).not.toBeInTheDocument(); + expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument(); - // We don't technically leave the route in this case, though the modal is hidden. - expect(window.location.pathname).toEqual(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`); + // Confirm route is correct + // We don't technically leave the route in this case, though the modal is hidden. + const locationDisplay = await screen.findByTestId('location-display'); + await waitFor(() => expect(locationDisplay.textContent).toEqual(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`)); - const alert = await findByRole(container, 'alert'); - expect(alert).toBeInTheDocument(); - expect(alert.textContent).toEqual(expect.stringContaining('You are not authorized to view this page.')); - }); + const alert = await findByRole(container, 'alert'); + expect(alert).toBeInTheDocument(); + expect(alert.textContent).toEqual(expect.stringContaining('You are not authorized to view this page.')); }); }); }); @@ -406,13 +416,13 @@ describe.each([ renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); - act(async () => { - userEvent.click(await screen.findByLabelText('Select Piazza')); - userEvent.click(queryByText(container, messages.nextButton.defaultMessage)); - waitForElementToBeRemoved(screen.getByRole('status')); + userEvent.click(screen.getByLabelText('Select Piazza')); + userEvent.click(queryByText(container, messages.nextButton.defaultMessage)); + expect(screen.queryByRole('status')).not.toBeInTheDocument(); + await waitFor(() => { if (showLTIConfig) { expect(queryByText(container, ltiMessages.formInstructions.defaultMessage)).toBeInTheDocument(); expect(queryByTestId(container, 'ltiConfigFields')).toBeInTheDocument(); @@ -460,18 +470,16 @@ describe.each([ renderComponent(`/course/${courseId}/pages-and-resources/discussion`); // This is an important line that ensures the spinner has been removed - and thus our main // content has been loaded - prior to proceeding with our expectations. - waitForElementToBeRemoved(screen.getByRole('status')); + await waitForElementToBeRemoved(screen.queryByRole('status')); - act(async () => { - userEvent.click(await screen.findByLabelText('Select Piazza')); - userEvent.click(await screen.findByText(messages.nextButton.defaultMessage)); + userEvent.click(screen.getByLabelText('Select Piazza')); + userEvent.click(screen.getByText(messages.nextButton.defaultMessage)); - waitForElementToBeRemoved(screen.getByRole('status')); - if (enablePIISharing) { - expect(queryByTestId(container, 'piiSharingFields')).toBeInTheDocument(); - } else { - expect(queryByTestId(container, 'piiSharingFields')).not.toBeInTheDocument(); - } - }); + expect(screen.queryByRole('status')).not.toBeInTheDocument(); + if (enablePIISharing) { + expect(queryByTestId(container, 'piiSharingFields')).toBeInTheDocument(); + } else { + expect(queryByTestId(container, 'piiSharingFields')).not.toBeInTheDocument(); + } }); }); diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/DiscussionRestriction.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/DiscussionRestriction.jsx index fce616a4aa..3fbee41c10 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/DiscussionRestriction.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/DiscussionRestriction.jsx @@ -40,7 +40,7 @@ const DiscussionRestriction = () => { const discussionRestrictionButtons = useMemo(() => discussionRestrictionOptions.map((restriction) => (