From 7f5e82a8441d2d1afb55c248cd144b6142734697 Mon Sep 17 00:00:00 2001 From: Raymond Zhou <56318341+rayzhou-bit@users.noreply.github.com> Date: Fri, 7 Jun 2024 10:40:59 -0700 Subject: [PATCH 01/10] fix: handle null displayname (#1074) --- .../add-component-modals/ComponentModalView.jsx | 2 +- src/studio-home/card-item/index.jsx | 2 +- src/studio-home/tabs-section/utils.js | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) 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/studio-home/card-item/index.jsx b/src/studio-home/card-item/index.jsx index f495794d80..2bc9c3e16f 100644 --- a/src/studio-home/card-item/index.jsx +++ b/src/studio-home/card-item/index.jsx @@ -42,7 +42,7 @@ const CardItem = ({ const isShowRerunLink = allowCourseReruns && rerunCreatorStatus && courseCreatorStatus === COURSE_CREATOR_STATES.granted; - const hasDisplayName = displayName.trim().length ? displayName : courseKey; + const hasDisplayName = (displayName ?? '').trim().length ? displayName : courseKey; return ( diff --git a/src/studio-home/tabs-section/utils.js b/src/studio-home/tabs-section/utils.js index 5d3822b8ed..dff32dc95a 100644 --- a/src/studio-home/tabs-section/utils.js +++ b/src/studio-home/tabs-section/utils.js @@ -5,8 +5,11 @@ * @returns {array} - An array of alphabetically sorted courses or libraries. */ const sortAlphabeticallyArray = (arr) => [...arr] - .sort((firstArrayData, secondArrayData) => firstArrayData - .displayName.localeCompare(secondArrayData.displayName)); + .sort((firstArrayData, secondArrayData) => { + const firstDisplayName = firstArrayData.displayName ?? ''; + const secondDisplayName = secondArrayData.displayName ?? ''; + return firstDisplayName.localeCompare(secondDisplayName); + }); // eslint-disable-next-line import/prefer-default-export export { sortAlphabeticallyArray }; From 6760b757742447e31038bb1a4abbdf763c5b6a72 Mon Sep 17 00:00:00 2001 From: Jillian Date: Tue, 11 Jun 2024 22:01:25 +0930 Subject: [PATCH 02/10] fix: warnings about Duplicate message id (#1061) Fixes warnings about "duplicate message IDs", which seem to have been made by copy-paste errors. --- src/accessibility-page/messages.js | 2 +- src/content-tags-drawer/messages.js | 2 +- src/course-outline/card-header/messages.js | 2 +- src/course-outline/page-alerts/messages.js | 2 +- src/course-outline/status-bar/messages.js | 2 +- .../subsection-card/messages.js | 2 +- .../course-team-member/messages.js | 2 +- src/files-and-videos/files-page/messages.js | 4 ++-- src/files-and-videos/generic/messages.js | 6 +++--- .../table-custom-columns/ActiveColumn.jsx | 21 ++++--------------- src/generic/ConnectionErrorAlert.jsx | 3 +-- src/generic/configure-modal/messages.js | 6 +++--- src/import-page/import-stepper/messages.js | 2 +- 13 files changed, 21 insertions(+), 35 deletions(-) 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/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-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/page-alerts/messages.js b/src/course-outline/page-alerts/messages.js index ea67c76ad9..5373e714e9 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', }, 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/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/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/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/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', }, }); From e22cce9fa6f9754f0c14ee373e094f6b5ecf1d3a Mon Sep 17 00:00:00 2001 From: Raymond Zhou <56318341+rayzhou-bit@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:42:00 -0400 Subject: [PATCH 03/10] feat: flcc to 2.2.0 (#1106) --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e20d38375c..6aab05e1f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@edx/frontend-component-footer": "^13.0.2", "@edx/frontend-component-header": "^5.1.0", "@edx/frontend-enterprise-hotjar": "^2.0.0", - "@edx/frontend-lib-content-components": "^2.1.11", + "@edx/frontend-lib-content-components": "^2.2.0", "@edx/frontend-platform": "7.0.1", "@edx/openedx-atlas": "^0.6.0", "@fortawesome/fontawesome-svg-core": "1.2.36", @@ -2580,9 +2580,9 @@ } }, "node_modules/@edx/frontend-lib-content-components": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/@edx/frontend-lib-content-components/-/frontend-lib-content-components-2.1.11.tgz", - "integrity": "sha512-vzDpneZIXmjFo5sZxxZiVjt1zgczfEkJhT2h/sg2mcJ0m7Zuo9dPJeilATqB0pSTjZnNsIbX+NfT/Dx/mSJciQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@edx/frontend-lib-content-components/-/frontend-lib-content-components-2.2.0.tgz", + "integrity": "sha512-lbwGKCHj3yED5pjfWlGJAKf0zofW4CKcIHTlZUInGiXjDGA5dgRrPHbGymKZZmgimt6qDO7koQxsy+DVsqUMLw==", "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", @@ -2621,7 +2621,7 @@ "xmlchecker": "^0.1.0" }, "peerDependencies": { - "@edx/frontend-platform": "^7.0.1", + "@edx/frontend-platform": "^7.0.1 || ^8.0.0", "@openedx/paragon": "^21.5.7 || ^22.0.0", "prop-types": "^15.5.10", "react": "^16.14.0 || ^17.0.0", diff --git a/package.json b/package.json index 7ab1b57903..e832bf828c 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@edx/frontend-component-footer": "^13.0.2", "@edx/frontend-component-header": "^5.1.0", "@edx/frontend-enterprise-hotjar": "^2.0.0", - "@edx/frontend-lib-content-components": "^2.1.11", + "@edx/frontend-lib-content-components": "^2.2.0", "@edx/frontend-platform": "7.0.1", "@edx/openedx-atlas": "^0.6.0", "@fortawesome/fontawesome-svg-core": "1.2.36", From f20e5311a9907a119ed099828433de89f4fe31ed Mon Sep 17 00:00:00 2001 From: Jillian Date: Thu, 13 Jun 2024 14:37:26 +0930 Subject: [PATCH 04/10] Fix some test warnings (#1062) * fix: paragon's Hyperlink no longer accepts a 'content' attribute * test: ensure all act() calls are async * test: Removed "async" from "describe" Returning a Promise from "describe" is not supported. * fix: DiscussionsSettings tests Previous commit revealed several issues with these tests * Don't nest userAction.click in act() -- nested act() statements have indeterminent behaviour. * Use getBy* instead of findBy* with userAction to avoid nested act() statements * Use fireEvent.click when the onClick handlers need to be called * Use queryBy* instead of getBy* when using .toBeInTheDocument or waitForElementToBeRemoved queryBy* return null when the element is not found. * fix: typo in data-testid Warning: React does not recognize the `data-testId` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `data-testid` instead. * test: Use useLocation to test route changes --------- Co-authored-by: Yusuf Musleh --- .../course-apps/proctoring/Settings.test.jsx | 294 ++++++--------- .../header-buttons/HeaderButtons.test.jsx | 8 +- .../ChecklistSection/ChecklistItemComment.jsx | 7 +- .../files-page/FilesPage.test.jsx | 343 ++++++++---------- .../generic/table-components/GalleryCard.jsx | 2 +- .../videos-page/VideosPage.test.jsx | 284 ++++++--------- .../TranscriptSettings.test.jsx | 192 ++++------ .../CreateOrRerunCourseForm.test.jsx | 43 +-- .../modal-dropzone/ModalDropzone.test.jsx | 2 +- .../discussions/DiscussionsSettings.test.jsx | 180 ++++----- .../apps/shared/DiscussionRestriction.jsx | 2 +- .../RestrictDatesInput.jsx | 2 +- .../RestrictionSchedules.jsx | 2 +- .../discussions/app-list/AppList.jsx | 2 +- .../ScheduleAndDetails.test.jsx | 23 +- src/studio-home/StudioHome.test.jsx | 2 +- .../CollapsibleStateWithAction.test.jsx | 2 +- .../OrganizationSection.test.jsx | 2 +- .../course-item/CourseItem.test.jsx | 2 +- src/taxonomy/TaxonomyLayout.test.jsx | 2 +- .../taxonomy-card/TaxonomyCard.test.jsx | 2 +- .../TaxonomyDetailSideCard.test.jsx | 2 +- 22 files changed, 598 insertions(+), 802 deletions(-) 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/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/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/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/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/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/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/pages-and-resources/discussions/DiscussionsSettings.test.jsx b/src/pages-and-resources/discussions/DiscussionsSettings.test.jsx index 2f8215ceff..60a257ee1f 100644 --- a/src/pages-and-resources/discussions/DiscussionsSettings.test.jsx +++ b/src/pages-and-resources/discussions/DiscussionsSettings.test.jsx @@ -5,13 +5,18 @@ import { import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { AppProvider, PageWrap } from '@edx/frontend-platform/react'; import { - act, findByRole, getByRole, queryByLabelText, queryByRole, queryByTestId, queryByText, render, - screen, waitFor, waitForElementToBeRemoved, + act, findByRole, fireEvent, getByRole, queryByLabelText, queryByRole, queryByTestId, queryByText, + render, screen, waitFor, waitForElementToBeRemoved, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import MockAdapter from 'axios-mock-adapter'; import React from 'react'; -import { Routes, Route, MemoryRouter } from 'react-router-dom'; +import { + Routes, + Route, + MemoryRouter, + useLocation, +} from 'react-router-dom'; import { fetchCourseDetail } from '../../data/thunks'; import initializeStore from '../../store'; import { executeThunk } from '../../utils'; @@ -37,6 +42,11 @@ let container; // Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest. ReactDOM.createPortal = jest.fn(node => 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) => (