fix: do open editor of new xblock when duplicating (#2017)
* feat: display editors as modals (#1838) * fix: do open editor of new xblock when duplicating (#1887) Fixes bug where after duplicating an xblock, the editor modal of the old xblock is being open instead of the new copied xblock.
This commit is contained in:
committed by
GitHub
parent
dd731a0d19
commit
944d1316ad
@@ -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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('render CourseUnit component correctly', async () => {
|
||||
const { getByText, getByRole, getByTestId } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('displays an error alert when a studioAjaxError message is received', async () => {
|
||||
const { getByTitle, getByTestId } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('closes the legacy edit modal when closeXBlockEditorModal message is received', async () => {
|
||||
const { getByTitle, queryByTitle } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('closes legacy edit modal and updates course unit sidebar after saveEditedXBlockData message', async () => {
|
||||
const { getByTitle, queryByTitle, getByTestId } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('updates course unit sidebar after receiving refreshPositions message', async () => {
|
||||
const { getByTitle, getByTestId } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
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('<CourseUnit />', () => {
|
||||
usageId: courseVerticalChildrenMock.children[0].block_id,
|
||||
});
|
||||
|
||||
expect(getByRole('dialog')).toBeInTheDocument();
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
userEvent.click(deleteButton);
|
||||
});
|
||||
|
||||
@@ -393,14 +392,14 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
simulatePostMessageEvent(messageTypes.duplicateXBlock, {
|
||||
id: courseVerticalChildrenMock.children[0].block_id,
|
||||
@@ -482,8 +479,14 @@ describe('<CourseUnit />', () => {
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
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('<CourseUnit />', () => {
|
||||
);
|
||||
|
||||
// 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('<CourseUnit />', () => {
|
||||
it('handles CourseUnit header action buttons', async () => {
|
||||
const { open } = window;
|
||||
window.open = jest.fn();
|
||||
const { getByRole } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('checks courseUnit title changing when edit query is successfully', async () => {
|
||||
const {
|
||||
findByText,
|
||||
queryByRole,
|
||||
getByRole,
|
||||
getByTestId,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
let editTitleButton = null;
|
||||
let titleEditField = null;
|
||||
const newDisplayName = `${unitDisplayName} new`;
|
||||
@@ -637,7 +635,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
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('<CourseUnit />', () => {
|
||||
});
|
||||
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('<CourseUnit />', () => {
|
||||
|
||||
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 () => {
|
||||
@@ -675,15 +674,14 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('handle creating Problem xblock and navigate to editor page', async () => {
|
||||
const { courseKey, locator } = courseCreateXblockMock;
|
||||
it('handle creating Problem xblock and showing editor modal', async () => {
|
||||
axiosMock
|
||||
.onPost(postXBlockBaseApiUrl({ type: 'problem', category: 'problem', parentLocator: blockId }))
|
||||
.reply(200, courseCreateXblockMock);
|
||||
const { getByText, getByRole } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
await waitFor(() => {
|
||||
userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage }));
|
||||
userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage }));
|
||||
});
|
||||
|
||||
axiosMock
|
||||
@@ -703,13 +701,18 @@ describe('<CourseUnit />', () => {
|
||||
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,
|
||||
});
|
||||
|
||||
userEvent.click(problemButton);
|
||||
expect(mockedUsedNavigate).toHaveBeenCalled();
|
||||
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseKey}/editor/problem/${locator}`);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', {
|
||||
name: new RegExp(`${addComponentMessages.blockEditorModalTitle.defaultMessage}`, 'i'),
|
||||
})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
axiosMock
|
||||
@@ -719,66 +722,28 @@ describe('<CourseUnit />', () => {
|
||||
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('handle creating Text xblock and saves scroll position in localStorage', async () => {
|
||||
const { getByText, getByRole } = render(<RootWrapper />);
|
||||
const xblockType = 'text';
|
||||
|
||||
axiosMock
|
||||
.onPost(postXBlockBaseApiUrl({ type: xblockType, category: 'html', parentLocator: blockId }))
|
||||
.reply(200, courseCreateXblockMock);
|
||||
|
||||
window.scrollTo(0, 250);
|
||||
Object.defineProperty(window, 'scrollY', { value: 250, configurable: true });
|
||||
|
||||
await waitFor(() => {
|
||||
const textButton = screen.getByRole('button', { name: /Text/i });
|
||||
|
||||
expect(getByText(addComponentMessages.title.defaultMessage)).toBeInTheDocument();
|
||||
|
||||
userEvent.click(textButton);
|
||||
|
||||
const addXBlockDialog = getByRole('dialog');
|
||||
expect(addXBlockDialog).toBeInTheDocument();
|
||||
|
||||
expect(getByText(
|
||||
addComponentMessages.modalContainerTitle.defaultMessage.replace('{componentTitle}', xblockType),
|
||||
)).toBeInTheDocument();
|
||||
|
||||
const textRadio = screen.getByRole('radio', { name: /Text/i });
|
||||
userEvent.click(textRadio);
|
||||
expect(textRadio).toBeChecked();
|
||||
|
||||
const selectBtn = getByRole('button', { name: addComponentMessages.modalBtnText.defaultMessage });
|
||||
expect(selectBtn).toBeInTheDocument();
|
||||
|
||||
userEvent.click(selectBtn);
|
||||
});
|
||||
|
||||
expect(localStorage.getItem('createXBlockLastYPosition')).toBe('250');
|
||||
});
|
||||
|
||||
it('correct addition of a new course unit after click on the "Add new unit" button', async () => {
|
||||
const { getByRole, getAllByTestId } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
let units = null;
|
||||
const updatedCourseSectionVerticalData = cloneDeep(courseSectionVerticalMock);
|
||||
const updatedAncestorsChild = updatedCourseSectionVerticalData.xblock_info.ancestor_info.ancestors[0];
|
||||
@@ -788,7 +753,7 @@ describe('<CourseUnit />', () => {
|
||||
]);
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -805,8 +770,8 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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;
|
||||
|
||||
@@ -818,7 +783,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('the sequence unit is updated after changing the unit header', async () => {
|
||||
const { getAllByTestId, getByTestId } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
const updatedCourseSectionVerticalData = cloneDeep(courseSectionVerticalMock);
|
||||
const updatedAncestorsChild = updatedCourseSectionVerticalData.xblock_info.ancestor_info.ancestors[0];
|
||||
set(updatedCourseSectionVerticalData, 'xblock_info.ancestor_info.ancestors[0].child_info.children', [
|
||||
@@ -850,7 +815,7 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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);
|
||||
@@ -862,20 +827,21 @@ describe('<CourseUnit />', () => {
|
||||
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 navigates to editor page', async () => {
|
||||
const { courseKey, locator } = courseCreateXblockMock;
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
await waitFor(() => {
|
||||
userEvent.click(getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage }));
|
||||
userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage }));
|
||||
});
|
||||
|
||||
axiosMock
|
||||
@@ -896,23 +862,23 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
// check if the sidebar status is Published and Live
|
||||
expect(getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(
|
||||
sidebarMessages.publishLastPublished.defaultMessage
|
||||
.replace('{publishedOn}', courseUnitIndexMock.published_on)
|
||||
.replace('{publishedBy}', userName),
|
||||
)).toBeInTheDocument();
|
||||
expect(queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument();
|
||||
|
||||
const videoButton = getByRole('button', {
|
||||
name: new RegExp(`${addComponentMessages.buttonText.defaultMessage} Video`, 'i'),
|
||||
});
|
||||
|
||||
userEvent.click(videoButton);
|
||||
expect(mockedUsedNavigate).toHaveBeenCalled();
|
||||
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseKey}/editor/video/${locator}`);
|
||||
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);
|
||||
@@ -920,45 +886,124 @@ describe('<CourseUnit />', () => {
|
||||
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();
|
||||
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(<RootWrapper />);
|
||||
|
||||
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(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);
|
||||
});
|
||||
|
||||
/** TODO -- fix this test.
|
||||
await waitFor(() => {
|
||||
expect(getByRole('textbox', { name: /paste your video id or url/i })).toBeInTheDocument();
|
||||
});
|
||||
*/
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
it('renders course unit details for a draft with unpublished changes', async () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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();
|
||||
@@ -966,14 +1011,14 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('renders course unit details in the sidebar', async () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1011,13 +1056,13 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should toggle visibility from sidebar and update course unit state accordingly', async () => {
|
||||
const { getByRole, getByTestId } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
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);
|
||||
@@ -1054,7 +1099,7 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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' });
|
||||
@@ -1086,12 +1131,12 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should publish course unit after click on the "Publish" button', async () => {
|
||||
const { getByTestId } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
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();
|
||||
|
||||
@@ -1125,12 +1170,12 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should discard changes after click on the "Discard changes" button', async () => {
|
||||
const { getByTestId, getByRole } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
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);
|
||||
@@ -1140,7 +1185,7 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
userEvent.click(discardChangesBtn);
|
||||
|
||||
const modalNotification = getByRole('dialog');
|
||||
const modalNotification = screen.getByRole('dialog');
|
||||
expect(modalNotification).toBeInTheDocument();
|
||||
expect(within(modalNotification)
|
||||
.getByText(sidebarMessages.modalDiscardUnitChangesDescription.defaultMessage)).toBeInTheDocument();
|
||||
@@ -1177,7 +1222,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should toggle visibility from header configure modal and update course unit state accordingly', async () => {
|
||||
const { getByRole, getByTestId } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
let courseUnitSidebar;
|
||||
let sidebarVisibilityCheckbox;
|
||||
let modalVisibilityCheckbox;
|
||||
@@ -1185,16 +1230,16 @@ describe('<CourseUnit />', () => {
|
||||
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)
|
||||
@@ -1250,8 +1295,8 @@ describe('<CourseUnit />', () => {
|
||||
...getConfig(),
|
||||
ENABLE_TAGGING_TAXONOMY_PAGES: 'true',
|
||||
});
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
await waitFor(() => { expect(getByText('Unit tags')).toBeInTheDocument(); });
|
||||
render(<RootWrapper />);
|
||||
await waitFor(() => { expect(screen.getByText('Unit tags')).toBeInTheDocument(); });
|
||||
});
|
||||
|
||||
it('hides the Tags sidebar when not enabled', async () => {
|
||||
@@ -1259,15 +1304,13 @@ describe('<CourseUnit />', () => {
|
||||
...getConfig(),
|
||||
ENABLE_TAGGING_TAXONOMY_PAGES: 'false',
|
||||
});
|
||||
const { queryByText } = render(<RootWrapper />);
|
||||
await waitFor(() => { expect(queryByText('Unit tags')).not.toBeInTheDocument(); });
|
||||
render(<RootWrapper />);
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
@@ -1279,8 +1322,8 @@ describe('<CourseUnit />', () => {
|
||||
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);
|
||||
@@ -1291,7 +1334,7 @@ describe('<CourseUnit />', () => {
|
||||
]);
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1307,7 +1350,7 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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;
|
||||
|
||||
@@ -1318,7 +1361,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should increase the number of course XBlocks after copying and pasting a block', async () => {
|
||||
const { getByRole, getByTitle } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
simulatePostMessageEvent(messageTypes.copyXBlock, {
|
||||
id: courseVerticalChildrenMock.children[0].block_id,
|
||||
@@ -1337,11 +1380,11 @@ describe('<CourseUnit />', () => {
|
||||
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
|
||||
@@ -1377,7 +1420,7 @@ describe('<CourseUnit />', () => {
|
||||
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
|
||||
@@ -1387,9 +1430,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('displays a notification about new files after pasting a component', async () => {
|
||||
const {
|
||||
queryByTestId, getByTestId, getByRole,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
@@ -1401,8 +1442,8 @@ describe('<CourseUnit />', () => {
|
||||
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];
|
||||
@@ -1421,7 +1462,7 @@ describe('<CourseUnit />', () => {
|
||||
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();
|
||||
@@ -1435,13 +1476,11 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
@@ -1453,8 +1492,8 @@ describe('<CourseUnit />', () => {
|
||||
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];
|
||||
@@ -1475,7 +1514,7 @@ describe('<CourseUnit />', () => {
|
||||
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();
|
||||
@@ -1489,13 +1528,11 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
@@ -1507,8 +1544,8 @@ describe('<CourseUnit />', () => {
|
||||
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];
|
||||
@@ -1529,7 +1566,7 @@ describe('<CourseUnit />', () => {
|
||||
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();
|
||||
@@ -1538,11 +1575,11 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseVerticalChildrenApiUrl(blockId))
|
||||
@@ -1553,10 +1590,10 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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();
|
||||
});
|
||||
@@ -1590,9 +1627,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should display "Move Modal" on receive trigger message', async () => {
|
||||
const {
|
||||
getByRole,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
await screen.findByText(unitDisplayName);
|
||||
|
||||
@@ -1606,15 +1641,12 @@ describe('<CourseUnit />', () => {
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
await screen.findByText(unitDisplayName);
|
||||
|
||||
@@ -1630,7 +1662,7 @@ describe('<CourseUnit />', () => {
|
||||
);
|
||||
|
||||
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();
|
||||
@@ -1638,7 +1670,7 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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();
|
||||
@@ -1646,7 +1678,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const currentComponentLocationText = getByText(
|
||||
const currentComponentLocationText = screen.getByText(
|
||||
moveModalMessages.moveModalOutlineItemCurrentComponentLocationText.defaultMessage,
|
||||
);
|
||||
expect(currentComponentLocationText).toBeInTheDocument();
|
||||
@@ -1654,9 +1686,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should allow move operation and handles it successfully', async () => {
|
||||
const {
|
||||
getByRole,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onPatch(postXBlockBaseApiUrl())
|
||||
@@ -1680,7 +1710,7 @@ describe('<CourseUnit />', () => {
|
||||
);
|
||||
|
||||
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();
|
||||
@@ -1688,7 +1718,7 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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();
|
||||
@@ -1697,14 +1727,14 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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();
|
||||
@@ -1718,10 +1748,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should display "Move Confirmation" alert after moving and undo operations', async () => {
|
||||
const {
|
||||
queryByRole,
|
||||
getByText,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onPatch(postXBlockBaseApiUrl())
|
||||
@@ -1738,18 +1765,18 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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();
|
||||
@@ -1757,9 +1784,9 @@ describe('<CourseUnit />', () => {
|
||||
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();
|
||||
@@ -1768,9 +1795,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should navigate to new location by button click', async () => {
|
||||
const {
|
||||
queryByRole,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onPatch(postXBlockBaseApiUrl())
|
||||
@@ -1785,7 +1810,7 @@ describe('<CourseUnit />', () => {
|
||||
callbackFn: requestData.callbackFn,
|
||||
}), store.dispatch);
|
||||
|
||||
const newLocationButton = queryByRole('button', {
|
||||
const newLocationButton = screen.queryByRole('button', {
|
||||
name: messages.newLocationButton.defaultMessage, hidden: true,
|
||||
});
|
||||
userEvent.click(newLocationButton);
|
||||
@@ -1798,16 +1823,14 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
describe('XBlock restrict access', () => {
|
||||
it('opens xblock restrict access modal successfully', async () => {
|
||||
const {
|
||||
getByTitle, getByTestId,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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();
|
||||
|
||||
@@ -1817,7 +1840,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
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();
|
||||
@@ -1826,12 +1849,10 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('closes xblock restrict access modal when cancel button is clicked', async () => {
|
||||
const {
|
||||
getByTitle, queryByTestId, getByTestId,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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,
|
||||
@@ -1839,7 +1860,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
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,
|
||||
@@ -1847,7 +1868,7 @@ describe('<CourseUnit />', () => {
|
||||
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 () => {
|
||||
@@ -1858,15 +1879,13 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
|
||||
const {
|
||||
getByTitle, getByRole, getByTestId, queryByTestId,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -1876,13 +1895,13 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -1912,17 +1931,17 @@ describe('<CourseUnit />', () => {
|
||||
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(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -2109,46 +2128,65 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('should render split test content page correctly', async () => {
|
||||
const {
|
||||
getByText,
|
||||
getByRole,
|
||||
queryByRole,
|
||||
getByTestId,
|
||||
queryByText,
|
||||
} = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
|
||||
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 }) => {
|
||||
@@ -2156,7 +2194,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
expect(
|
||||
queryByRole('link', { name: sidebarMessages.sidebarSplitTestLearnMoreLinkLabel.defaultMessage }),
|
||||
screen.queryByRole('link', { name: sidebarMessages.sidebarSplitTestLearnMoreLinkLabel.defaultMessage }),
|
||||
).toHaveAttribute('href', helpLinkUrl);
|
||||
});
|
||||
});
|
||||
@@ -2169,7 +2207,7 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
it('renders and navigates to the new HTML XBlock editor after xblock duplicating', async () => {
|
||||
const { getByTitle } = render(<RootWrapper />);
|
||||
render(<RootWrapper />);
|
||||
const updatedCourseVerticalChildrenMock = JSON.parse(JSON.stringify(courseVerticalChildrenMock));
|
||||
const targetBlockId = updatedCourseVerticalChildrenMock.children[1].block_id;
|
||||
|
||||
@@ -2196,7 +2234,7 @@ describe('<CourseUnit />', () => {
|
||||
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,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
ActionRow, Button, StandardModal, useToggle,
|
||||
} 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,8 @@ 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 = ({
|
||||
parentLocator,
|
||||
@@ -24,7 +27,6 @@ const AddComponent = ({
|
||||
addComponentTemplateData,
|
||||
handleCreateNewCourseXBlock,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const intl = useIntl();
|
||||
const [isOpenAdvanced, openAdvanced, closeAdvanced] = useToggle(false);
|
||||
const [isOpenHtml, openHtml, closeHtml] = useToggle(false);
|
||||
@@ -32,10 +34,17 @@ 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);
|
||||
const [courseId, setCourseId] = useState(null);
|
||||
const [newBlockId, setNewBlockId] = useState(null);
|
||||
const [isSelectLibraryContentModalOpen, showSelectLibraryContentModal, closeSelectLibraryContentModal] = useToggle();
|
||||
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) {
|
||||
@@ -54,6 +63,12 @@ const AddComponent = ({
|
||||
closeSelectLibraryContentModal();
|
||||
}, [selectedComponents]);
|
||||
|
||||
const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
|
||||
closeXBlockEditorModal();
|
||||
closeVideoSelectorModal();
|
||||
sendMessageToIframe(messageTypes.refreshXBlock, null);
|
||||
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe]);
|
||||
|
||||
const handleLibraryV2Selection = useCallback((selection) => {
|
||||
handleCreateNewCourseXBlock({
|
||||
type: COMPONENT_TYPES.libraryV2,
|
||||
@@ -71,12 +86,28 @@ const AddComponent = ({
|
||||
handleCreateNewCourseXBlock({ type, parentLocator: blockId });
|
||||
break;
|
||||
case COMPONENT_TYPES.problem:
|
||||
case COMPONENT_TYPES.video:
|
||||
handleCreateNewCourseXBlock({ type, parentLocator: blockId }, ({ courseKey, locator }) => {
|
||||
localStorage.setItem('createXBlockLastYPosition', window.scrollY);
|
||||
navigate(`/course/${courseKey}/editor/${type}/${locator}`);
|
||||
setCourseId(courseKey);
|
||||
setBlockType(type);
|
||||
setNewBlockId(locator);
|
||||
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:
|
||||
@@ -99,9 +130,11 @@ const AddComponent = ({
|
||||
type,
|
||||
boilerplate: moduleName,
|
||||
parentLocator: blockId,
|
||||
}, ({ courseKey, locator }) => {
|
||||
localStorage.setItem('createXBlockLastYPosition', window.scrollY);
|
||||
navigate(`/course/${courseKey}/editor/html/${locator}`);
|
||||
}, /* istanbul ignore next */ ({ courseKey, locator }) => {
|
||||
setCourseId(courseKey);
|
||||
setBlockType(type);
|
||||
setNewBlockId(locator);
|
||||
showXBlockEditorModal();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
@@ -201,6 +234,43 @@ const AddComponent = ({
|
||||
onChangeComponentSelection={setSelectedComponents}
|
||||
/>
|
||||
</StandardModal>
|
||||
<StandardModal
|
||||
title={intl.formatMessage(messages.videoPickerModalTitle)}
|
||||
isOpen={isVideoSelectorModalOpen}
|
||||
onClose={closeVideoSelectorModal}
|
||||
isOverflowVisible={false}
|
||||
size="xl"
|
||||
>
|
||||
<div className="selector-page">
|
||||
<VideoSelectorPage
|
||||
blockId={newBlockId}
|
||||
courseId={courseId}
|
||||
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
|
||||
lmsEndpointUrl={getConfig().LMS_BASE_URL}
|
||||
onCancel={closeVideoSelectorModal}
|
||||
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
|
||||
/>
|
||||
</div>
|
||||
</StandardModal>
|
||||
<StandardModal
|
||||
title={intl.formatMessage(messages.blockEditorModalTitle)}
|
||||
isOpen={isXBlockEditorModalOpen}
|
||||
onClose={closeXBlockEditorModal}
|
||||
isOverflowVisible={false}
|
||||
size="xl"
|
||||
>
|
||||
<div className="editor-page">
|
||||
<EditorPage
|
||||
courseId={courseId}
|
||||
blockType={blockType}
|
||||
blockId={newBlockId}
|
||||
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
|
||||
lmsEndpointUrl={getConfig().LMS_BASE_URL}
|
||||
onClose={closeXBlockEditorModal}
|
||||
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
|
||||
/>
|
||||
</div>
|
||||
</StandardModal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,16 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Add selected components',
|
||||
description: 'Problem bank component add button text.',
|
||||
},
|
||||
videoPickerModalTitle: {
|
||||
id: 'course-authoring.course-unit.modal.video-title.text',
|
||||
defaultMessage: 'Select video',
|
||||
description: 'Video picker modal title.',
|
||||
},
|
||||
blockEditorModalTitle: {
|
||||
id: 'course-authoring.course-unit.modal.block-editor-title.text',
|
||||
defaultMessage: 'Edit component',
|
||||
description: 'Block editor modal title.',
|
||||
},
|
||||
modalContainerTitle: {
|
||||
id: 'course-authoring.course-unit.modal.container.title',
|
||||
defaultMessage: 'Add {componentTitle} component',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export type UseMessageHandlersTypes = {
|
||||
courseId: string;
|
||||
navigate: (path: string) => void;
|
||||
dispatch: (action: any) => void;
|
||||
setIframeOffset: (height: number) => 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;
|
||||
handleCloseLegacyEditorXBlockModal: () => void;
|
||||
@@ -14,7 +14,6 @@ export type UseMessageHandlersTypes = {
|
||||
handleOpenManageTagsModal: (id: string) => void;
|
||||
handleShowProcessingNotification: (variant: string) => void;
|
||||
handleHideProcessingNotification: () => void;
|
||||
handleRedirectToXBlockEditPage: (payload: { type: string, locator: string }) => void;
|
||||
};
|
||||
|
||||
export type MessageHandlersTypes = Record<string, (payload: any) => void>;
|
||||
|
||||
@@ -16,7 +16,6 @@ import { MessageHandlersTypes, UseMessageHandlersTypes } from './types';
|
||||
*/
|
||||
export const useMessageHandlers = ({
|
||||
courseId,
|
||||
navigate,
|
||||
dispatch,
|
||||
setIframeOffset,
|
||||
handleDeleteXBlock,
|
||||
@@ -30,15 +29,15 @@ export const useMessageHandlers = ({
|
||||
handleOpenManageTagsModal,
|
||||
handleShowProcessingNotification,
|
||||
handleHideProcessingNotification,
|
||||
handleRedirectToXBlockEditPage,
|
||||
handleEditXBlock,
|
||||
}: UseMessageHandlersTypes): MessageHandlersTypes => {
|
||||
const { copyToClipboard } = useClipboard();
|
||||
|
||||
return useMemo(() => ({
|
||||
[messageTypes.copyXBlock]: ({ usageId }) => copyToClipboard(usageId),
|
||||
[messageTypes.deleteXBlock]: ({ usageId }) => handleDeleteXBlock(usageId),
|
||||
[messageTypes.newXBlockEditor]: ({ blockType, usageId }) => navigate(`/course/${courseId}/editor/${blockType}/${usageId}`),
|
||||
[messageTypes.duplicateXBlock]: ({ blockType, usageId }) => handleDuplicateXBlock(blockType, usageId),
|
||||
[messageTypes.newXBlockEditor]: ({ blockType, usageId }) => handleEditXBlock(blockType, usageId),
|
||||
[messageTypes.duplicateXBlock]: ({ usageId }) => handleDuplicateXBlock(usageId),
|
||||
[messageTypes.manageXBlockAccess]: ({ usageId }) => handleManageXBlockAccess(usageId),
|
||||
[messageTypes.scrollToXBlock]: debounce(({ scrollOffset }) => handleScrollToXBlock(scrollOffset), 1000),
|
||||
[messageTypes.toggleCourseXBlockDropdown]: ({
|
||||
@@ -52,9 +51,14 @@ export const useMessageHandlers = ({
|
||||
[messageTypes.openManageTags]: (payload) => handleOpenManageTagsModal(payload.contentId),
|
||||
[messageTypes.addNewComponent]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.adding),
|
||||
[messageTypes.pasteNewComponent]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.pasting),
|
||||
[messageTypes.copyXBlockLegacy]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.copying),
|
||||
[messageTypes.copyXBlockLegacy]: /* istanbul ignore next */ () => handleShowProcessingNotification(
|
||||
NOTIFICATION_MESSAGES.copying,
|
||||
),
|
||||
[messageTypes.hideProcessingNotification]: handleHideProcessingNotification,
|
||||
[messageTypes.handleRedirectToXBlockEditPage]: (payload) => handleRedirectToXBlockEditPage(payload),
|
||||
[messageTypes.handleRedirectToXBlockEditPage]: /* istanbul ignore next */ (payload) => handleEditXBlock(
|
||||
payload.type,
|
||||
payload.locator,
|
||||
),
|
||||
}), [
|
||||
courseId,
|
||||
handleDeleteXBlock,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import {
|
||||
FC, useEffect, useState, useMemo, useCallback,
|
||||
} from 'react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useToggle, Sheet } from '@openedx/paragon';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useToggle, Sheet, StandardModal } from '@openedx/paragon';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
hideProcessingNotification,
|
||||
@@ -13,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,
|
||||
@@ -35,16 +35,22 @@ 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<XBlockContainerIframeProps> = ({
|
||||
courseId, blockId, unitXBlockActions, courseVerticalChildren, handleConfigureSubmit, isUnitVerticalType,
|
||||
}) => {
|
||||
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<string>('');
|
||||
const { useVideoGalleryFlow } = useSelector(getWaffleFlags);
|
||||
const [newBlockId, setNewBlockId] = useState<string>('');
|
||||
const [accessManagedXBlockData, setAccessManagedXBlockData] = useState<AccessManagedXBlockDataTypes | {}>({});
|
||||
const [iframeOffset, setIframeOffset] = useState(0);
|
||||
const [deleteXBlockId, setDeleteXBlockId] = useState<string | null>(null);
|
||||
@@ -64,14 +70,27 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
setIframeRef(iframeRef);
|
||||
}, [setIframeRef]);
|
||||
|
||||
const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
|
||||
closeXBlockEditorModal();
|
||||
closeVideoSelectorModal();
|
||||
sendMessageToIframe(messageTypes.refreshXBlock, null);
|
||||
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe]);
|
||||
|
||||
const handleEditXBlock = useCallback((type: string, id: string) => {
|
||||
setBlockType(type);
|
||||
setNewBlockId(id);
|
||||
if (type === 'video' && useVideoGalleryFlow) {
|
||||
showVideoSelectorModal();
|
||||
} else {
|
||||
showXBlockEditorModal();
|
||||
}
|
||||
}, [showVideoSelectorModal, showXBlockEditorModal]);
|
||||
|
||||
const handleDuplicateXBlock = useCallback(
|
||||
(blockType: string, usageId: string) => {
|
||||
(usageId: string) => {
|
||||
unitXBlockActions.handleDuplicate(usageId);
|
||||
if (supportedEditors[blockType]) {
|
||||
navigate(`/course/${courseId}/editor/${blockType}/${usageId}`);
|
||||
}
|
||||
},
|
||||
[unitXBlockActions, courseId, navigate],
|
||||
[unitXBlockActions, courseId],
|
||||
);
|
||||
|
||||
const handleDeleteXBlock = (usageId: string) => {
|
||||
@@ -147,13 +166,8 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
dispatch(hideProcessingNotification());
|
||||
};
|
||||
|
||||
const handleRedirectToXBlockEditPage = (payload: { type: string, locator: string }) => {
|
||||
navigate(`/course/${courseId}/editor/${payload.type}/${payload.locator}`);
|
||||
};
|
||||
|
||||
const messageHandlers = useMessageHandlers({
|
||||
courseId,
|
||||
navigate,
|
||||
dispatch,
|
||||
setIframeOffset,
|
||||
handleDeleteXBlock,
|
||||
@@ -167,7 +181,7 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
handleOpenManageTagsModal,
|
||||
handleShowProcessingNotification,
|
||||
handleHideProcessingNotification,
|
||||
handleRedirectToXBlockEditPage,
|
||||
handleEditXBlock,
|
||||
});
|
||||
|
||||
useIframeMessages(messageHandlers);
|
||||
@@ -186,6 +200,43 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
close={closeDeleteModal}
|
||||
onDeleteSubmit={onDeleteSubmit}
|
||||
/>
|
||||
<StandardModal
|
||||
title={intl.formatMessage(messages.videoPickerModalTitle)}
|
||||
isOpen={isVideoSelectorModalOpen}
|
||||
onClose={closeVideoSelectorModal}
|
||||
isOverflowVisible={false}
|
||||
size="xl"
|
||||
>
|
||||
<div className="selector-page">
|
||||
<VideoSelectorPage
|
||||
blockId={newBlockId}
|
||||
courseId={courseId}
|
||||
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
|
||||
lmsEndpointUrl={getConfig().LMS_BASE_URL}
|
||||
onCancel={closeVideoSelectorModal}
|
||||
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
|
||||
/>
|
||||
</div>
|
||||
</StandardModal>
|
||||
<StandardModal
|
||||
title={intl.formatMessage(messages.blockEditorModalTitle)}
|
||||
isOpen={isXBlockEditorModalOpen}
|
||||
onClose={closeXBlockEditorModal}
|
||||
isOverflowVisible={false}
|
||||
size="xl"
|
||||
>
|
||||
<div className="editor-page">
|
||||
<EditorPage
|
||||
courseId={courseId}
|
||||
blockType={blockType}
|
||||
blockId={newBlockId}
|
||||
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
|
||||
lmsEndpointUrl={getConfig().LMS_BASE_URL}
|
||||
onClose={closeXBlockEditorModal}
|
||||
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
|
||||
/>
|
||||
</div>
|
||||
</StandardModal>
|
||||
{Object.keys(accessManagedXBlockData).length ? (
|
||||
<ConfigureModal
|
||||
isXBlockComponent
|
||||
|
||||
@@ -15,6 +15,15 @@ const messages = defineMessages({
|
||||
id: 'course-authoring.course-unit.xblock.iframe.label',
|
||||
defaultMessage: '{xblockCount} xBlocks inside the frame',
|
||||
},
|
||||
videoPickerModalTitle: {
|
||||
id: 'course-authoring.course-unit.xblock.video-editor.title',
|
||||
defaultMessage: 'Select video',
|
||||
},
|
||||
blockEditorModalTitle: {
|
||||
id: 'course-authoring.course-unit.xblock.editor.title',
|
||||
defaultMessage: 'Edit component',
|
||||
description: 'Block editor modal title.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -26,6 +26,7 @@ const slice = createSlice({
|
||||
useNewCertificatesPage: true,
|
||||
useNewTextbooksPage: true,
|
||||
useNewGroupConfigurationsPage: true,
|
||||
useVideoGalleryFlow: false,
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
|
||||
@@ -7,7 +7,6 @@ import * as hooks from './hooks';
|
||||
|
||||
import supportedEditors from './supportedEditors';
|
||||
import type { EditorComponent } from './EditorComponent';
|
||||
import { useEditorContext } from './EditorContext';
|
||||
import AdvancedEditor from './AdvancedEditor';
|
||||
|
||||
export interface Props extends EditorComponent {
|
||||
@@ -17,7 +16,6 @@ export interface Props extends EditorComponent {
|
||||
learningContextId: string | null;
|
||||
lmsEndpointUrl: string | null;
|
||||
studioEndpointUrl: string | null;
|
||||
fullScreen?: boolean; // eslint-disable-line react/no-unused-prop-types
|
||||
}
|
||||
|
||||
const Editor: React.FC<Props> = ({
|
||||
@@ -42,7 +40,6 @@ const Editor: React.FC<Props> = ({
|
||||
studioEndpointUrl,
|
||||
},
|
||||
});
|
||||
const { fullScreen } = useEditorContext();
|
||||
|
||||
const EditorComponent = supportedEditors[blockType];
|
||||
|
||||
@@ -60,24 +57,7 @@ const Editor: React.FC<Props> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const innerEditor = <EditorComponent {...{ onClose, returnFunction }} />;
|
||||
|
||||
if (fullScreen) {
|
||||
return (
|
||||
<div
|
||||
className="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
className="pgn__modal-fullscreen h-100"
|
||||
role="dialog"
|
||||
aria-label={blockType}
|
||||
>
|
||||
{innerEditor}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return innerEditor;
|
||||
return <EditorComponent {...{ onClose, returnFunction }} />;
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
|
||||
@@ -7,14 +7,6 @@ import React from 'react';
|
||||
*/
|
||||
export interface EditorContext {
|
||||
learningContextId: string;
|
||||
/**
|
||||
* When editing components in the libraries part of the Authoring MFE, we show
|
||||
* the editors in a modal (fullScreen = false). This is the preferred approach
|
||||
* so that authors can see context behind the modal.
|
||||
* However, when making edits from the legacy course view, we display the
|
||||
* editors in a fullscreen view. This approach is deprecated.
|
||||
*/
|
||||
fullScreen: boolean;
|
||||
}
|
||||
|
||||
const context = React.createContext<EditorContext | undefined>(undefined);
|
||||
@@ -32,7 +24,6 @@ export function useEditorContext() {
|
||||
export const EditorContextProvider: React.FC<{
|
||||
children: React.ReactNode,
|
||||
learningContextId: string;
|
||||
fullScreen: boolean;
|
||||
}> = ({ children, ...contextData }) => {
|
||||
const ctx: EditorContext = React.useMemo(() => ({ ...contextData }), []);
|
||||
return <context.Provider value={ctx}>{children}</context.Provider>;
|
||||
|
||||
@@ -37,7 +37,6 @@ const defaultPropsHtml = {
|
||||
lmsEndpointUrl: 'http://lms.test.none/',
|
||||
studioEndpointUrl: 'http://cms.test.none/',
|
||||
onClose: jest.fn(),
|
||||
fullScreen: false,
|
||||
};
|
||||
const fieldsHtml = {
|
||||
displayName: 'Introduction to Testing',
|
||||
@@ -66,22 +65,6 @@ describe('EditorPage', () => {
|
||||
expect(modalElement.classList).not.toContain('pgn__modal-fullscreen');
|
||||
});
|
||||
|
||||
test('it can display the Text (html) editor as a full page (when coming from the legacy UI)', async () => {
|
||||
jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => (
|
||||
{ status: 200, data: snakeCaseObject(fieldsHtml) }
|
||||
));
|
||||
|
||||
render(<EditorPage {...defaultPropsHtml} fullScreen />);
|
||||
|
||||
// Then the editor should open
|
||||
expect(await screen.findByRole('heading', { name: /Introduction to Testing/ })).toBeInTheDocument();
|
||||
|
||||
const modalElement = screen.getByRole('dialog');
|
||||
expect(modalElement.classList).toContain('pgn__modal-fullscreen');
|
||||
expect(modalElement.classList).not.toContain('pgn__modal');
|
||||
expect(modalElement.classList).not.toContain('pgn__modal-xl');
|
||||
});
|
||||
|
||||
test('it shows the Advanced Editor if there is no corresponding editor', async () => {
|
||||
jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => ( // eslint-disable-next-line
|
||||
{ status: 200, data: { display_name: 'Fake Un-editable Block', category: 'fake', metadata: {}, data: '' } }
|
||||
|
||||
@@ -14,7 +14,6 @@ interface Props extends EditorComponent {
|
||||
isMarkdownEditorEnabledForCourse?: boolean;
|
||||
lmsEndpointUrl?: string;
|
||||
studioEndpointUrl?: string;
|
||||
fullScreen?: boolean;
|
||||
children?: never;
|
||||
}
|
||||
|
||||
@@ -31,7 +30,6 @@ const EditorPage: React.FC<Props> = ({
|
||||
studioEndpointUrl = null,
|
||||
onClose = null,
|
||||
returnFunction = null,
|
||||
fullScreen = true,
|
||||
}) => (
|
||||
<Provider store={store}>
|
||||
<ErrorBoundary
|
||||
@@ -40,7 +38,7 @@ const EditorPage: React.FC<Props> = ({
|
||||
studioEndpointUrl,
|
||||
}}
|
||||
>
|
||||
<EditorContextProvider fullScreen={fullScreen} learningContextId={courseId}>
|
||||
<EditorContextProvider learningContextId={courseId}>
|
||||
<Editor
|
||||
{...{
|
||||
onClose,
|
||||
|
||||
@@ -9,6 +9,8 @@ const VideoSelector = ({
|
||||
learningContextId,
|
||||
lmsEndpointUrl,
|
||||
studioEndpointUrl,
|
||||
returnFunction,
|
||||
onCancel,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const loading = hooks.useInitializeApp({
|
||||
@@ -26,7 +28,7 @@ const VideoSelector = ({
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<VideoGallery />
|
||||
<VideoGallery returnFunction={returnFunction} onCancel={onCancel} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -10,6 +10,8 @@ const VideoSelectorPage = ({
|
||||
courseId,
|
||||
lmsEndpointUrl,
|
||||
studioEndpointUrl,
|
||||
returnFunction,
|
||||
onCancel,
|
||||
}) => (
|
||||
<Provider store={store}>
|
||||
<ErrorBoundary
|
||||
@@ -24,6 +26,8 @@ const VideoSelectorPage = ({
|
||||
learningContextId: courseId,
|
||||
lmsEndpointUrl,
|
||||
studioEndpointUrl,
|
||||
returnFunction,
|
||||
onCancel,
|
||||
}}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
@@ -42,6 +46,8 @@ VideoSelectorPage.propTypes = {
|
||||
courseId: PropTypes.string,
|
||||
lmsEndpointUrl: PropTypes.string,
|
||||
studioEndpointUrl: PropTypes.string,
|
||||
returnFunction: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
};
|
||||
|
||||
export default VideoSelectorPage;
|
||||
|
||||
@@ -32,7 +32,6 @@ const defaultPropsHtml = {
|
||||
lmsEndpointUrl: 'http://lms.test.none/',
|
||||
studioEndpointUrl: 'http://cms.test.none/',
|
||||
onClose: jest.fn(),
|
||||
fullScreen: false,
|
||||
};
|
||||
const fieldsHtml = {
|
||||
displayName: 'Introduction to Testing',
|
||||
|
||||
@@ -14,7 +14,6 @@ import { Close } from '@openedx/paragon/icons';
|
||||
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { EditorComponent } from '../../EditorComponent';
|
||||
import { useEditorContext } from '../../EditorContext';
|
||||
import TitleHeader from './components/TitleHeader';
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
@@ -30,37 +29,18 @@ interface WrapperProps {
|
||||
}
|
||||
|
||||
export const EditorModalWrapper: React.FC<WrapperProps & { onClose: () => void }> = ({ children, onClose }) => {
|
||||
const { fullScreen } = useEditorContext();
|
||||
const intl = useIntl();
|
||||
if (fullScreen) {
|
||||
return (
|
||||
<div
|
||||
className="editor-container d-flex flex-column position-relative zindex-0"
|
||||
style={{ minHeight: '100%' }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const title = intl.formatMessage(messages.modalTitle);
|
||||
return (
|
||||
<ModalDialog isOpen size="xl" isOverflowVisible={false} onClose={onClose} title={title}>{children}</ModalDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export const EditorModalBody: React.FC<WrapperProps> = ({ children }) => {
|
||||
const { fullScreen } = useEditorContext();
|
||||
return <ModalDialog.Body className={fullScreen ? 'pb-6' : 'pb-0'}>{ children }</ModalDialog.Body>;
|
||||
};
|
||||
export const EditorModalBody: React.FC<WrapperProps> = ({ children }) => <ModalDialog.Body className="pb-0">{ children }</ModalDialog.Body>;
|
||||
|
||||
export const FooterWrapper: React.FC<WrapperProps> = ({ children }) => {
|
||||
const { fullScreen } = useEditorContext();
|
||||
if (fullScreen) {
|
||||
return <div className="editor-footer fixed-bottom">{children}</div>;
|
||||
}
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{ children }</>;
|
||||
};
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
export const FooterWrapper: React.FC<WrapperProps> = ({ children }) => <>{ children }</>;
|
||||
|
||||
interface Props extends EditorComponent {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('SelectTypeModal', () => {
|
||||
jest.spyOn(hooks, 'onSelect').mockImplementation(mockSelect);
|
||||
// This is a new-style test, unlike most of the old snapshot-based editor tests.
|
||||
render(
|
||||
<EditorContextProvider fullScreen={false} learningContextId="course-v1:Org+COURSE+RUN">
|
||||
<EditorContextProvider learningContextId="course-v1:Org+COURSE+RUN">
|
||||
<Provider store={editorStore}>
|
||||
<SelectTypeModal onClose={mockClose} />
|
||||
</Provider>
|
||||
|
||||
@@ -18,6 +18,7 @@ exports[`VideoEditor snapshots renders as expected with default behavior 1`] = `
|
||||
"useSelector": [MockFunction],
|
||||
}
|
||||
}
|
||||
onClose={[MockFunction props.onClose]}
|
||||
/>
|
||||
</div>
|
||||
</EditorContainer>
|
||||
|
||||
@@ -7,7 +7,9 @@ import VideoSettingsModal from './VideoSettingsModal';
|
||||
import { RequestKeys } from '../../../data/constants/requests';
|
||||
|
||||
interface Props {
|
||||
onReturn?: (() => void);
|
||||
isLibrary: boolean;
|
||||
onClose?: (() => void) | null;
|
||||
}
|
||||
|
||||
export const {
|
||||
@@ -27,13 +29,15 @@ export const hooks = {
|
||||
|
||||
const VideoEditorModal: React.FC<Props> = ({
|
||||
isLibrary,
|
||||
onClose,
|
||||
onReturn,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const location = useLocation();
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const selectedVideoId = searchParams.get('selectedVideoId');
|
||||
const selectedVideoUrl = searchParams.get('selectedVideoUrl');
|
||||
const onReturn = hooks.useReturnToGallery();
|
||||
const onSettingsReturn = onReturn || hooks.useReturnToGallery();
|
||||
const isLoaded = useSelector(
|
||||
(state) => selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchVideos }),
|
||||
);
|
||||
@@ -44,8 +48,9 @@ const VideoEditorModal: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<VideoSettingsModal {...{
|
||||
onReturn,
|
||||
onReturn: onSettingsReturn,
|
||||
isLibrary,
|
||||
onClose,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -20,11 +20,13 @@ import messages from '../../messages';
|
||||
interface Props {
|
||||
onReturn: () => void;
|
||||
isLibrary: boolean;
|
||||
onClose?: (() => void) | null;
|
||||
}
|
||||
|
||||
const VideoSettingsModal: React.FC<Props> = ({
|
||||
onReturn,
|
||||
isLibrary,
|
||||
onClose,
|
||||
}) => (
|
||||
<>
|
||||
{!isLibrary && (
|
||||
@@ -32,7 +34,7 @@ const VideoSettingsModal: React.FC<Props> = ({
|
||||
variant="link"
|
||||
className="text-primary-500"
|
||||
size="sm"
|
||||
onClick={onReturn}
|
||||
onClick={onClose || onReturn}
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
marginLeft: '3px',
|
||||
|
||||
@@ -39,7 +39,7 @@ const VideoEditor: React.FC<EditorComponent> = ({
|
||||
>
|
||||
{(isCreateWorkflow || studioViewFinished) ? (
|
||||
<div className="video-editor">
|
||||
<VideoEditorModal {...{ isLibrary }} />
|
||||
<VideoEditorModal {...{ isLibrary, onClose, returnFunction }} />
|
||||
</div>
|
||||
) : (
|
||||
<div style={{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = () => {
|
||||
<SelectionModal
|
||||
{...{
|
||||
isOpen: true,
|
||||
close: handleCancel,
|
||||
size: 'fullscreen',
|
||||
close: onCancel || handleCancel,
|
||||
size: 'xl',
|
||||
isFullscreenScroll: false,
|
||||
galleryError,
|
||||
inputError,
|
||||
@@ -79,10 +100,34 @@ const VideoGallery = () => {
|
||||
isFetchError,
|
||||
}}
|
||||
/>
|
||||
<StandardModal
|
||||
title={intl.formatMessage(messages.videoUploadModalTitle)}
|
||||
isOpen={isVideoUploadModalOpen}
|
||||
onClose={closeVideoUploadModal}
|
||||
isOverflowVisible={false}
|
||||
size="xl"
|
||||
hasCloseButton={false}
|
||||
>
|
||||
<div className="editor-page">
|
||||
<VideoUploadEditor
|
||||
onUpload={onVideoUpload}
|
||||
onClose={closeVideoUploadModal}
|
||||
/>
|
||||
</div>
|
||||
</StandardModal>
|
||||
{isVideoEditorModalOpen && (
|
||||
<VideoEditor
|
||||
onClose={closeVideoEditorModal}
|
||||
returnFunction={returnFunction}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
VideoGallery.propTypes = {};
|
||||
VideoGallery.propTypes = {
|
||||
onCancel: PropTypes.func,
|
||||
returnFunction: PropTypes.func,
|
||||
};
|
||||
|
||||
export default VideoGallery;
|
||||
|
||||
@@ -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' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 (
|
||||
<div className="d-flex flex-column">
|
||||
@@ -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 }) => {
|
||||
<Dropzone
|
||||
accept={{ 'video/*': ['.mp4', '.mov'] }}
|
||||
onProcessUpload={handleProcessUpload}
|
||||
inputComponent={<URLUploader />}
|
||||
inputComponent={<URLUploader onUpload={onUpload} />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
URLUploader.propTypes = {
|
||||
onUpload: PropTypes.func,
|
||||
};
|
||||
|
||||
VideoUploader.propTypes = {
|
||||
setLoading: PropTypes.func.isRequired,
|
||||
onUpload: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
export default VideoUploader;
|
||||
|
||||
@@ -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 ({
|
||||
|
||||
@@ -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) ? (
|
||||
<div className="d-flex marked-area flex-column p-3">
|
||||
<VideoUploader setLoading={setLoading} />
|
||||
<VideoUploader onUpload={onUpload} onClose={onClose} setLoading={setLoading} />
|
||||
</div>
|
||||
) : (
|
||||
<div style={{
|
||||
@@ -30,4 +31,9 @@ const VideoUploadEditor = () => {
|
||||
);
|
||||
};
|
||||
|
||||
VideoUploadEditor.propTypes = {
|
||||
onUpload: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
export default VideoUploadEditor;
|
||||
|
||||
@@ -41,7 +41,6 @@ export const ComponentEditorModal: React.FC<Record<never, never>> = () => {
|
||||
lmsEndpointUrl={getConfig().LMS_BASE_URL}
|
||||
onClose={onClose}
|
||||
returnFunction={() => onClose}
|
||||
fullScreen={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user