diff --git a/src/course-unit/CourseUnit.test.jsx b/src/course-unit/CourseUnit.test.jsx index f1c6197a4..e168ec09e 100644 --- a/src/course-unit/CourseUnit.test.jsx +++ b/src/course-unit/CourseUnit.test.jsx @@ -65,6 +65,7 @@ import xblockContainerIframeMessages from './xblock-container-iframe/messages'; import headerNavigationsMessages from './header-navigations/messages'; import sidebarMessages from './sidebar/messages'; import messages from './messages'; +import * as selectors from '../data/selectors'; let axiosMock; let store; @@ -166,27 +167,27 @@ describe('', () => { }); it('render CourseUnit component correctly', async () => { - const { getByText, getByRole, getByTestId } = render(); + render(); const currentSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name; const currentSubSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name; await waitFor(() => { - const unitHeaderTitle = getByTestId('unit-header-title'); - expect(getByText(unitDisplayName)).toBeInTheDocument(); + const unitHeaderTitle = screen.getByTestId('unit-header-title'); + expect(screen.getByText(unitDisplayName)).toBeInTheDocument(); expect(within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonEdit.defaultMessage })).toBeInTheDocument(); expect(within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonSettings.defaultMessage })).toBeInTheDocument(); - expect(getByRole('button', { name: headerNavigationsMessages.viewLiveButton.defaultMessage })).toBeInTheDocument(); - expect(getByRole('button', { name: headerNavigationsMessages.previewButton.defaultMessage })).toBeInTheDocument(); - expect(getByRole('button', { name: currentSectionName })).toBeInTheDocument(); - expect(getByRole('button', { name: currentSubSectionName })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: headerNavigationsMessages.viewLiveButton.defaultMessage })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: headerNavigationsMessages.previewButton.defaultMessage })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: currentSectionName })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: currentSubSectionName })).toBeInTheDocument(); }); }); it('renders the course unit iframe with correct attributes', async () => { - const { getByTitle } = render(); + render(); await waitFor(() => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toHaveAttribute('src', `${getConfig().STUDIO_BASE_URL}/container_embed/${blockId}`); expect(iframe).toHaveAttribute('allow', IFRAME_FEATURE_POLICY); expect(iframe).toHaveAttribute('style', 'height: 0px;'); @@ -210,27 +211,27 @@ describe('', () => { }); it('displays an error alert when a studioAjaxError message is received', async () => { - const { getByTitle, getByTestId } = render(); + render(); await waitFor(() => { - const xblocksIframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const xblocksIframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(xblocksIframe).toBeInTheDocument(); simulatePostMessageEvent(messageTypes.studioAjaxError, { error: 'Some error text...', }); }); - expect(getByTestId('saving-error-alert')).toBeInTheDocument(); + expect(screen.getByTestId('saving-error-alert')).toBeInTheDocument(); }); it('renders XBlock iframe and opens legacy edit modal on editXBlock message', async () => { - const { getByTitle } = render(); + render(); await waitFor(() => { - const xblocksIframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const xblocksIframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(xblocksIframe).toBeInTheDocument(); simulatePostMessageEvent(messageTypes.editXBlock, { id: blockId }); - const legacyXBlockEditModalIframe = getByTitle( + const legacyXBlockEditModalIframe = screen.getByTitle( xblockContainerIframeMessages.legacyEditModalIframeTitle.defaultMessage, ); expect(legacyXBlockEditModalIframe).toBeInTheDocument(); @@ -248,14 +249,14 @@ describe('', () => { }); it('closes the legacy edit modal when closeXBlockEditorModal message is received', async () => { - const { getByTitle, queryByTitle } = render(); + render(); await waitFor(() => { - const xblocksIframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const xblocksIframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(xblocksIframe).toBeInTheDocument(); simulatePostMessageEvent(messageTypes.closeXBlockEditorModal, { id: blockId }); - const legacyXBlockEditModalIframe = queryByTitle( + const legacyXBlockEditModalIframe = screen.queryByTitle( xblockContainerIframeMessages.legacyEditModalIframeTitle.defaultMessage, ); expect(legacyXBlockEditModalIframe).not.toBeInTheDocument(); @@ -263,14 +264,14 @@ describe('', () => { }); it('closes legacy edit modal and updates course unit sidebar after saveEditedXBlockData message', async () => { - const { getByTitle, queryByTitle, getByTestId } = render(); + render(); await waitFor(() => { - const xblocksIframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const xblocksIframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(xblocksIframe).toBeInTheDocument(); simulatePostMessageEvent(messageTypes.saveEditedXBlockData); - const legacyXBlockEditModalIframe = queryByTitle( + const legacyXBlockEditModalIframe = screen.queryByTitle( xblockContainerIframeMessages.legacyEditModalIframeTitle.defaultMessage, ); expect(legacyXBlockEditModalIframe).not.toBeInTheDocument(); @@ -285,7 +286,7 @@ describe('', () => { }); await waitFor(() => { - const courseUnitSidebar = getByTestId('course-unit-sidebar'); + const courseUnitSidebar = screen.getByTestId('course-unit-sidebar'); expect( within(courseUnitSidebar).getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage), ).toBeInTheDocument(); @@ -304,10 +305,10 @@ describe('', () => { }); it('updates course unit sidebar after receiving refreshPositions message', async () => { - const { getByTitle, getByTestId } = render(); + render(); await waitFor(() => { - const xblocksIframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const xblocksIframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(xblocksIframe).toBeInTheDocument(); simulatePostMessageEvent(messageTypes.refreshPositions); }); @@ -321,7 +322,7 @@ describe('', () => { }); await waitFor(() => { - const courseUnitSidebar = getByTestId('course-unit-sidebar'); + const courseUnitSidebar = screen.getByTestId('course-unit-sidebar'); expect( within(courseUnitSidebar).getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage), ).toBeInTheDocument(); @@ -340,12 +341,10 @@ describe('', () => { }); it('checks whether xblock is removed when the corresponding delete button is clicked and the sidebar is the updated', async () => { - const { - getByTitle, getByText, queryByRole, getByRole, - } = render(); + render(); await waitFor(async () => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toHaveAttribute( 'aria-label', xblockContainerIframeMessages.xblockIframeLabel.defaultMessage @@ -356,10 +355,10 @@ describe('', () => { usageId: courseVerticalChildrenMock.children[0].block_id, }); - expect(getByText(/Delete this component?/i)).toBeInTheDocument(); - expect(getByText(/Deleting this component is permanent and cannot be undone./i)).toBeInTheDocument(); + expect(screen.getByText(/Delete this component?/i)).toBeInTheDocument(); + expect(screen.getByText(/Deleting this component is permanent and cannot be undone./i)).toBeInTheDocument(); - const dialog = getByRole('dialog'); + const dialog = screen.getByRole('dialog'); expect(dialog).toBeInTheDocument(); // Find the Cancel and Delete buttons within the iframe by their specific classes @@ -372,7 +371,7 @@ describe('', () => { usageId: courseVerticalChildrenMock.children[0].block_id, }); - expect(getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('dialog')).toBeInTheDocument(); userEvent.click(deleteButton); }); @@ -393,14 +392,14 @@ describe('', () => { await waitFor(() => { // check if the sidebar status is Published and Live - expect(getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText( sidebarMessages.publishLastPublished.defaultMessage .replace('{publishedOn}', courseUnitIndexMock.published_on) .replace('{publishedBy}', userName), )).toBeInTheDocument(); - expect(queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument(); - expect(getByText(unitDisplayName)).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument(); + expect(screen.getByText(unitDisplayName)).toBeInTheDocument(); }); axiosMock @@ -431,28 +430,28 @@ describe('', () => { await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch); await waitFor(() => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toHaveAttribute( 'aria-label', xblockContainerIframeMessages.xblockIframeLabel.defaultMessage .replace('{xblockCount}', updatedCourseVerticalChildren.length), ); // after removing the xblock, the sidebar status changes to Draft (unpublished changes) - expect(getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); + expect(screen.getByText( sidebarMessages.publishInfoDraftSaved.defaultMessage .replace('{editedOn}', courseUnitIndexMock.edited_on) .replace('{editedBy}', courseUnitIndexMock.edited_by), )).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText( sidebarMessages.releaseInfoWithSection.defaultMessage .replace('{sectionName}', courseUnitIndexMock.release_date_from), )).toBeInTheDocument(); @@ -460,9 +459,7 @@ describe('', () => { }); it('checks if xblock is a duplicate when the corresponding duplicate button is clicked and if the sidebar status is updated', async () => { - const { - getByTitle, getByRole, getByText, queryByRole, - } = render(); + render(); simulatePostMessageEvent(messageTypes.duplicateXBlock, { id: courseVerticalChildrenMock.children[0].block_id, @@ -482,8 +479,14 @@ describe('', () => { const updatedCourseVerticalChildren = [ ...courseVerticalChildrenMock.children, { - ...courseVerticalChildrenMock.children[0], name: 'New Cloned XBlock', + block_id: '1234567890', + block_type: 'drag-and-drop-v2', + user_partition_info: { + selectable_partitions: [], + selected_partition_index: -1, + selected_groups_label: '', + }, }, ]; @@ -495,9 +498,9 @@ describe('', () => { }); await waitFor(() => { - userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })); - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toHaveAttribute( 'aria-label', xblockContainerIframeMessages.xblockIframeLabel.defaultMessage @@ -526,14 +529,14 @@ describe('', () => { await waitFor(() => { // check if the sidebar status is Published and Live - expect(getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText( sidebarMessages.publishLastPublished.defaultMessage .replace('{publishedOn}', courseUnitIndexMock.published_on) .replace('{publishedBy}', userName), )).toBeInTheDocument(); - expect(queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument(); - expect(getByText(unitDisplayName)).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument(); + expect(screen.getByText(unitDisplayName)).toBeInTheDocument(); }); axiosMock @@ -542,7 +545,7 @@ describe('', () => { await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch); await waitFor(() => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toHaveAttribute( 'aria-label', xblockContainerIframeMessages.xblockIframeLabel.defaultMessage @@ -550,21 +553,21 @@ describe('', () => { ); // after duplicate the xblock, the sidebar status changes to Draft (unpublished changes) - expect(getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); + expect(screen.getByText( sidebarMessages.publishInfoDraftSaved.defaultMessage .replace('{editedOn}', courseUnitIndexMock.edited_on) .replace('{editedBy}', courseUnitIndexMock.edited_by), )).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText( sidebarMessages.releaseInfoWithSection.defaultMessage .replace('{sectionName}', courseUnitIndexMock.release_date_from), )).toBeInTheDocument(); @@ -574,19 +577,19 @@ describe('', () => { it('handles CourseUnit header action buttons', async () => { const { open } = window; window.open = jest.fn(); - const { getByRole } = render(); + render(); const { draft_preview_link: draftPreviewLink, published_preview_link: publishedPreviewLink, } = courseSectionVerticalMock; await waitFor(() => { - const viewLiveButton = getByRole('button', { name: headerNavigationsMessages.viewLiveButton.defaultMessage }); + const viewLiveButton = screen.getByRole('button', { name: headerNavigationsMessages.viewLiveButton.defaultMessage }); userEvent.click(viewLiveButton); expect(window.open).toHaveBeenCalled(); expect(window.open).toHaveBeenCalledWith(publishedPreviewLink, '_blank'); - const previewButton = getByRole('button', { name: headerNavigationsMessages.previewButton.defaultMessage }); + const previewButton = screen.getByRole('button', { name: headerNavigationsMessages.previewButton.defaultMessage }); userEvent.click(previewButton); expect(window.open).toHaveBeenCalled(); expect(window.open).toHaveBeenCalledWith(draftPreviewLink, '_blank'); @@ -596,12 +599,7 @@ describe('', () => { }); it('checks courseUnit title changing when edit query is successfully', async () => { - const { - findByText, - queryByRole, - getByRole, - getByTestId, - } = render(); + render(); let editTitleButton = null; let titleEditField = null; const newDisplayName = `${unitDisplayName} new`; @@ -637,7 +635,7 @@ describe('', () => { }); await waitFor(() => { - const unitHeaderTitle = getByTestId('unit-header-title'); + const unitHeaderTitle = screen.getByTestId('unit-header-title'); editTitleButton = within(unitHeaderTitle) .getByRole('button', { name: headerTitleMessages.altButtonEdit.defaultMessage }); titleEditField = within(unitHeaderTitle) @@ -645,7 +643,7 @@ describe('', () => { }); expect(titleEditField).not.toBeInTheDocument(); userEvent.click(editTitleButton); - titleEditField = getByRole('textbox', { name: headerTitleMessages.ariaLabelButtonEdit.defaultMessage }); + titleEditField = screen.getByRole('textbox', { name: headerTitleMessages.ariaLabelButtonEdit.defaultMessage }); await userEvent.clear(titleEditField); await userEvent.type(titleEditField, newDisplayName); @@ -653,9 +651,10 @@ describe('', () => { expect(titleEditField).toHaveValue(newDisplayName); - titleEditField = queryByRole('textbox', { name: headerTitleMessages.ariaLabelButtonEdit.defaultMessage }); + titleEditField = screen.queryByRole('textbox', { name: headerTitleMessages.ariaLabelButtonEdit.defaultMessage }); + titleEditField = screen.queryByRole('textbox', { name: headerTitleMessages.ariaLabelButtonEdit.defaultMessage }); expect(titleEditField).not.toBeInTheDocument(); - expect(await findByText(newDisplayName)).toBeInTheDocument(); + expect(await screen.findByText(newDisplayName)).toBeInTheDocument(); }); it('doesn\'t handle creating xblock and displays an error message', async () => { @@ -679,10 +678,10 @@ describe('', () => { axiosMock .onPost(postXBlockBaseApiUrl({ type: 'problem', category: 'problem', parentLocator: blockId })) .reply(200, courseCreateXblockMock); - const { getByText, getByRole } = render(); + render(); await waitFor(() => { - userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })); }); axiosMock @@ -702,7 +701,7 @@ describe('', () => { await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch); await waitFor(() => { - const problemButton = getByRole('button', { + const problemButton = screen.getByRole('button', { name: new RegExp(`problem ${addComponentMessages.buttonText.defaultMessage} Problem`, 'i'), hidden: true, }); @@ -711,7 +710,7 @@ describe('', () => { }); await waitFor(() => { - expect(getByRole('heading', { + expect(screen.getByRole('heading', { name: new RegExp(`${addComponentMessages.blockEditorModalTitle.defaultMessage}`, 'i'), })).toBeInTheDocument(); }); @@ -723,28 +722,28 @@ describe('', () => { await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch); // after creating problem xblock, the sidebar status changes to Draft (unpublished changes) - expect(getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); + expect(screen.getByText( sidebarMessages.publishInfoDraftSaved.defaultMessage .replace('{editedOn}', courseUnitIndexMock.edited_on) .replace('{editedBy}', courseUnitIndexMock.edited_by), )).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText( sidebarMessages.releaseInfoWithSection.defaultMessage .replace('{sectionName}', courseUnitIndexMock.release_date_from), )).toBeInTheDocument(); }); it('correct addition of a new course unit after click on the "Add new unit" button', async () => { - const { getByRole, getAllByTestId } = render(); + render(); let units = null; const updatedCourseSectionVerticalData = cloneDeep(courseSectionVerticalMock); const updatedAncestorsChild = updatedCourseSectionVerticalData.xblock_info.ancestor_info.ancestors[0]; @@ -754,7 +753,7 @@ describe('', () => { ]); await waitFor(async () => { - units = getAllByTestId('course-unit-btn'); + units = screen.getAllByTestId('course-unit-btn'); const courseUnits = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[0].child_info.children; expect(units).toHaveLength(courseUnits.length); }); @@ -771,8 +770,8 @@ describe('', () => { await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); - const addNewUnitBtn = getByRole('button', { name: courseSequenceMessages.newUnitBtnText.defaultMessage }); - units = getAllByTestId('course-unit-btn'); + const addNewUnitBtn = screen.getByRole('button', { name: courseSequenceMessages.newUnitBtnText.defaultMessage }); + units = screen.getAllByTestId('course-unit-btn'); const updatedCourseUnits = updatedCourseSectionVerticalData .xblock_info.ancestor_info.ancestors[0].child_info.children; @@ -784,7 +783,7 @@ describe('', () => { }); it('the sequence unit is updated after changing the unit header', async () => { - const { getAllByTestId, getByTestId } = render(); + render(); const updatedCourseSectionVerticalData = cloneDeep(courseSectionVerticalMock); const updatedAncestorsChild = updatedCourseSectionVerticalData.xblock_info.ancestor_info.ancestors[0]; set(updatedCourseSectionVerticalData, 'xblock_info.ancestor_info.ancestors[0].child_info.children', [ @@ -816,7 +815,7 @@ describe('', () => { await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); - const unitHeaderTitle = getByTestId('unit-header-title'); + const unitHeaderTitle = screen.getByTestId('unit-header-title'); const editTitleButton = within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonEdit.defaultMessage }); userEvent.click(editTitleButton); @@ -828,19 +827,21 @@ describe('', () => { await userEvent.tab(); await waitFor(async () => { - const units = getAllByTestId('course-unit-btn'); + const units = screen.getAllByTestId('course-unit-btn'); expect(units.some(unit => unit.title === newDisplayName)).toBe(true); }); }); - it('handles creating Video xblock and showing editor modal', async () => { + it('handles creating Video xblock and showing editor modal using videogalleryflow', async () => { + const waffleSpy = jest.spyOn(selectors, 'getWaffleFlags').mockReturnValue({ useVideoGalleryFlow: true }); + axiosMock .onPost(postXBlockBaseApiUrl({ type: 'video', category: 'video', parentLocator: blockId })) .reply(200, courseCreateXblockMock); - const { getByText, queryByRole, getByRole } = render(); + render(); await waitFor(() => { - userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })); }); axiosMock @@ -861,15 +862,89 @@ describe('', () => { await waitFor(() => { // check if the sidebar status is Published and Live - expect(getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument(); + }); + + expect(screen.getByText( + sidebarMessages.publishLastPublished.defaultMessage + .replace('{publishedOn}', courseUnitIndexMock.published_on) + .replace('{publishedBy}', userName), + )).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument(); + + const videoButton = screen.getByRole('button', { + name: new RegExp(`${addComponentMessages.buttonText.defaultMessage} Video`, 'i'), + hidden: true, + }); + + userEvent.click(videoButton); + + axiosMock + .onGet(getCourseUnitApiUrl(blockId)) + .reply(200, courseUnitIndexMock); + + await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch); + + // after creating video xblock, the sidebar status changes to Draft (unpublished changes) + expect(screen.getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); + expect(screen.getByText( + sidebarMessages.publishInfoDraftSaved.defaultMessage + .replace('{editedOn}', courseUnitIndexMock.edited_on) + .replace('{editedBy}', courseUnitIndexMock.edited_by), + )).toBeInTheDocument(); + expect(screen.getByText( + sidebarMessages.releaseInfoWithSection.defaultMessage + .replace('{sectionName}', courseUnitIndexMock.release_date_from), + )).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /add video to your course/i, hidden: true })).toBeInTheDocument(); + waffleSpy.mockRestore(); + }); + + it('handles creating Video xblock and showing editor modal', async () => { + axiosMock + .onPost(postXBlockBaseApiUrl({ type: 'video', category: 'video', parentLocator: blockId })) + .reply(200, courseCreateXblockMock); + render(); + + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })); + }); + + axiosMock + .onPost(getXBlockBaseApiUrl(blockId), { + publish: PUBLISH_TYPES.makePublic, + }) + .reply(200, { dummy: 'value' }); + axiosMock + .onGet(getCourseUnitApiUrl(blockId)) + .reply(200, { + ...courseUnitIndexMock, + visibility_state: UNIT_VISIBILITY_STATES.live, + has_changes: false, + published_by: userName, + }); + + await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch); + + await waitFor(() => { + // check if the sidebar status is Published and Live + expect(screen.getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText( sidebarMessages.publishLastPublished.defaultMessage .replace('{publishedOn}', courseUnitIndexMock.published_on) .replace('{publishedBy}', userName), )).toBeInTheDocument(); - expect(queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument(); - const videoButton = getByRole('button', { + const videoButton = screen.getByRole('button', { name: new RegExp(`${addComponentMessages.buttonText.defaultMessage} Video`, 'i'), hidden: true, }); @@ -890,45 +965,45 @@ describe('', () => { await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch); // after creating video xblock, the sidebar status changes to Draft (unpublished changes) - expect(getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); + expect(screen.getByText( sidebarMessages.publishInfoDraftSaved.defaultMessage .replace('{editedOn}', courseUnitIndexMock.edited_on) .replace('{editedBy}', courseUnitIndexMock.edited_by), )).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText( sidebarMessages.releaseInfoWithSection.defaultMessage .replace('{sectionName}', courseUnitIndexMock.release_date_from), )).toBeInTheDocument(); }); it('renders course unit details for a draft with unpublished changes', async () => { - const { getByText } = render(); + render(); await waitFor(() => { - expect(getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityStaffAndLearnersTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.releaseStatusTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.sidebarBodyNote.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityWillBeVisibleToTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.visibilityCheckboxTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonPublishTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.actionButtonDiscardChangesTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(courseUnitIndexMock.release_date)).toBeInTheDocument(); + expect(screen.getByText( sidebarMessages.publishInfoDraftSaved.defaultMessage .replace('{editedOn}', courseUnitIndexMock.edited_on) .replace('{editedBy}', courseUnitIndexMock.edited_by), )).toBeInTheDocument(); - expect(getByText( + expect(screen.getByText( sidebarMessages.releaseInfoWithSection.defaultMessage .replace('{sectionName}', courseUnitIndexMock.release_date_from), )).toBeInTheDocument(); @@ -936,14 +1011,14 @@ describe('', () => { }); it('renders course unit details in the sidebar', async () => { - const { getByText } = render(); + render(); const courseUnitLocationId = extractCourseUnitId(courseUnitIndexMock.id); await waitFor(() => { - expect(getByText(sidebarMessages.sidebarHeaderUnitLocationTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(sidebarMessages.unitLocationTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(courseUnitLocationId)).toBeInTheDocument(); - expect(getByText(sidebarMessages.unitLocationDescription.defaultMessage + expect(screen.getByText(sidebarMessages.sidebarHeaderUnitLocationTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.unitLocationTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(courseUnitLocationId)).toBeInTheDocument(); + expect(screen.getByText(sidebarMessages.unitLocationDescription.defaultMessage .replace('{id}', courseUnitLocationId))).toBeInTheDocument(); }); }); @@ -981,13 +1056,13 @@ describe('', () => { }); it('should toggle visibility from sidebar and update course unit state accordingly', async () => { - const { getByRole, getByTestId } = render(); + render(); let courseUnitSidebar; let draftUnpublishedChangesHeading; let visibilityCheckbox; await waitFor(() => { - courseUnitSidebar = getByTestId('course-unit-sidebar'); + courseUnitSidebar = screen.getByTestId('course-unit-sidebar'); draftUnpublishedChangesHeading = within(courseUnitSidebar) .getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage); @@ -1024,7 +1099,7 @@ describe('', () => { userEvent.click(visibilityCheckbox); - const modalNotification = getByRole('dialog'); + const modalNotification = screen.getByRole('dialog'); const makeVisibilityBtn = within(modalNotification).getByRole('button', { name: sidebarMessages.modalMakeVisibilityActionButtonText.defaultMessage }); const cancelBtn = within(modalNotification).getByRole('button', { name: sidebarMessages.modalMakeVisibilityCancelButtonText.defaultMessage }); const headingElement = within(modalNotification).getByRole('heading', { name: sidebarMessages.modalMakeVisibilityTitle.defaultMessage, class: 'pgn__modal-title' }); @@ -1056,12 +1131,12 @@ describe('', () => { }); it('should publish course unit after click on the "Publish" button', async () => { - const { getByTestId } = render(); + render(); let courseUnitSidebar; let publishBtn; await waitFor(() => { - courseUnitSidebar = getByTestId('course-unit-sidebar'); + courseUnitSidebar = screen.getByTestId('course-unit-sidebar'); publishBtn = within(courseUnitSidebar).queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage }); expect(publishBtn).toBeInTheDocument(); @@ -1095,12 +1170,12 @@ describe('', () => { }); it('should discard changes after click on the "Discard changes" button', async () => { - const { getByTestId, getByRole } = render(); + render(); let courseUnitSidebar; let discardChangesBtn; await waitFor(() => { - courseUnitSidebar = getByTestId('course-unit-sidebar'); + courseUnitSidebar = screen.getByTestId('course-unit-sidebar'); const draftUnpublishedChangesHeading = within(courseUnitSidebar) .getByText(sidebarMessages.sidebarTitleDraftUnpublishedChanges.defaultMessage); @@ -1110,7 +1185,7 @@ describe('', () => { userEvent.click(discardChangesBtn); - const modalNotification = getByRole('dialog'); + const modalNotification = screen.getByRole('dialog'); expect(modalNotification).toBeInTheDocument(); expect(within(modalNotification) .getByText(sidebarMessages.modalDiscardUnitChangesDescription.defaultMessage)).toBeInTheDocument(); @@ -1147,7 +1222,7 @@ describe('', () => { }); it('should toggle visibility from header configure modal and update course unit state accordingly', async () => { - const { getByRole, getByTestId } = render(); + render(); let courseUnitSidebar; let sidebarVisibilityCheckbox; let modalVisibilityCheckbox; @@ -1155,16 +1230,16 @@ describe('', () => { let restrictAccessSelect; await waitFor(() => { - courseUnitSidebar = getByTestId('course-unit-sidebar'); + courseUnitSidebar = screen.getByTestId('course-unit-sidebar'); sidebarVisibilityCheckbox = within(courseUnitSidebar) .getByLabelText(sidebarMessages.visibilityCheckboxTitle.defaultMessage); expect(sidebarVisibilityCheckbox).not.toBeChecked(); - const headerConfigureBtn = getByRole('button', { name: /settings/i }); + const headerConfigureBtn = screen.getByRole('button', { name: /settings/i }); expect(headerConfigureBtn).toBeInTheDocument(); userEvent.click(headerConfigureBtn); - configureModal = getByTestId('configure-modal'); + configureModal = screen.getByTestId('configure-modal'); restrictAccessSelect = within(configureModal) .getByRole('combobox', { name: configureModalMessages.restrictAccessTo.defaultMessage }); expect(within(configureModal) @@ -1220,8 +1295,8 @@ describe('', () => { ...getConfig(), ENABLE_TAGGING_TAXONOMY_PAGES: 'true', }); - const { getByText } = render(); - await waitFor(() => { expect(getByText('Unit tags')).toBeInTheDocument(); }); + render(); + await waitFor(() => { expect(screen.getByText('Unit tags')).toBeInTheDocument(); }); }); it('hides the Tags sidebar when not enabled', async () => { @@ -1229,15 +1304,13 @@ describe('', () => { ...getConfig(), ENABLE_TAGGING_TAXONOMY_PAGES: 'false', }); - const { queryByText } = render(); - await waitFor(() => { expect(queryByText('Unit tags')).not.toBeInTheDocument(); }); + render(); + await waitFor(() => { expect(screen.queryByText('Unit tags')).not.toBeInTheDocument(); }); }); describe('Copy paste functionality', () => { it('should copy a unit, paste it as a new unit, and update the course section vertical data', async () => { - const { - getAllByTestId, getByRole, - } = render(); + render(); axiosMock .onGet(getCourseUnitApiUrl(courseId)) @@ -1249,8 +1322,8 @@ describe('', () => { await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch); await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); - userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); - userEvent.click(getByRole('button', { name: courseSequenceMessages.pasteAsNewUnitLink.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: courseSequenceMessages.pasteAsNewUnitLink.defaultMessage })); let units = null; const updatedCourseSectionVerticalData = cloneDeep(courseSectionVerticalMock); @@ -1261,7 +1334,7 @@ describe('', () => { ]); await waitFor(() => { - units = getAllByTestId('course-unit-btn'); + units = screen.getAllByTestId('course-unit-btn'); const courseUnits = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[0].child_info.children; expect(units).toHaveLength(courseUnits.length); }); @@ -1277,7 +1350,7 @@ describe('', () => { await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); - units = getAllByTestId('course-unit-btn'); + units = screen.getAllByTestId('course-unit-btn'); const updatedCourseUnits = updatedCourseSectionVerticalData .xblock_info.ancestor_info.ancestors[0].child_info.children; @@ -1288,7 +1361,7 @@ describe('', () => { }); it('should increase the number of course XBlocks after copying and pasting a block', async () => { - const { getByRole, getByTitle } = render(); + render(); simulatePostMessageEvent(messageTypes.copyXBlock, { id: courseVerticalChildrenMock.children[0].block_id, @@ -1307,11 +1380,11 @@ describe('', () => { await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch); await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); - userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); - userEvent.click(getByRole('button', { name: messages.pasteButtonText.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: messages.pasteButtonText.defaultMessage })); await waitFor(() => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toHaveAttribute( 'aria-label', xblockContainerIframeMessages.xblockIframeLabel.defaultMessage @@ -1347,7 +1420,7 @@ describe('', () => { await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch); await waitFor(() => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toHaveAttribute( 'aria-label', xblockContainerIframeMessages.xblockIframeLabel.defaultMessage @@ -1357,9 +1430,7 @@ describe('', () => { }); it('displays a notification about new files after pasting a component', async () => { - const { - queryByTestId, getByTestId, getByRole, - } = render(); + render(); axiosMock .onGet(getCourseUnitApiUrl(courseId)) @@ -1371,8 +1442,8 @@ describe('', () => { await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch); await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); - userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); - userEvent.click(getByRole('button', { name: courseSequenceMessages.pasteAsNewUnitLink.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: courseSequenceMessages.pasteAsNewUnitLink.defaultMessage })); const updatedCourseSectionVerticalData = cloneDeep(courseSectionVerticalMock); const updatedAncestorsChild = updatedCourseSectionVerticalData.xblock_info.ancestor_info.ancestors[0]; @@ -1391,7 +1462,7 @@ describe('', () => { global.localStorage.setItem('staticFileNotices', JSON.stringify(clipboardMockResponse.staticFileNotices)); await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); await executeThunk(createNewCourseXBlock(camelCaseObject(postXBlockBody), null, blockId), store.dispatch); - const newFilesAlert = getByTestId('has-new-files-alert'); + const newFilesAlert = screen.getByTestId('has-new-files-alert'); expect(within(newFilesAlert) .getByText(pasteNotificationsMessages.hasNewFilesTitle.defaultMessage)).toBeInTheDocument(); @@ -1405,13 +1476,11 @@ describe('', () => { userEvent.click(within(newFilesAlert).getByText(/Dismiss/i)); - expect(queryByTestId('has-new-files-alert')).toBeNull(); + expect(screen.queryByTestId('has-new-files-alert')).toBeNull(); }); it('displays a notification about conflicting errors after pasting a component', async () => { - const { - queryByTestId, getByTestId, getByRole, - } = render(); + render(); axiosMock .onGet(getCourseUnitApiUrl(courseId)) @@ -1423,8 +1492,8 @@ describe('', () => { await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch); await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); - userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); - userEvent.click(getByRole('button', { name: courseSequenceMessages.pasteAsNewUnitLink.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: courseSequenceMessages.pasteAsNewUnitLink.defaultMessage })); const updatedCourseSectionVerticalData = cloneDeep(courseSectionVerticalMock); const updatedAncestorsChild = updatedCourseSectionVerticalData.xblock_info.ancestor_info.ancestors[0]; @@ -1445,7 +1514,7 @@ describe('', () => { global.localStorage.setItem('staticFileNotices', JSON.stringify(clipboardMockResponse.staticFileNotices)); await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); await executeThunk(createNewCourseXBlock(camelCaseObject(postXBlockBody), null, blockId), store.dispatch); - const conflictingErrorsAlert = getByTestId('has-conflicting-errors-alert'); + const conflictingErrorsAlert = screen.getByTestId('has-conflicting-errors-alert'); expect(within(conflictingErrorsAlert) .getByText(pasteNotificationsMessages.hasConflictingErrorsTitle.defaultMessage)).toBeInTheDocument(); @@ -1459,13 +1528,11 @@ describe('', () => { userEvent.click(within(conflictingErrorsAlert).getByText(/Dismiss/i)); - expect(queryByTestId('has-conflicting-errors-alert')).toBeNull(); + expect(screen.queryByTestId('has-conflicting-errors-alert')).toBeNull(); }); it('displays a notification about error files after pasting a component', async () => { - const { - queryByTestId, getByTestId, getByRole, - } = render(); + render(); axiosMock .onGet(getCourseUnitApiUrl(courseId)) @@ -1477,8 +1544,8 @@ describe('', () => { await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch); await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); - userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); - userEvent.click(getByRole('button', { name: courseSequenceMessages.pasteAsNewUnitLink.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage })); + userEvent.click(screen.getByRole('button', { name: courseSequenceMessages.pasteAsNewUnitLink.defaultMessage })); const updatedCourseSectionVerticalData = cloneDeep(courseSectionVerticalMock); const updatedAncestorsChild = updatedCourseSectionVerticalData.xblock_info.ancestor_info.ancestors[0]; @@ -1499,7 +1566,7 @@ describe('', () => { global.localStorage.setItem('staticFileNotices', JSON.stringify(clipboardMockResponse.staticFileNotices)); await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); await executeThunk(createNewCourseXBlock(camelCaseObject(postXBlockBody), null, blockId), store.dispatch); - const errorFilesAlert = getByTestId('has-error-files-alert'); + const errorFilesAlert = screen.getByTestId('has-error-files-alert'); expect(within(errorFilesAlert) .getByText(pasteNotificationsMessages.hasErrorsTitle.defaultMessage)).toBeInTheDocument(); @@ -1508,11 +1575,11 @@ describe('', () => { userEvent.click(within(errorFilesAlert).getByText(/Dismiss/i)); - expect(queryByTestId('has-error-files')).toBeNull(); + expect(screen.queryByTestId('has-error-files')).toBeNull(); }); it('should hide the "Paste component" block if canPasteComponent is false', async () => { - const { queryByText, queryByRole } = render(); + render(); axiosMock .onGet(getCourseVerticalChildrenApiUrl(blockId)) @@ -1523,10 +1590,10 @@ describe('', () => { await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch); - expect(queryByRole('button', { + expect(screen.queryByRole('button', { name: messages.pasteButtonText.defaultMessage, })).not.toBeInTheDocument(); - expect(queryByText( + expect(screen.queryByText( pasteComponentMessages.pasteButtonWhatsInClipboardText.defaultMessage, )).not.toBeInTheDocument(); }); @@ -1560,9 +1627,7 @@ describe('', () => { }); it('should display "Move Modal" on receive trigger message', async () => { - const { - getByRole, - } = render(); + render(); await screen.findByText(unitDisplayName); @@ -1576,15 +1641,12 @@ describe('', () => { await screen.findByText( moveModalMessages.moveModalTitle.defaultMessage.replace('{displayName}', requestData.title), ); - expect(getByRole('button', { name: moveModalMessages.moveModalSubmitButton.defaultMessage })).toBeInTheDocument(); - expect(getByRole('button', { name: moveModalMessages.moveModalCancelButton.defaultMessage })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: moveModalMessages.moveModalSubmitButton.defaultMessage })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: moveModalMessages.moveModalCancelButton.defaultMessage })).toBeInTheDocument(); }); it('should navigates to xBlock current unit', async () => { - const { - getByText, - getByRole, - } = render(); + render(); await screen.findByText(unitDisplayName); @@ -1600,7 +1662,7 @@ describe('', () => { ); const currentSection = courseOutlineInfoMock.child_info.children[1]; - const currentSectionItemBtn = getByRole('button', { + const currentSectionItemBtn = screen.getByRole('button', { name: `${currentSection.display_name} ${moveModalMessages.moveModalOutlineItemCurrentLocationText.defaultMessage} ${moveModalMessages.moveModalOutlineItemViewText.defaultMessage}`, }); expect(currentSectionItemBtn).toBeInTheDocument(); @@ -1608,7 +1670,7 @@ describe('', () => { await waitFor(() => { const currentSubsection = currentSection.child_info.children[0]; - const currentSubsectionItemBtn = getByRole('button', { + const currentSubsectionItemBtn = screen.getByRole('button', { name: `${currentSubsection.display_name} ${moveModalMessages.moveModalOutlineItemCurrentLocationText.defaultMessage} ${moveModalMessages.moveModalOutlineItemViewText.defaultMessage}`, }); expect(currentSubsectionItemBtn).toBeInTheDocument(); @@ -1616,7 +1678,7 @@ describe('', () => { }); await waitFor(() => { - const currentComponentLocationText = getByText( + const currentComponentLocationText = screen.getByText( moveModalMessages.moveModalOutlineItemCurrentComponentLocationText.defaultMessage, ); expect(currentComponentLocationText).toBeInTheDocument(); @@ -1624,9 +1686,7 @@ describe('', () => { }); it('should allow move operation and handles it successfully', async () => { - const { - getByRole, - } = render(); + render(); axiosMock .onPatch(postXBlockBaseApiUrl()) @@ -1650,7 +1710,7 @@ describe('', () => { ); const currentSection = courseOutlineInfoMock.child_info.children[1]; - const currentSectionItemBtn = getByRole('button', { + const currentSectionItemBtn = screen.getByRole('button', { name: `${currentSection.display_name} ${moveModalMessages.moveModalOutlineItemCurrentLocationText.defaultMessage} ${moveModalMessages.moveModalOutlineItemViewText.defaultMessage}`, }); expect(currentSectionItemBtn).toBeInTheDocument(); @@ -1658,7 +1718,7 @@ describe('', () => { const currentSubsection = currentSection.child_info.children[1]; await waitFor(() => { - const currentSubsectionItemBtn = getByRole('button', { + const currentSubsectionItemBtn = screen.getByRole('button', { name: `${currentSubsection.display_name} ${moveModalMessages.moveModalOutlineItemViewText.defaultMessage}`, }); expect(currentSubsectionItemBtn).toBeInTheDocument(); @@ -1667,14 +1727,14 @@ describe('', () => { await waitFor(() => { const currentUnit = currentSubsection.child_info.children[0]; - const currentUnitItemBtn = getByRole('button', { + const currentUnitItemBtn = screen.getByRole('button', { name: `${currentUnit.display_name} ${moveModalMessages.moveModalOutlineItemViewText.defaultMessage}`, }); expect(currentUnitItemBtn).toBeInTheDocument(); userEvent.click(currentUnitItemBtn); }); - const moveModalBtn = getByRole('button', { + const moveModalBtn = screen.getByRole('button', { name: moveModalMessages.moveModalSubmitButton.defaultMessage, }); expect(moveModalBtn).toBeInTheDocument(); @@ -1688,10 +1748,7 @@ describe('', () => { }); it('should display "Move Confirmation" alert after moving and undo operations', async () => { - const { - queryByRole, - getByText, - } = render(); + render(); axiosMock .onPatch(postXBlockBaseApiUrl()) @@ -1708,18 +1765,18 @@ describe('', () => { simulatePostMessageEvent(messageTypes.rollbackMovedXBlock, { locator: requestData.sourceLocator }); - const dismissButton = queryByRole('button', { + const dismissButton = screen.queryByRole('button', { name: /dismiss/i, hidden: true, }); - const undoButton = queryByRole('button', { + const undoButton = screen.queryByRole('button', { name: messages.undoMoveButton.defaultMessage, hidden: true, }); - const newLocationButton = queryByRole('button', { + const newLocationButton = screen.queryByRole('button', { name: messages.newLocationButton.defaultMessage, hidden: true, }); - expect(getByText(messages.alertMoveSuccessTitle.defaultMessage)).toBeInTheDocument(); - expect(getByText(`${requestData.title} has been moved`)).toBeInTheDocument(); + expect(screen.getByText(messages.alertMoveSuccessTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(`${requestData.title} has been moved`)).toBeInTheDocument(); expect(dismissButton).toBeInTheDocument(); expect(undoButton).toBeInTheDocument(); expect(newLocationButton).toBeInTheDocument(); @@ -1727,9 +1784,9 @@ describe('', () => { userEvent.click(undoButton); await waitFor(() => { - expect(getByText(messages.alertMoveCancelTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(messages.alertMoveCancelTitle.defaultMessage)).toBeInTheDocument(); }); - expect(getByText( + expect(screen.getByText( messages.alertMoveCancelDescription.defaultMessage.replace('{title}', requestData.title), )).toBeInTheDocument(); expect(dismissButton).toBeInTheDocument(); @@ -1738,9 +1795,7 @@ describe('', () => { }); it('should navigate to new location by button click', async () => { - const { - queryByRole, - } = render(); + render(); axiosMock .onPatch(postXBlockBaseApiUrl()) @@ -1755,7 +1810,7 @@ describe('', () => { callbackFn: requestData.callbackFn, }), store.dispatch); - const newLocationButton = queryByRole('button', { + const newLocationButton = screen.queryByRole('button', { name: messages.newLocationButton.defaultMessage, hidden: true, }); userEvent.click(newLocationButton); @@ -1768,16 +1823,14 @@ describe('', () => { describe('XBlock restrict access', () => { it('opens xblock restrict access modal successfully', async () => { - const { - getByTitle, getByTestId, - } = render(); + render(); const modalSubtitleText = configureModalMessages.restrictAccessTo.defaultMessage; const modalCancelBtnText = configureModalMessages.cancelButton.defaultMessage; const modalSaveBtnText = configureModalMessages.saveButton.defaultMessage; await waitFor(() => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); const usageId = courseVerticalChildrenMock.children[0].block_id; expect(iframe).toBeInTheDocument(); @@ -1787,7 +1840,7 @@ describe('', () => { }); await waitFor(() => { - const configureModal = getByTestId('configure-modal'); + const configureModal = screen.getByTestId('configure-modal'); expect(within(configureModal).getByText(modalSubtitleText)).toBeInTheDocument(); expect(within(configureModal).getByRole('button', { name: modalCancelBtnText })).toBeInTheDocument(); @@ -1796,12 +1849,10 @@ describe('', () => { }); it('closes xblock restrict access modal when cancel button is clicked', async () => { - const { - getByTitle, queryByTestId, getByTestId, - } = render(); + render(); await waitFor(() => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toBeInTheDocument(); simulatePostMessageEvent(messageTypes.manageXBlockAccess, { usageId: courseVerticalChildrenMock.children[0].block_id, @@ -1809,7 +1860,7 @@ describe('', () => { }); await waitFor(() => { - const configureModal = getByTestId('configure-modal'); + const configureModal = screen.getByTestId('configure-modal'); expect(configureModal).toBeInTheDocument(); userEvent.click(within(configureModal).getByRole('button', { name: configureModalMessages.cancelButton.defaultMessage, @@ -1817,7 +1868,7 @@ describe('', () => { expect(handleConfigureSubmitMock).not.toHaveBeenCalled(); }); - expect(queryByTestId('configure-modal')).not.toBeInTheDocument(); + expect(screen.queryByTestId('configure-modal')).not.toBeInTheDocument(); }); it('handles submit xblock restrict access data when save button is clicked', async () => { @@ -1828,15 +1879,13 @@ describe('', () => { }) .reply(200, { dummy: 'value' }); - const { - getByTitle, getByRole, getByTestId, queryByTestId, - } = render(); + render(); const accessGroupName1 = userPartitionInfoFormatted.selectablePartitions[0].groups[0].name; const accessGroupName2 = userPartitionInfoFormatted.selectablePartitions[0].groups[1].name; await waitFor(() => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toBeInTheDocument(); }); @@ -1846,13 +1895,13 @@ describe('', () => { }); }); - const configureModal = await waitFor(() => getByTestId('configure-modal')); + const configureModal = await waitFor(() => screen.getByTestId('configure-modal')); expect(configureModal).toBeInTheDocument(); expect(within(configureModal).queryByText(accessGroupName1)).not.toBeInTheDocument(); expect(within(configureModal).queryByText(accessGroupName2)).not.toBeInTheDocument(); - const restrictAccessSelect = getByRole('combobox', { + const restrictAccessSelect = screen.getByRole('combobox', { name: configureModalMessages.restrictAccessTo.defaultMessage, }); @@ -1882,17 +1931,17 @@ describe('', () => { expect(axiosMock.history.post[0].url).toBe(getXBlockBaseApiUrl(id)); }); - expect(queryByTestId('configure-modal')).not.toBeInTheDocument(); + expect(screen.queryByTestId('configure-modal')).not.toBeInTheDocument(); }); }); const checkLegacyEditModalOnEditMessage = async () => { - const { getByTitle, getByTestId } = render(); + render(); await waitFor(() => { - const editButton = getByTestId('header-edit-button'); + const editButton = screen.getByTestId('header-edit-button'); expect(editButton).toBeInTheDocument(); - const xblocksIframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const xblocksIframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(xblocksIframe).toBeInTheDocument(); userEvent.click(editButton); }); @@ -2079,46 +2128,65 @@ describe('', () => { }); it('should render split test content page correctly', async () => { - const { - getByText, - getByRole, - queryByRole, - getByTestId, - queryByText, - } = render(); + render(); const currentSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name; const currentSubSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name; const helpLinkUrl = 'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_components.html#components-that-contain-other-components'; waitFor(() => { - const unitHeaderTitle = getByTestId('unit-header-title'); - expect(getByText(unitDisplayName)).toBeInTheDocument(); + const unitHeaderTitle = screen.getByTestId('unit-header-title'); + expect(screen.getByText(unitDisplayName)).toBeInTheDocument(); expect(within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonEdit.defaultMessage })).toBeInTheDocument(); expect(within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonSettings.defaultMessage })).toBeInTheDocument(); - expect(getByRole('button', { name: currentSectionName })).toBeInTheDocument(); - expect(getByRole('button', { name: currentSubSectionName })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: currentSectionName })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: currentSubSectionName })).toBeInTheDocument(); - expect(queryByRole('heading', { name: addComponentMessages.title.defaultMessage })).not.toBeInTheDocument(); - expect(queryByRole('button', { name: headerNavigationsMessages.viewLiveButton.defaultMessage })).not.toBeInTheDocument(); - expect(queryByRole('button', { name: headerNavigationsMessages.previewButton.defaultMessage })).not.toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: addComponentMessages.title.defaultMessage })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: headerNavigationsMessages.viewLiveButton.defaultMessage })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: headerNavigationsMessages.previewButton.defaultMessage })).not.toBeInTheDocument(); - expect(queryByRole('heading', { name: /unit tags/i })).not.toBeInTheDocument(); - expect(queryByRole('heading', { name: /unit location/i })).not.toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: /unit tags/i })).not.toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: /unit location/i })).not.toBeInTheDocument(); // Sidebar const sidebarContent = [ - { query: queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestAddComponentTitle.defaultMessage }, - { query: queryByText, name: sidebarMessages.sidebarSplitTestSelectComponentType.defaultMessage.replaceAll('{bold_tag}', '') }, - { query: queryByText, name: sidebarMessages.sidebarSplitTestComponentAdded.defaultMessage }, - { query: queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestEditComponentTitle.defaultMessage }, - { query: queryByText, name: sidebarMessages.sidebarSplitTestEditComponentInstruction.defaultMessage.replaceAll('{bold_tag}', '') }, - { query: queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestReorganizeComponentTitle.defaultMessage }, - { query: queryByText, name: sidebarMessages.sidebarSplitTestReorganizeComponentInstruction.defaultMessage }, - { query: queryByText, name: sidebarMessages.sidebarSplitTestReorganizeGroupsInstruction.defaultMessage }, - { query: queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestExperimentComponentTitle.defaultMessage }, - { query: queryByText, name: sidebarMessages.sidebarSplitTestExperimentComponentInstruction.defaultMessage }, - { query: queryByRole, type: 'link', name: sidebarMessages.sidebarSplitTestLearnMoreLinkLabel.defaultMessage }, + { query: screen.queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestAddComponentTitle.defaultMessage }, + { query: screen.queryByText, name: sidebarMessages.sidebarSplitTestSelectComponentType.defaultMessage.replaceAll('{bold_tag}', '') }, + { query: screen.queryByText, name: sidebarMessages.sidebarSplitTestComponentAdded.defaultMessage }, + { query: screen.queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestEditComponentTitle.defaultMessage }, + { + query: screen.queryByText, + name: sidebarMessages.sidebarSplitTestEditComponentInstruction.defaultMessage + .replaceAll('{bold_tag}', ''), + }, + { + query: screen.queryByRole, + type: 'heading', + name: sidebarMessages.sidebarSplitTestReorganizeComponentTitle.defaultMessage, + }, + { + query: screen.queryByText, + name: sidebarMessages.sidebarSplitTestReorganizeComponentInstruction.defaultMessage, + }, + { + query: screen.queryByText, + name: sidebarMessages.sidebarSplitTestReorganizeGroupsInstruction.defaultMessage, + }, + { + query: screen.queryByRole, + type: 'heading', + name: sidebarMessages.sidebarSplitTestExperimentComponentTitle.defaultMessage, + }, + { + query: screen.queryByText, + name: sidebarMessages.sidebarSplitTestExperimentComponentInstruction.defaultMessage, + }, + { + query: screen.queryByRole, + type: 'link', + name: sidebarMessages.sidebarSplitTestLearnMoreLinkLabel.defaultMessage, + }, ]; sidebarContent.forEach(({ query, type, name }) => { @@ -2126,7 +2194,7 @@ describe('', () => { }); expect( - queryByRole('link', { name: sidebarMessages.sidebarSplitTestLearnMoreLinkLabel.defaultMessage }), + screen.queryByRole('link', { name: sidebarMessages.sidebarSplitTestLearnMoreLinkLabel.defaultMessage }), ).toHaveAttribute('href', helpLinkUrl); }); }); @@ -2139,7 +2207,7 @@ describe('', () => { }); it('renders and navigates to the new HTML XBlock editor after xblock duplicating', async () => { - const { getByTitle } = render(); + render(); const updatedCourseVerticalChildrenMock = JSON.parse(JSON.stringify(courseVerticalChildrenMock)); const targetBlockId = updatedCourseVerticalChildrenMock.children[1].block_id; @@ -2166,7 +2234,7 @@ describe('', () => { await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch); await waitFor(() => { - const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); + const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); expect(iframe).toBeInTheDocument(); simulatePostMessageEvent(messageTypes.currentXBlockId, { id: targetBlockId, diff --git a/src/course-unit/add-component/AddComponent.jsx b/src/course-unit/add-component/AddComponent.jsx index 74431791a..9e0fd850f 100644 --- a/src/course-unit/add-component/AddComponent.jsx +++ b/src/course-unit/add-component/AddComponent.jsx @@ -8,6 +8,7 @@ import { } from '@openedx/paragon'; import { getCourseSectionVertical } from '../data/selectors'; +import { getWaffleFlags } from '../../data/selectors'; import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants'; import ComponentModalView from './add-component-modals/ComponentModalView'; import AddComponentButton from './add-component-btn'; @@ -16,6 +17,7 @@ import { ComponentPicker } from '../../library-authoring/component-picker'; import { messageTypes } from '../constants'; import { useIframe } from '../../generic/hooks/context/hooks'; import { useEventListener } from '../../generic/hooks'; +import VideoSelectorPage from '../../editors/VideoSelectorPage'; import EditorPage from '../../editors/EditorPage'; const AddComponent = ({ @@ -32,6 +34,7 @@ const AddComponent = ({ const { componentTemplates = {} } = useSelector(getCourseSectionVertical); const blockId = addComponentTemplateData.parentLocator || parentLocator; const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle(); + const [isVideoSelectorModalOpen, showVideoSelectorModal, closeVideoSelectorModal] = useToggle(); const [isXBlockEditorModalOpen, showXBlockEditorModal, closeXBlockEditorModal] = useToggle(); const [blockType, setBlockType] = useState(null); @@ -41,6 +44,7 @@ const AddComponent = ({ const [selectedComponents, setSelectedComponents] = useState([]); const [usageId, setUsageId] = useState(null); const { sendMessageToIframe } = useIframe(); + const { useVideoGalleryFlow } = useSelector(getWaffleFlags); const receiveMessage = useCallback(({ data: { type, payload } }) => { if (type === messageTypes.showMultipleComponentPicker) { @@ -61,8 +65,9 @@ const AddComponent = ({ const onXBlockSave = useCallback(/* istanbul ignore next */ () => { closeXBlockEditorModal(); + closeVideoSelectorModal(); sendMessageToIframe(messageTypes.refreshXBlock, null); - }, [closeXBlockEditorModal, sendMessageToIframe]); + }, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe]); const handleLibraryV2Selection = useCallback((selection) => { handleCreateNewCourseXBlock({ @@ -80,7 +85,6 @@ const AddComponent = ({ case COMPONENT_TYPES.dragAndDrop: handleCreateNewCourseXBlock({ type, parentLocator: blockId }); break; - case COMPONENT_TYPES.video: case COMPONENT_TYPES.problem: handleCreateNewCourseXBlock({ type, parentLocator: blockId }, ({ courseKey, locator }) => { setCourseId(courseKey); @@ -89,6 +93,21 @@ const AddComponent = ({ showXBlockEditorModal(); }); break; + case COMPONENT_TYPES.video: + handleCreateNewCourseXBlock( + { type, parentLocator: blockId }, + /* istanbul ignore next */ ({ courseKey, locator }) => { + setCourseId(courseKey); + setBlockType(type); + setNewBlockId(locator); + if (useVideoGalleryFlow) { + showVideoSelectorModal(); + } else { + showXBlockEditorModal(); + } + }, + ); + break; // TODO: The library functional will be a bit different of current legacy (CMS) // behaviour and this ticket is on hold (blocked by other development team). case COMPONENT_TYPES.library: @@ -215,6 +234,24 @@ const AddComponent = ({ onChangeComponentSelection={setSelectedComponents} /> + +
+ onXBlockSave} + /> +
+
void; handleDeleteXBlock: (usageId: string) => void; handleScrollToXBlock: (scrollOffset: number) => void; - handleDuplicateXBlock: (blockType: string, usageId: string) => void; + handleDuplicateXBlock: (usageId: string) => void; handleEditXBlock: (blockType: string, usageId: string) => void; handleManageXBlockAccess: (usageId: string) => void; handleShowLegacyEditXBlockModal: (id: string) => void; diff --git a/src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx b/src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx index b5e50b427..daae1b822 100644 --- a/src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx +++ b/src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx @@ -37,7 +37,7 @@ export const useMessageHandlers = ({ [messageTypes.copyXBlock]: ({ usageId }) => copyToClipboard(usageId), [messageTypes.deleteXBlock]: ({ usageId }) => handleDeleteXBlock(usageId), [messageTypes.newXBlockEditor]: ({ blockType, usageId }) => handleEditXBlock(blockType, usageId), - [messageTypes.duplicateXBlock]: ({ blockType, usageId }) => handleDuplicateXBlock(blockType, usageId), + [messageTypes.duplicateXBlock]: ({ usageId }) => handleDuplicateXBlock(usageId), [messageTypes.manageXBlockAccess]: ({ usageId }) => handleManageXBlockAccess(usageId), [messageTypes.scrollToXBlock]: debounce(({ scrollOffset }) => handleScrollToXBlock(scrollOffset), 1000), [messageTypes.toggleCourseXBlockDropdown]: ({ diff --git a/src/course-unit/xblock-container-iframe/index.tsx b/src/course-unit/xblock-container-iframe/index.tsx index e780e3485..0c1ab91e2 100644 --- a/src/course-unit/xblock-container-iframe/index.tsx +++ b/src/course-unit/xblock-container-iframe/index.tsx @@ -4,8 +4,7 @@ import { } from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; import { useToggle, Sheet, StandardModal } from '@openedx/paragon'; -import { useDispatch } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; import { hideProcessingNotification, @@ -14,9 +13,9 @@ import { import DeleteModal from '../../generic/delete-modal/DeleteModal'; import ConfigureModal from '../../generic/configure-modal/ConfigureModal'; import ModalIframe from '../../generic/modal-iframe'; +import { getWaffleFlags } from '../../data/selectors'; import { IFRAME_FEATURE_POLICY } from '../../constants'; import ContentTagsDrawer from '../../content-tags-drawer/ContentTagsDrawer'; -import supportedEditors from '../../editors/supportedEditors'; import { useIframe } from '../../generic/hooks/context/hooks'; import { fetchCourseSectionVerticalData, @@ -36,6 +35,7 @@ import messages from './messages'; import { useIframeBehavior } from '../../generic/hooks/useIframeBehavior'; import { useIframeContent } from '../../generic/hooks/useIframeContent'; import { useIframeMessages } from '../../generic/hooks/useIframeMessages'; +import VideoSelectorPage from '../../editors/VideoSelectorPage'; import EditorPage from '../../editors/EditorPage'; const XBlockContainerIframe: FC = ({ @@ -43,12 +43,13 @@ const XBlockContainerIframe: FC = ({ }) => { const intl = useIntl(); const dispatch = useDispatch(); - const navigate = useNavigate(); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false); const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false); + const [isVideoSelectorModalOpen, showVideoSelectorModal, closeVideoSelectorModal] = useToggle(); const [isXBlockEditorModalOpen, showXBlockEditorModal, closeXBlockEditorModal] = useToggle(); const [blockType, setBlockType] = useState(''); + const { useVideoGalleryFlow } = useSelector(getWaffleFlags); const [newBlockId, setNewBlockId] = useState(''); const [accessManagedXBlockData, setAccessManagedXBlockData] = useState({}); const [iframeOffset, setIframeOffset] = useState(0); @@ -71,24 +72,25 @@ const XBlockContainerIframe: FC = ({ const onXBlockSave = useCallback(/* istanbul ignore next */ () => { closeXBlockEditorModal(); + closeVideoSelectorModal(); sendMessageToIframe(messageTypes.refreshXBlock, null); - }, [closeXBlockEditorModal, sendMessageToIframe]); + }, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe]); const handleEditXBlock = useCallback((type: string, id: string) => { setBlockType(type); setNewBlockId(id); - showXBlockEditorModal(); - }, [showXBlockEditorModal]); + if (type === 'video' && useVideoGalleryFlow) { + showVideoSelectorModal(); + } else { + showXBlockEditorModal(); + } + }, [showVideoSelectorModal, showXBlockEditorModal]); const handleDuplicateXBlock = useCallback( - (type: string, usageId: string) => { + (usageId: string) => { unitXBlockActions.handleDuplicate(usageId); - if (supportedEditors[type]) { - // istanbul ignore next - handleEditXBlock(type, usageId); - } }, - [unitXBlockActions, courseId, navigate], + [unitXBlockActions, courseId], ); const handleDeleteXBlock = (usageId: string) => { @@ -198,6 +200,24 @@ const XBlockContainerIframe: FC = ({ close={closeDeleteModal} onDeleteSubmit={onDeleteSubmit} /> + +
+ onXBlockSave} + /> +
+
{ const dispatch = useDispatch(); const loading = hooks.useInitializeApp({ @@ -26,7 +28,7 @@ const VideoSelector = ({ return null; } return ( - + ); }; @@ -35,6 +37,8 @@ VideoSelector.propTypes = { learningContextId: PropTypes.string.isRequired, lmsEndpointUrl: PropTypes.string.isRequired, studioEndpointUrl: PropTypes.string.isRequired, + returnFunction: PropTypes.func, + onCancel: PropTypes.func, }; export default VideoSelector; diff --git a/src/editors/VideoSelectorPage.jsx b/src/editors/VideoSelectorPage.jsx index 0d9609b04..1f2fe1a76 100644 --- a/src/editors/VideoSelectorPage.jsx +++ b/src/editors/VideoSelectorPage.jsx @@ -10,6 +10,8 @@ const VideoSelectorPage = ({ courseId, lmsEndpointUrl, studioEndpointUrl, + returnFunction, + onCancel, }) => ( @@ -42,6 +46,8 @@ VideoSelectorPage.propTypes = { courseId: PropTypes.string, lmsEndpointUrl: PropTypes.string, studioEndpointUrl: PropTypes.string, + returnFunction: PropTypes.func, + onCancel: PropTypes.func, }; export default VideoSelectorPage; diff --git a/src/editors/containers/VideoGallery/hooks.js b/src/editors/containers/VideoGallery/hooks.js index 648db7a64..7b392463f 100644 --- a/src/editors/containers/VideoGallery/hooks.js +++ b/src/editors/containers/VideoGallery/hooks.js @@ -86,6 +86,7 @@ export const filterList = ({ export const useVideoListProps = ({ searchSortProps, videos, + returnFunction, }) => { const [highlighted, setHighlighted] = React.useState(null); const [ @@ -128,7 +129,10 @@ export const useVideoListProps = ({ }, selectBtnProps: { onClick: () => { - if (highlighted) { + /* istanbul ignore next */ + if (returnFunction) { + returnFunction()(); + } else if (highlighted) { navigateTo(`/course/${learningContextId}/editor/video/${blockId}?selectedVideoId=${highlighted}`); } else { setShowSelectVideoError(true); @@ -138,10 +142,15 @@ export const useVideoListProps = ({ }; }; -export const useVideoUploadHandler = ({ replace }) => { +export const useVideoUploadHandler = ({ replace, uploadHandler }) => { const learningContextId = useSelector(selectors.app.learningContextId); const blockId = useSelector(selectors.app.blockId); const path = `/course/${learningContextId}/editor/video_upload/${blockId}`; + if (uploadHandler) { + return () => { + uploadHandler(); + }; + } if (replace) { return () => window.location.replace(path); } @@ -191,11 +200,12 @@ export const getstatusBadgeVariant = ({ status }) => { export const getStatusMessage = ({ status }) => Object.values(filterMessages).find((m) => m.defaultMessage === status); -export const useVideoProps = ({ videos }) => { +export const useVideoProps = ({ videos, uploadHandler, returnFunction }) => { const searchSortProps = useSearchAndSortProps(); const videoList = useVideoListProps({ searchSortProps, videos, + returnFunction, }); const { galleryError, @@ -203,7 +213,7 @@ export const useVideoProps = ({ videos }) => { inputError, selectBtnProps, } = videoList; - const fileInput = { click: useVideoUploadHandler({ replace: false }) }; + const fileInput = { click: useVideoUploadHandler({ replace: false, uploadHandler }) }; return { galleryError, diff --git a/src/editors/containers/VideoGallery/index.jsx b/src/editors/containers/VideoGallery/index.jsx index 5f5a8a8ca..ac25c8526 100644 --- a/src/editors/containers/VideoGallery/index.jsx +++ b/src/editors/containers/VideoGallery/index.jsx @@ -1,5 +1,10 @@ -import React, { useEffect } from 'react'; -import { Image } from '@openedx/paragon'; +import React, { useCallback, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { + Image, useToggle, StandardModal, +} from '@openedx/paragon'; +import { useSearchParams } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { selectors } from '../../data/redux'; import * as hooks from './hooks'; @@ -8,8 +13,11 @@ import { acceptedImgKeys } from './utils'; import messages from './messages'; import { RequestKeys } from '../../data/constants/requests'; import videoThumbnail from '../../data/images/videoThumbnail.svg'; +import VideoUploadEditor from '../VideoUploadEditor'; +import VideoEditor from '../VideoEditor'; -const VideoGallery = () => { +const VideoGallery = ({ returnFunction, onCancel }) => { + const intl = useIntl(); const rawVideos = useSelector(selectors.app.videos); const isLoaded = useSelector( (state) => selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchVideos }), @@ -21,14 +29,27 @@ const VideoGallery = () => { (state) => selectors.requests.isFailed(state, { requestKey: RequestKeys.uploadVideo }), ); const videos = hooks.buildVideos({ rawVideos }); - const handleVideoUpload = hooks.useVideoUploadHandler({ replace: true }); + const [isVideoUploadModalOpen, showVideoUploadModal, closeVideoUploadModal] = useToggle(); + const [isVideoEditorModalOpen, showVideoEditorModal, closeVideoEditorModal] = useToggle(); + const setSearchParams = useSearchParams()[1]; useEffect(() => { - // If no videos exists redirects to the video upload screen + // If no videos exists opens to the video upload modal if (isLoaded && videos.length === 0) { - handleVideoUpload(); + showVideoUploadModal(); } }, [isLoaded]); + + const onVideoUpload = useCallback((videoUrl) => { + closeVideoUploadModal(); + showVideoEditorModal(); + setSearchParams({ selectedVideoUrl: videoUrl }); + }, [closeVideoUploadModal, showVideoEditorModal, setSearchParams]); + + const uploadHandler = useCallback(() => { + showVideoUploadModal(); + }); + const { galleryError, inputError, @@ -36,7 +57,7 @@ const VideoGallery = () => { galleryProps, searchSortProps, selectBtnProps, - } = hooks.useVideoProps({ videos }); + } = hooks.useVideoProps({ videos, uploadHandler, returnFunction }); const handleCancel = hooks.useCancelHandler(); const modalMessages = { @@ -60,8 +81,8 @@ const VideoGallery = () => { { isFetchError, }} /> + +
+ +
+
+ {isVideoEditorModalOpen && ( + + )} ); }; -VideoGallery.propTypes = {}; +VideoGallery.propTypes = { + onCancel: PropTypes.func, + returnFunction: PropTypes.func, +}; export default VideoGallery; diff --git a/src/editors/containers/VideoGallery/index.test.jsx b/src/editors/containers/VideoGallery/index.test.jsx index 1cffffea8..6e45a02d2 100644 --- a/src/editors/containers/VideoGallery/index.test.jsx +++ b/src/editors/containers/VideoGallery/index.test.jsx @@ -6,6 +6,8 @@ import React from 'react'; import { act, fireEvent, render, screen, } from '@testing-library/react'; +import * as reactRouterDom from 'react-router-dom'; +import * as reduxThunks from '../../data/redux'; import VideoGallery from './index'; @@ -120,11 +122,10 @@ describe('VideoGallery', () => { expect(screen.getByText(video.client_video_id)).toBeInTheDocument() )); }); - it('navigates to video upload page when there are no videos', async () => { - expect(window.location.replace).not.toHaveBeenCalled(); + it('renders video upload modal when there are no videos', async () => { updateState({ videos: [] }); await renderComponent(); - expect(window.location.replace).toHaveBeenCalled(); + expect(screen.getByRole('heading', { name: /upload or embed a new video/i })).toBeInTheDocument(); }); it.each([ [/newest/i, [2, 1, 3]], @@ -191,5 +192,36 @@ describe('VideoGallery', () => { expect(screen.queryByText('client_id_1')).not.toBeInTheDocument(); expect(screen.queryByText('client_id_3')).not.toBeInTheDocument(); }); + + it('calls onVideoUpload correctly when a video is uploaded', async () => { + // Mock useSearchParams + const setSearchParams = jest.fn(); + jest.spyOn(reactRouterDom, 'useSearchParams').mockReturnValue([{}, setSearchParams]); + + // Mock the uploadVideo thunk to immediately call postUploadRedirect + jest.spyOn(reduxThunks.thunkActions.video, 'uploadVideo').mockImplementation( + ({ postUploadRedirect }) => () => { + if (postUploadRedirect) { + postUploadRedirect('http://test.video/url.mp4'); + } + return { type: 'MOCK_UPLOAD_VIDEO' }; + }, + ); + + await renderComponent(); + + // Open the upload modal by clicking the button + const openModalButton = screen.getByRole('button', { name: /upload or embed a new video/i }); + fireEvent.click(openModalButton); + + // Wait for the input to appear in the modal + const urlInput = await screen.findByPlaceholderText('Paste your video ID or URL'); + fireEvent.change(urlInput, { target: { value: 'http://test.video/url.mp4' } }); + + const submitButton = screen.getByRole('button', { name: /submit/i }); + fireEvent.click(submitButton); + + expect(setSearchParams).toHaveBeenCalledWith({ selectedVideoUrl: 'http://test.video/url.mp4' }); + }); }); }); diff --git a/src/editors/containers/VideoGallery/messages.js b/src/editors/containers/VideoGallery/messages.js index e26dd63db..3dd446b7c 100644 --- a/src/editors/containers/VideoGallery/messages.js +++ b/src/editors/containers/VideoGallery/messages.js @@ -21,7 +21,16 @@ const messages = { defaultMessage: 'Upload or embed a new video', description: 'Label for upload button', }, - + videoUploadModalTitle: { + id: 'authoring.selectvideomodal.upload.title', + defaultMessage: 'Upload or embed a new video', + description: 'Label for upload modal', + }, + videoEditorModalTitle: { + id: 'authoring.selectvideomodal.edit.title', + defaultMessage: 'Edit selected video', + description: 'Label for editor modal', + }, // Sort Dropdown sortByDateNewest: { id: 'authoring.selectvideomodal.sort.datenewest.label', diff --git a/src/editors/containers/VideoUploadEditor/VideoUploader.jsx b/src/editors/containers/VideoUploadEditor/VideoUploader.jsx index 028d1c085..09d943db8 100644 --- a/src/editors/containers/VideoUploadEditor/VideoUploader.jsx +++ b/src/editors/containers/VideoUploadEditor/VideoUploader.jsx @@ -10,9 +10,9 @@ import { thunkActions } from '../../data/redux'; import * as hooks from './hooks'; import messages from './messages'; -const URLUploader = () => { +const URLUploader = ({ onUpload }) => { const [textInputValue, setTextInputValue] = React.useState(''); - const onURLUpload = hooks.onVideoUpload('selectedVideoUrl'); + const onURLUpload = hooks.onVideoUpload('selectedVideoUrl', onUpload); const intl = useIntl(); return (
@@ -58,16 +58,16 @@ const URLUploader = () => { ); }; -export const VideoUploader = ({ setLoading }) => { +export const VideoUploader = ({ setLoading, onUpload, onClose }) => { const dispatch = useDispatch(); const intl = useIntl(); - const goBack = hooks.useHistoryGoBack(); + const goBack = onClose || hooks.useHistoryGoBack(); const handleProcessUpload = ({ fileData }) => { dispatch(thunkActions.video.uploadVideo({ supportedFiles: [fileData], setLoadSpinner: setLoading, - postUploadRedirect: hooks.onVideoUpload('selectedVideoId'), + postUploadRedirect: hooks.onVideoUpload('selectedVideoId', onUpload), })); }; @@ -85,14 +85,20 @@ export const VideoUploader = ({ setLoading }) => { } + inputComponent={} />
); }; +URLUploader.propTypes = { + onUpload: PropTypes.func, +}; + VideoUploader.propTypes = { setLoading: PropTypes.func.isRequired, + onUpload: PropTypes.func, + onClose: PropTypes.func, }; export default VideoUploader; diff --git a/src/editors/containers/VideoUploadEditor/hooks.js b/src/editors/containers/VideoUploadEditor/hooks.js index a2774d9c6..3cc1f8468 100644 --- a/src/editors/containers/VideoUploadEditor/hooks.js +++ b/src/editors/containers/VideoUploadEditor/hooks.js @@ -11,15 +11,20 @@ export const { navigateTo, } = appHooks; -export const postUploadRedirect = (storeState, uploadType = 'selectedVideoUrl') => { +export const postUploadRedirect = (storeState, uploadType = 'selectedVideoUrl', onUpload = null) => { const learningContextId = selectors.app.learningContextId(storeState); const blockId = selectors.app.blockId(storeState); + if (onUpload) { + return (videoUrl) => { + onUpload(videoUrl, learningContextId, blockId); + }; + } return (videoUrl) => navigateTo(`/course/${learningContextId}/editor/video/${blockId}?${uploadType}=${videoUrl}`); }; -export const onVideoUpload = (uploadType) => { +export const onVideoUpload = (uploadType, onUpload) => { const storeState = store.getState(); - return module.postUploadRedirect(storeState, uploadType); + return module.postUploadRedirect(storeState, uploadType, onUpload); }; export const useUploadVideo = async ({ diff --git a/src/editors/containers/VideoUploadEditor/index.jsx b/src/editors/containers/VideoUploadEditor/index.jsx index ae2be7b5f..91664d3e0 100644 --- a/src/editors/containers/VideoUploadEditor/index.jsx +++ b/src/editors/containers/VideoUploadEditor/index.jsx @@ -1,17 +1,18 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Spinner } from '@openedx/paragon'; import './index.scss'; import messages from './messages'; import { VideoUploader } from './VideoUploader'; -const VideoUploadEditor = () => { +const VideoUploadEditor = ({ onUpload, onClose }) => { const [loading, setLoading] = React.useState(false); const intl = useIntl(); return (!loading) ? (
- +
) : (
{ ); }; +VideoUploadEditor.propTypes = { + onUpload: PropTypes.func, + onClose: PropTypes.func, +}; + export default VideoUploadEditor;