Compare commits
9 Commits
release/te
...
release/te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14e122a672 | ||
|
|
f459f53343 | ||
|
|
a5a7d03d12 | ||
|
|
41fc478efe | ||
|
|
06497bf85c | ||
|
|
7e0b7f94e8 | ||
|
|
4bc34c268b | ||
|
|
2973614e3b | ||
|
|
bdc99fddc3 |
@@ -82,7 +82,7 @@ describe('<CourseLibraries />', () => {
|
||||
expect(reviewTab).toHaveAttribute('aria-selected', 'true');
|
||||
|
||||
userEvent.click(allTab);
|
||||
const alert = await screen.findByRole('alert');
|
||||
const alert = (await screen.findAllByRole('alert'))[0];
|
||||
expect(await within(alert).findByText(
|
||||
'5 library components are out of sync. Review updates to accept or ignore changes',
|
||||
)).toBeInTheDocument();
|
||||
@@ -105,7 +105,7 @@ describe('<CourseLibraries />', () => {
|
||||
userEvent.click(allTab);
|
||||
expect(allTab).toHaveAttribute('aria-selected', 'true');
|
||||
|
||||
const alert = await screen.findByRole('alert');
|
||||
const alert = (await screen.findAllByRole('alert'))[0];
|
||||
expect(await within(alert).findByText(
|
||||
'5 library components are out of sync. Review updates to accept or ignore changes',
|
||||
)).toBeInTheDocument();
|
||||
@@ -133,7 +133,7 @@ describe('<CourseLibraries />', () => {
|
||||
expect(reviewTab).toHaveAttribute('aria-selected', 'true');
|
||||
|
||||
userEvent.click(allTab);
|
||||
const alert = await screen.findByRole('alert');
|
||||
const alert = (await screen.findAllByRole('alert'))[0];
|
||||
expect(await within(alert).findByText(
|
||||
'5 library components are out of sync. Review updates to accept or ignore changes',
|
||||
)).toBeInTheDocument();
|
||||
@@ -156,7 +156,7 @@ describe('<CourseLibraries />', () => {
|
||||
|
||||
screen.logTestingPlaygroundURL();
|
||||
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
expect(screen.queryAllByRole('alert').length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
Tabs,
|
||||
} from '@openedx/paragon';
|
||||
import {
|
||||
Cached, CheckCircle, Launch, Loop,
|
||||
Cached, CheckCircle, Launch, Loop, Info,
|
||||
} from '@openedx/paragon/icons';
|
||||
|
||||
import sumBy from 'lodash/sumBy';
|
||||
@@ -33,6 +33,7 @@ import { useStudioHome } from '../studio-home/hooks';
|
||||
import NewsstandIcon from '../generic/NewsstandIcon';
|
||||
import ReviewTabContent from './ReviewTabContent';
|
||||
import { OutOfSyncAlert } from './OutOfSyncAlert';
|
||||
import AlertMessage from '../generic/alert-message';
|
||||
|
||||
interface Props {
|
||||
courseId: string;
|
||||
@@ -199,6 +200,12 @@ export const CourseLibraries: React.FC<Props> = ({ courseId }) => {
|
||||
showAlert={showReviewAlert && tabKey === CourseLibraryTabs.all}
|
||||
setShowAlert={setShowReviewAlert}
|
||||
/>
|
||||
{ /* TODO: Remove this alert after implement container in this page */}
|
||||
<AlertMessage
|
||||
title={intl.formatMessage(messages.unitsUpdatesWarning)}
|
||||
icon={Info}
|
||||
variant="info"
|
||||
/>
|
||||
<SubHeader
|
||||
title={intl.formatMessage(messages.headingTitle)}
|
||||
subtitle={intl.formatMessage(messages.headingSubtitle)}
|
||||
|
||||
@@ -116,6 +116,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Something went wrong! Could not fetch results.',
|
||||
description: 'Generic error message displayed when fetching link data fails.',
|
||||
},
|
||||
unitsUpdatesWarning: {
|
||||
id: 'course-authoring.course-libraries.home-tab.warning.units',
|
||||
defaultMessage: 'Currently this page only tracks component updates. To check for unit updates, go to your Course Outline.',
|
||||
description: 'Warning message shown in library sync page about units updates.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @ts-check
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
Hyperlink,
|
||||
Icon,
|
||||
IconButton,
|
||||
IconButtonWithTooltip,
|
||||
useToggle,
|
||||
} from '@openedx/paragon';
|
||||
import {
|
||||
@@ -133,19 +135,24 @@ const CardHeader = ({
|
||||
) : (
|
||||
<>
|
||||
{titleComponent}
|
||||
{readyToSync && (
|
||||
<IconButton
|
||||
className="item-card-button-icon"
|
||||
data-testid={`${namePrefix}-sync-button`}
|
||||
alt={intl.formatMessage(messages.readyToSyncButtonAlt)}
|
||||
iconAs={SyncIcon}
|
||||
onClick={onClickSync}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
className="item-card-button-icon"
|
||||
<IconButtonWithTooltip
|
||||
className={classNames(
|
||||
'item-card-button-icon',
|
||||
{
|
||||
'item-card-button-icon-disabled': isDisabledEditField,
|
||||
},
|
||||
)}
|
||||
data-testid={`${namePrefix}-edit-button`}
|
||||
alt={intl.formatMessage(messages.altButtonEdit)}
|
||||
alt={intl.formatMessage(
|
||||
isDisabledEditField ? messages.cannotEditTooltip : messages.altButtonRename,
|
||||
)}
|
||||
tooltipContent={(
|
||||
<div>
|
||||
{intl.formatMessage(
|
||||
isDisabledEditField ? messages.cannotEditTooltip : messages.altButtonRename,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
iconAs={EditIcon}
|
||||
onClick={onClickEdit}
|
||||
// @ts-ignore
|
||||
@@ -161,6 +168,15 @@ const CardHeader = ({
|
||||
<TagCount count={contentTagCount} onClick={openManageTagsDrawer} />
|
||||
)}
|
||||
{extraActionsComponent}
|
||||
{readyToSync && (
|
||||
<IconButtonWithTooltip
|
||||
data-testid={`${namePrefix}-sync-button`}
|
||||
alt={intl.formatMessage(messages.readyToSyncButtonAlt)}
|
||||
iconAs={SyncIcon}
|
||||
tooltipContent={<div>{intl.formatMessage(messages.readyToSyncButtonAlt)}</div>}
|
||||
onClick={onClickSync}
|
||||
/>
|
||||
)}
|
||||
<Dropdown data-testid={`${namePrefix}-card-header__menu`} onClick={onClickMenuButton}>
|
||||
<Dropdown.Toggle
|
||||
className="item-card-header__menu"
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
&:hover {
|
||||
.item-card-button-icon {
|
||||
opacity: 1;
|
||||
|
||||
&.item-card-button-icon-disabled {
|
||||
pointer-events: all;
|
||||
opacity: .5;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ const messages = defineMessages({
|
||||
id: 'course-authoring.course-outline.card.status-badge.draft-unpublished-changes',
|
||||
defaultMessage: 'Draft (Unpublished changes)',
|
||||
},
|
||||
altButtonEdit: {
|
||||
altButtonRename: {
|
||||
id: 'course-authoring.course-outline.card.button.edit.alt',
|
||||
defaultMessage: 'Edit',
|
||||
defaultMessage: 'Rename',
|
||||
},
|
||||
menuPublish: {
|
||||
id: 'course-authoring.course-outline.card.menu.publish',
|
||||
@@ -82,6 +82,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Update available - click to sync',
|
||||
description: 'Alt text for the sync icon button.',
|
||||
},
|
||||
cannotEditTooltip: {
|
||||
id: 'course-authoring.course-outline.card.button.edit.disable.tooltip',
|
||||
defaultMessage: 'This object was added from a library, so it cannot be edited.',
|
||||
description: 'Tooltip text of button when the object was added from a library.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -176,7 +176,7 @@ describe('<UnitCard />', () => {
|
||||
|
||||
// Should open compare preview modal
|
||||
expect(screen.getByRole('heading', { name: /preview changes: unit name/i })).toBeInTheDocument();
|
||||
expect(screen.getByText('Preview not available')).toBeInTheDocument();
|
||||
expect(screen.getByText('Preview not available for unit changes at this time')).toBeInTheDocument();
|
||||
|
||||
// Click on accept changes
|
||||
const acceptChangesButton = screen.getByText(/accept changes/i);
|
||||
@@ -196,7 +196,7 @@ describe('<UnitCard />', () => {
|
||||
|
||||
// Should open compare preview modal
|
||||
expect(screen.getByRole('heading', { name: /preview changes: unit name/i })).toBeInTheDocument();
|
||||
expect(screen.getByText('Preview not available')).toBeInTheDocument();
|
||||
expect(screen.getByText('Preview not available for unit changes at this time')).toBeInTheDocument();
|
||||
|
||||
// Click on ignore changes
|
||||
const ignoreChangesButton = screen.getByRole('button', { name: /ignore changes/i });
|
||||
|
||||
@@ -41,6 +41,7 @@ const CourseUnit = ({ courseId }) => {
|
||||
courseUnit,
|
||||
isLoading,
|
||||
sequenceId,
|
||||
courseUnitLoadingStatus,
|
||||
unitTitle,
|
||||
unitCategory,
|
||||
errorMessage,
|
||||
@@ -210,6 +211,7 @@ const CourseUnit = ({ courseId }) => {
|
||||
courseId={courseId}
|
||||
blockId={blockId}
|
||||
isUnitVerticalType={isUnitVerticalType}
|
||||
courseUnitLoadingStatus={courseUnitLoadingStatus}
|
||||
unitXBlockActions={unitXBlockActions}
|
||||
courseVerticalChildren={courseVerticalChildren.children}
|
||||
handleConfigureSubmit={handleConfigureSubmit}
|
||||
|
||||
@@ -17,7 +17,6 @@ import { cloneDeep, set } from 'lodash';
|
||||
|
||||
import {
|
||||
getCourseSectionVerticalApiUrl,
|
||||
getCourseUnitApiUrl,
|
||||
getCourseVerticalChildrenApiUrl,
|
||||
getCourseOutlineInfoUrl,
|
||||
getXBlockBaseApiUrl,
|
||||
@@ -28,7 +27,6 @@ import {
|
||||
deleteUnitItemQuery,
|
||||
editCourseUnitVisibilityAndData,
|
||||
fetchCourseSectionVerticalData,
|
||||
fetchCourseUnitQuery,
|
||||
fetchCourseVerticalChildrenData,
|
||||
getCourseOutlineInfoQuery,
|
||||
patchUnitItemQuery,
|
||||
@@ -37,13 +35,12 @@ import initializeStore from '../store';
|
||||
import {
|
||||
courseCreateXblockMock,
|
||||
courseSectionVerticalMock,
|
||||
courseUnitIndexMock,
|
||||
courseUnitMock,
|
||||
courseVerticalChildrenMock,
|
||||
clipboardMockResponse,
|
||||
courseOutlineInfoMock,
|
||||
} from './__mocks__';
|
||||
import { clipboardUnit, clipboardXBlock } from '../__mocks__';
|
||||
import { clipboardUnit } from '../__mocks__';
|
||||
import { executeThunk } from '../utils';
|
||||
import { IFRAME_FEATURE_POLICY } from '../constants';
|
||||
import pasteComponentMessages from '../generic/clipboard/paste-component/messages';
|
||||
@@ -72,7 +69,8 @@ let store;
|
||||
let queryClient;
|
||||
const courseId = '123';
|
||||
const blockId = '567890';
|
||||
const unitDisplayName = courseUnitIndexMock.metadata.display_name;
|
||||
const sequenceId = 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5';
|
||||
const unitDisplayName = courseSectionVerticalMock.xblock_info.display_name;
|
||||
const mockedUsedNavigate = jest.fn();
|
||||
const userName = 'openedx';
|
||||
const handleConfigureSubmitMock = jest.fn();
|
||||
@@ -90,7 +88,7 @@ const postXBlockBody = {
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: () => ({ blockId }),
|
||||
useParams: () => ({ blockId, sequenceId }),
|
||||
useNavigate: () => mockedUsedNavigate,
|
||||
}));
|
||||
|
||||
@@ -146,14 +144,10 @@ describe('<CourseUnit />', () => {
|
||||
axiosMock
|
||||
.onGet(getClipboardUrl())
|
||||
.reply(200, clipboardUnit);
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
axiosMock
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId, courseId), store.dispatch);
|
||||
axiosMock
|
||||
.onGet(getCourseVerticalChildrenApiUrl(blockId))
|
||||
.reply(200, courseVerticalChildrenMock);
|
||||
@@ -168,8 +162,8 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
it('render CourseUnit component correctly', async () => {
|
||||
render(<RootWrapper />);
|
||||
const currentSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;
|
||||
const currentSubSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;
|
||||
const currentSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name;
|
||||
const currentSubSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name;
|
||||
|
||||
await waitFor(() => {
|
||||
const unitHeaderTitle = screen.getByTestId('unit-header-title');
|
||||
@@ -278,11 +272,14 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
has_changes: true,
|
||||
published_by: userName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
has_changes: true,
|
||||
published_by: userName,
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -314,11 +311,14 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
has_changes: true,
|
||||
published_by: userName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
has_changes: true,
|
||||
published_by: userName,
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -381,12 +381,15 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
},
|
||||
});
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
|
||||
@@ -395,7 +398,7 @@ describe('<CourseUnit />', () => {
|
||||
expect(screen.getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishLastPublished.defaultMessage
|
||||
.replace('{publishedOn}', courseUnitIndexMock.published_on)
|
||||
.replace('{publishedOn}', courseSectionVerticalMock.xblock_info.published_on)
|
||||
.replace('{publishedBy}', userName),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument();
|
||||
@@ -405,6 +408,9 @@ describe('<CourseUnit />', () => {
|
||||
axiosMock
|
||||
.onDelete(getXBlockBaseApiUrl(courseVerticalChildrenMock.children[0].block_id))
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseSectionVerticalApiUrl(courseId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
await executeThunk(deleteUnitItemQuery(
|
||||
courseId,
|
||||
courseVerticalChildrenMock.children[0].block_id,
|
||||
@@ -425,8 +431,8 @@ describe('<CourseUnit />', () => {
|
||||
await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -445,15 +451,15 @@ describe('<CourseUnit />', () => {
|
||||
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(courseSectionVerticalMock.xblock_info.release_date)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishInfoDraftSaved.defaultMessage
|
||||
.replace('{editedOn}', courseUnitIndexMock.edited_on)
|
||||
.replace('{editedBy}', courseUnitIndexMock.edited_by),
|
||||
.replace('{editedOn}', courseSectionVerticalMock.xblock_info.edited_on)
|
||||
.replace('{editedBy}', courseSectionVerticalMock.xblock_info.edited_by),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.releaseInfoWithSection.defaultMessage
|
||||
.replace('{sectionName}', courseUnitIndexMock.release_date_from),
|
||||
.replace('{sectionName}', courseSectionVerticalMock.xblock_info.release_date_from),
|
||||
)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -465,10 +471,6 @@ describe('<CourseUnit />', () => {
|
||||
id: courseVerticalChildrenMock.children[0].block_id,
|
||||
});
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
|
||||
axiosMock
|
||||
.onPost(postXBlockBaseApiUrl({
|
||||
parent_locator: blockId,
|
||||
@@ -518,12 +520,15 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
},
|
||||
});
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
|
||||
@@ -532,7 +537,7 @@ describe('<CourseUnit />', () => {
|
||||
expect(screen.getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishLastPublished.defaultMessage
|
||||
.replace('{publishedOn}', courseUnitIndexMock.published_on)
|
||||
.replace('{publishedOn}', courseSectionVerticalMock.xblock_info.published_on)
|
||||
.replace('{publishedBy}', userName),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument();
|
||||
@@ -540,8 +545,8 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -561,15 +566,15 @@ describe('<CourseUnit />', () => {
|
||||
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(courseSectionVerticalMock.xblock_info.release_date)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishInfoDraftSaved.defaultMessage
|
||||
.replace('{editedOn}', courseUnitIndexMock.edited_on)
|
||||
.replace('{editedBy}', courseUnitIndexMock.edited_by),
|
||||
.replace('{editedOn}', courseSectionVerticalMock.xblock_info.edited_on)
|
||||
.replace('{editedBy}', courseSectionVerticalMock.xblock_info.edited_by),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.releaseInfoWithSection.defaultMessage
|
||||
.replace('{sectionName}', courseUnitIndexMock.release_date_from),
|
||||
.replace('{sectionName}', courseSectionVerticalMock.xblock_info.release_date_from),
|
||||
)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -612,12 +617,15 @@ describe('<CourseUnit />', () => {
|
||||
}))
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
metadata: {
|
||||
...courseUnitIndexMock.metadata,
|
||||
display_name: newDisplayName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
metadata: {
|
||||
...courseSectionVerticalMock.xblock_info.metadata,
|
||||
display_name: newDisplayName,
|
||||
},
|
||||
},
|
||||
});
|
||||
axiosMock
|
||||
@@ -690,12 +698,15 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
@@ -710,8 +721,8 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
|
||||
@@ -724,15 +735,15 @@ describe('<CourseUnit />', () => {
|
||||
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(courseSectionVerticalMock.xblock_info.release_date)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishInfoDraftSaved.defaultMessage
|
||||
.replace('{editedOn}', courseUnitIndexMock.edited_on)
|
||||
.replace('{editedBy}', courseUnitIndexMock.edited_by),
|
||||
.replace('{editedOn}', courseSectionVerticalMock.xblock_info.edited_on)
|
||||
.replace('{editedBy}', courseSectionVerticalMock.xblock_info.edited_by),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.releaseInfoWithSection.defaultMessage
|
||||
.replace('{sectionName}', courseUnitIndexMock.release_date_from),
|
||||
.replace('{sectionName}', courseSectionVerticalMock.xblock_info.release_date_from),
|
||||
)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -794,12 +805,15 @@ describe('<CourseUnit />', () => {
|
||||
},
|
||||
}))
|
||||
.reply(200, { dummy: 'value' })
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
metadata: {
|
||||
...courseUnitIndexMock.metadata,
|
||||
display_name: newDisplayName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
metadata: {
|
||||
...courseSectionVerticalMock.xblock_info.metadata,
|
||||
display_name: newDisplayName,
|
||||
},
|
||||
},
|
||||
})
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
@@ -844,12 +858,15 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
@@ -861,7 +878,7 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishLastPublished.defaultMessage
|
||||
.replace('{publishedOn}', courseUnitIndexMock.published_on)
|
||||
.replace('{publishedOn}', courseSectionVerticalMock.xblock_info.published_on)
|
||||
.replace('{publishedBy}', userName),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument();
|
||||
@@ -874,8 +891,8 @@ describe('<CourseUnit />', () => {
|
||||
userEvent.click(videoButton);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
|
||||
@@ -888,15 +905,15 @@ describe('<CourseUnit />', () => {
|
||||
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(courseSectionVerticalMock.xblock_info.release_date)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishInfoDraftSaved.defaultMessage
|
||||
.replace('{editedOn}', courseUnitIndexMock.edited_on)
|
||||
.replace('{editedBy}', courseUnitIndexMock.edited_by),
|
||||
.replace('{editedOn}', courseSectionVerticalMock.xblock_info.edited_on)
|
||||
.replace('{editedBy}', courseSectionVerticalMock.xblock_info.edited_by),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.releaseInfoWithSection.defaultMessage
|
||||
.replace('{sectionName}', courseUnitIndexMock.release_date_from),
|
||||
.replace('{sectionName}', courseSectionVerticalMock.xblock_info.release_date_from),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: /add video to your course/i, hidden: true })).toBeInTheDocument();
|
||||
waffleSpy.mockRestore();
|
||||
@@ -918,12 +935,15 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
@@ -933,7 +953,7 @@ describe('<CourseUnit />', () => {
|
||||
expect(screen.getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishLastPublished.defaultMessage
|
||||
.replace('{publishedOn}', courseUnitIndexMock.published_on)
|
||||
.replace('{publishedOn}', courseSectionVerticalMock.xblock_info.published_on)
|
||||
.replace('{publishedBy}', userName),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: sidebarMessages.actionButtonPublishTitle.defaultMessage })).not.toBeInTheDocument();
|
||||
@@ -953,8 +973,8 @@ describe('<CourseUnit />', () => {
|
||||
*/
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
|
||||
@@ -967,15 +987,15 @@ describe('<CourseUnit />', () => {
|
||||
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(courseSectionVerticalMock.xblock_info.release_date)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishInfoDraftSaved.defaultMessage
|
||||
.replace('{editedOn}', courseUnitIndexMock.edited_on)
|
||||
.replace('{editedBy}', courseUnitIndexMock.edited_by),
|
||||
.replace('{editedOn}', courseSectionVerticalMock.xblock_info.edited_on)
|
||||
.replace('{editedBy}', courseSectionVerticalMock.xblock_info.edited_by),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.releaseInfoWithSection.defaultMessage
|
||||
.replace('{sectionName}', courseUnitIndexMock.release_date_from),
|
||||
.replace('{sectionName}', courseSectionVerticalMock.xblock_info.release_date_from),
|
||||
)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -991,22 +1011,22 @@ describe('<CourseUnit />', () => {
|
||||
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(courseSectionVerticalMock.xblock_info.release_date)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.publishInfoDraftSaved.defaultMessage
|
||||
.replace('{editedOn}', courseUnitIndexMock.edited_on)
|
||||
.replace('{editedBy}', courseUnitIndexMock.edited_by),
|
||||
.replace('{editedOn}', courseSectionVerticalMock.xblock_info.edited_on)
|
||||
.replace('{editedBy}', courseSectionVerticalMock.xblock_info.edited_by),
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.getByText(
|
||||
sidebarMessages.releaseInfoWithSection.defaultMessage
|
||||
.replace('{sectionName}', courseUnitIndexMock.release_date_from),
|
||||
.replace('{sectionName}', courseSectionVerticalMock.xblock_info.release_date_from),
|
||||
)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders course unit details in the sidebar', async () => {
|
||||
render(<RootWrapper />);
|
||||
const courseUnitLocationId = extractCourseUnitId(courseUnitIndexMock.id);
|
||||
const courseUnitLocationId = extractCourseUnitId(courseSectionVerticalMock.xblock_info.id);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(sidebarMessages.sidebarHeaderUnitLocationTitle.defaultMessage)).toBeInTheDocument();
|
||||
@@ -1033,13 +1053,16 @@ describe('<CourseUnit />', () => {
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(courseId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
currently_visible_to_students: false,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
currently_visible_to_students: false,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(courseId), store.dispatch);
|
||||
|
||||
await waitFor(() => {
|
||||
const alert = screen.queryAllByRole('alert').find(
|
||||
@@ -1076,11 +1099,14 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.staffOnly,
|
||||
has_explicit_staff_lock: true,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.staffOnly,
|
||||
has_explicit_staff_lock: true,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.republish, true), store.dispatch);
|
||||
@@ -1113,8 +1139,8 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.republish, null), store.dispatch);
|
||||
|
||||
@@ -1143,12 +1169,15 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.live,
|
||||
has_changes: false,
|
||||
published_by: userName,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.makePublic, true), store.dispatch);
|
||||
@@ -1157,7 +1186,7 @@ describe('<CourseUnit />', () => {
|
||||
.getByText(sidebarMessages.sidebarTitlePublishedAndLive.defaultMessage)).toBeInTheDocument();
|
||||
expect(within(courseUnitSidebar).getByText(
|
||||
sidebarMessages.publishLastPublished.defaultMessage
|
||||
.replace('{publishedOn}', courseUnitIndexMock.published_on)
|
||||
.replace('{publishedOn}', courseSectionVerticalMock.xblock_info.published_on)
|
||||
.replace('{publishedBy}', userName),
|
||||
)).toBeInTheDocument();
|
||||
expect(publishBtn).not.toBeInTheDocument();
|
||||
@@ -1199,9 +1228,14 @@ describe('<CourseUnit />', () => {
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock, published: true, has_changes: false,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
published: true,
|
||||
has_changes: false,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(editCourseUnitVisibilityAndData(
|
||||
@@ -1258,17 +1292,20 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
axiosMock
|
||||
.onPost(getXBlockBaseApiUrl(courseUnitIndexMock.id), {
|
||||
.onPost(getXBlockBaseApiUrl(courseSectionVerticalMock.xblock_info.id), {
|
||||
publish: null,
|
||||
metadata: { visible_to_staff_only: true, group_access: { 50: [2] }, discussion_enabled: true },
|
||||
})
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.replyOnce(200, {
|
||||
...courseUnitIndexMock,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.staffOnly,
|
||||
has_explicit_staff_lock: true,
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
visibility_state: UNIT_VISIBILITY_STATES.staffOnly,
|
||||
has_explicit_staff_lock: true,
|
||||
},
|
||||
});
|
||||
|
||||
const modalSaveBtn = within(configureModal)
|
||||
@@ -1307,13 +1344,15 @@ describe('<CourseUnit />', () => {
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
enable_copy_paste_units: true,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
enable_copy_paste_units: true,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage }));
|
||||
@@ -1362,20 +1401,17 @@ describe('<CourseUnit />', () => {
|
||||
});
|
||||
|
||||
axiosMock
|
||||
.onGet(getClipboardUrl())
|
||||
.reply(200, clipboardXBlock);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
enable_copy_paste_units: true,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
enable_copy_paste_units: true,
|
||||
},
|
||||
});
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage }));
|
||||
userEvent.click(screen.getByRole('button', { name: messages.pasteButtonText.defaultMessage }));
|
||||
|
||||
await waitFor(() => {
|
||||
const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage);
|
||||
@@ -1427,13 +1463,15 @@ describe('<CourseUnit />', () => {
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
enable_copy_paste_units: true,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
enable_copy_paste_units: true,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage }));
|
||||
@@ -1477,13 +1515,15 @@ describe('<CourseUnit />', () => {
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
enable_copy_paste_units: true,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
enable_copy_paste_units: true,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage }));
|
||||
@@ -1529,13 +1569,15 @@ describe('<CourseUnit />', () => {
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
enable_copy_paste_units: true,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
enable_copy_paste_units: true,
|
||||
},
|
||||
});
|
||||
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: sidebarMessages.actionButtonCopyUnitTitle.defaultMessage }));
|
||||
@@ -1687,8 +1729,8 @@ describe('<CourseUnit />', () => {
|
||||
.reply(200, {});
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
|
||||
await screen.findByText(unitDisplayName);
|
||||
|
||||
@@ -1970,7 +2012,6 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
describe('Library Content page', () => {
|
||||
const newUnitId = '12345';
|
||||
const sequenceId = courseSectionVerticalMock.subsection_location;
|
||||
|
||||
beforeEach(async () => {
|
||||
axiosMock
|
||||
@@ -1987,20 +2028,6 @@ describe('<CourseUnit />', () => {
|
||||
},
|
||||
});
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
category: 'library_content',
|
||||
ancestor_info: {
|
||||
...courseUnitIndexMock.ancestor_info,
|
||||
child_info: {
|
||||
...courseUnitIndexMock.ancestor_info.child_info,
|
||||
category: 'library_content',
|
||||
},
|
||||
},
|
||||
});
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
});
|
||||
|
||||
it('navigates to library content page on receive window event', async () => {
|
||||
@@ -2020,8 +2047,8 @@ describe('<CourseUnit />', () => {
|
||||
findByTestId,
|
||||
} = render(<RootWrapper />);
|
||||
|
||||
const currentSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;
|
||||
const currentSubSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;
|
||||
const currentSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name;
|
||||
const currentSubSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name;
|
||||
|
||||
const unitHeaderTitle = await findByTestId('unit-header-title');
|
||||
await findByText(unitDisplayName);
|
||||
@@ -2049,7 +2076,6 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
describe('Split Test Content page', () => {
|
||||
const newUnitId = '12345';
|
||||
const sequenceId = courseSectionVerticalMock.subsection_location;
|
||||
|
||||
beforeEach(async () => {
|
||||
axiosMock
|
||||
@@ -2066,20 +2092,6 @@ describe('<CourseUnit />', () => {
|
||||
},
|
||||
});
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
category: 'split_test',
|
||||
ancestor_info: {
|
||||
...courseUnitIndexMock.ancestor_info,
|
||||
child_info: {
|
||||
...courseUnitIndexMock.ancestor_info.child_info,
|
||||
category: 'split_test',
|
||||
},
|
||||
},
|
||||
});
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
});
|
||||
|
||||
it('navigates to split test content page on receive window event', async () => {
|
||||
@@ -2124,8 +2136,8 @@ describe('<CourseUnit />', () => {
|
||||
it('should render split test content page correctly', async () => {
|
||||
render(<RootWrapper />);
|
||||
|
||||
const currentSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;
|
||||
const currentSubSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;
|
||||
const currentSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name;
|
||||
const currentSubSectionName = courseSectionVerticalMock.xblock_info.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(() => {
|
||||
@@ -2210,10 +2222,6 @@ describe('<CourseUnit />', () => {
|
||||
? { ...child, block_type: 'html' }
|
||||
: child));
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
|
||||
axiosMock
|
||||
.onPost(postXBlockBaseApiUrl({
|
||||
parent_locator: blockId,
|
||||
@@ -2251,15 +2259,19 @@ describe('<CourseUnit />', () => {
|
||||
render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(courseId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
upstreamInfo: {
|
||||
upstreamRef: 'lct:org:lib:unit:unit-1',
|
||||
upstreamLink: 'some-link',
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
upstreamInfo: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
upstreamRef: 'lct:org:lib:unit:unit-1',
|
||||
upstreamLink: 'some-link',
|
||||
},
|
||||
},
|
||||
});
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(courseId), store.dispatch);
|
||||
|
||||
expect(screen.getByText(/this unit can only be edited from the \./i)).toBeInTheDocument();
|
||||
|
||||
|
||||
@@ -1,1126 +0,0 @@
|
||||
module.exports = {
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec',
|
||||
display_name: 'Getting Started',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Jan 03, 2024 at 12:06 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'needs_attention',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: true,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
data: '',
|
||||
metadata: {
|
||||
display_name: 'Getting Started',
|
||||
xml_attributes: {
|
||||
filename: [
|
||||
'vertical/867dddb6f55d410caaa9c1eb9c6743ec.xml',
|
||||
'vertical/867dddb6f55d410caaa9c1eb9c6743ec.xml',
|
||||
],
|
||||
},
|
||||
},
|
||||
ancestor_info: {
|
||||
ancestors: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5',
|
||||
display_name: 'Lesson 1 - Getting Started',
|
||||
category: 'sequential',
|
||||
has_children: true,
|
||||
edited_on: 'Jan 03, 2024 at 12:06 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40sequential%2Bblock%4019a30717eff543078a5d94ae9d6c18a5',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'needs_attention',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: null,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
hide_after_due: false,
|
||||
is_proctored_exam: false,
|
||||
was_exam_ever_linked_with_external: false,
|
||||
online_proctoring_rules: '',
|
||||
is_practice_exam: false,
|
||||
is_onboarding_exam: false,
|
||||
is_time_limited: false,
|
||||
exam_review_rules: '',
|
||||
default_time_limit_minutes: null,
|
||||
proctoring_exam_configuration_link: null,
|
||||
supports_onboarding: false,
|
||||
show_review_rules: true,
|
||||
child_info: {
|
||||
category: 'vertical',
|
||||
display_name: 'Unit',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec',
|
||||
display_name: 'Getting Started',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Jan 03, 2024 at 12:06 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'needs_attention',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: true,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@4f6c1b4e316a419ab5b6bf30e6c708e9',
|
||||
display_name: 'Working with Videos',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@4f6c1b4e316a419ab5b6bf30e6c708e9',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@3dc16db8d14842e38324e95d4030b8a0',
|
||||
display_name: 'Videos on edX',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@3dc16db8d14842e38324e95d4030b8a0',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@4a1bba2a403f40bca5ec245e945b0d76',
|
||||
display_name: 'Video Demonstrations',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@4a1bba2a403f40bca5ec245e945b0d76',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@256f17a44983429fb1a60802203ee4e0',
|
||||
display_name: 'Video Presentation Styles',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@256f17a44983429fb1a60802203ee4e0',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@e3601c0abee6427d8c17e6d6f8fdddd1',
|
||||
display_name: 'Interactive Questions',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@e3601c0abee6427d8c17e6d6f8fdddd1',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@a79d59cd72034188a71d388f4954a606',
|
||||
display_name: 'Exciting Labs and Tools',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@a79d59cd72034188a71d388f4954a606',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@134df56c516a4a0dbb24dd5facef746e',
|
||||
display_name: 'Reading Assignments',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@134df56c516a4a0dbb24dd5facef746e',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@d91b9e5d8bc64d57a1332d06bf2f2193',
|
||||
display_name: 'When Are Your Exams? ',
|
||||
category: 'vertical',
|
||||
has_children: true,
|
||||
edited_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@d91b9e5d8bc64d57a1332d06bf2f2193',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
discussion_enabled: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions',
|
||||
display_name: 'Example Week 1: Getting Started',
|
||||
category: 'chapter',
|
||||
has_children: true,
|
||||
edited_on: 'Jan 03, 2024 at 12:06 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 28, 2023 at 10:00 UTC',
|
||||
studio_url: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40chapter%2Bblock%40interactive_demonstrations',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: null,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
highlights: [],
|
||||
highlights_enabled: true,
|
||||
highlights_preview_only: false,
|
||||
highlights_doc_url: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_course_highlight_emails.html',
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@course+block@course',
|
||||
display_name: 'Demonstration Course',
|
||||
category: 'course',
|
||||
has_children: true,
|
||||
unit_level_discussions: false,
|
||||
edited_on: 'Jan 03, 2024 at 12:06 UTC',
|
||||
published: true,
|
||||
published_on: 'Jan 03, 2024 at 08:57 UTC',
|
||||
studio_url: '/course/course-v1:edX+DemoX+Demo_Course',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: null,
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: null,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
highlights_enabled_for_messaging: false,
|
||||
highlights_enabled: true,
|
||||
highlights_preview_only: false,
|
||||
highlights_doc_url: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_course_highlight_emails.html',
|
||||
enable_proctored_exams: false,
|
||||
create_zendesk_tickets: true,
|
||||
enable_timed_exams: true,
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestor_has_staff_lock: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
enable_copy_paste_units: false,
|
||||
edited_by: 'edx',
|
||||
published_by: null,
|
||||
currently_visible_to_students: true,
|
||||
has_partition_group_components: false,
|
||||
release_date_from: 'Section "Example Week 1: Getting Started"',
|
||||
staff_lock_from: null,
|
||||
upstreamInfo: {
|
||||
upstreamLink: undefined,
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
export { default as courseUnitIndexMock } from './courseUnitIndex';
|
||||
export { default as courseSectionVerticalMock } from './courseSectionVertical';
|
||||
export { default as courseUnitMock } from './courseUnit';
|
||||
export { default as courseCreateXblockMock } from './courseCreateXblock';
|
||||
|
||||
@@ -5,11 +5,11 @@ import {
|
||||
} from '../../testUtils';
|
||||
|
||||
import { executeThunk } from '../../utils';
|
||||
import { getCourseSectionVerticalApiUrl, getCourseUnitApiUrl } from '../data/api';
|
||||
import { getCourseSectionVerticalApiUrl } from '../data/api';
|
||||
import { getApiWaffleFlagsUrl } from '../../data/api';
|
||||
import { fetchWaffleFlags } from '../../data/thunks';
|
||||
import { fetchCourseSectionVerticalData, fetchCourseUnitQuery } from '../data/thunk';
|
||||
import { courseSectionVerticalMock, courseUnitIndexMock } from '../__mocks__';
|
||||
import { fetchCourseSectionVerticalData } from '../data/thunk';
|
||||
import { courseSectionVerticalMock } from '../__mocks__';
|
||||
import Breadcrumbs from './Breadcrumbs';
|
||||
|
||||
let axiosMock;
|
||||
@@ -43,9 +43,9 @@ describe('<Breadcrumbs />', () => {
|
||||
reduxStore = mocks.reduxStore;
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), reduxStore.dispatch);
|
||||
.onGet(getCourseSectionVerticalApiUrl(courseId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
await executeThunk(fetchCourseSectionVerticalData(courseId), reduxStore.dispatch);
|
||||
axiosMock
|
||||
.onGet(getCourseSectionVerticalApiUrl(courseId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
|
||||
@@ -12,16 +12,19 @@ export function useSequenceNavigationMetadata(courseId, currentSequenceId, curre
|
||||
const isLastUnit = !nextUrl;
|
||||
const sequenceIds = useSelector(getSequenceIds);
|
||||
const sequenceIndex = sequenceIds.indexOf(currentSequenceId);
|
||||
const unitIndex = sequence.unitIds.indexOf(currentUnitId);
|
||||
let unitIndex = sequence?.unitIds.indexOf(currentUnitId);
|
||||
|
||||
const nextSequenceId = sequenceIndex < sequenceIds.length - 1 ? sequenceIds[sequenceIndex + 1] : null;
|
||||
const previousSequenceId = sequenceIndex > 0 ? sequenceIds[sequenceIndex - 1] : null;
|
||||
|
||||
if (!unitIndex) {
|
||||
// Handle case where unitIndex is not found
|
||||
unitIndex = 0;
|
||||
}
|
||||
let nextLink;
|
||||
const nextIndex = unitIndex + 1;
|
||||
|
||||
if (nextIndex < sequence.unitIds.length) {
|
||||
const nextUnitId = sequence.unitIds[nextIndex];
|
||||
if (nextIndex < sequence?.unitIds.length) {
|
||||
const nextUnitId = sequence?.unitIds[nextIndex];
|
||||
nextLink = `/course/${courseId}/container/${nextUnitId}/${currentSequenceId}`;
|
||||
} else if (nextSequenceId) {
|
||||
const pathToNextUnit = decodeURIComponent(nextUrl);
|
||||
@@ -32,7 +35,7 @@ export function useSequenceNavigationMetadata(courseId, currentSequenceId, curre
|
||||
const previousIndex = unitIndex - 1;
|
||||
|
||||
if (previousIndex >= 0) {
|
||||
const previousUnitId = sequence.unitIds[previousIndex];
|
||||
const previousUnitId = sequence?.unitIds[previousIndex];
|
||||
previousLink = `/course/${courseId}/container/${previousUnitId}/${currentSequenceId}`;
|
||||
} else if (previousSequenceId) {
|
||||
const pathToPreviousUnit = decodeURIComponent(prevUrl);
|
||||
|
||||
@@ -35,7 +35,7 @@ const SequenceNavigation = ({
|
||||
|
||||
const shouldDisplayNotificationTriggerInSequence = useWindowSize().width < breakpoints.small.minWidth;
|
||||
const renderUnitButtons = () => {
|
||||
if (sequence.unitIds?.length === 0 || unitId === null) {
|
||||
if (sequence.unitIds.length === 0 || unitId === null) {
|
||||
return (
|
||||
<div style={{ flexBasis: '100%', minWidth: 0, borderBottom: 'solid 1px #EAEAEA' }} />
|
||||
);
|
||||
@@ -43,7 +43,7 @@ const SequenceNavigation = ({
|
||||
|
||||
return (
|
||||
<SequenceNavigationTabs
|
||||
unitIds={sequence.unitIds || []}
|
||||
unitIds={sequence?.unitIds || []}
|
||||
unitId={unitId}
|
||||
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
|
||||
showPasteUnit={showPasteUnit}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { isUnitReadOnly, normalizeCourseSectionVerticalData, updateXBlockBlockId
|
||||
|
||||
const getStudioBaseUrl = () => getConfig().STUDIO_BASE_URL;
|
||||
|
||||
export const getCourseUnitApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/container/${itemId}`;
|
||||
export const getXBlockBaseApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/${itemId}`;
|
||||
export const getCourseSectionVerticalApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container_handler/${itemId}`;
|
||||
export const getCourseVerticalChildrenApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container/vertical/${itemId}/children`;
|
||||
@@ -15,20 +14,6 @@ export const getCourseOutlineInfoUrl = (courseId) => `${getStudioBaseUrl()}/cour
|
||||
export const postXBlockBaseApiUrl = () => `${getStudioBaseUrl()}/xblock/`;
|
||||
export const libraryBlockChangesUrl = (blockId) => `${getStudioBaseUrl()}/api/contentstore/v2/downstreams/${blockId}/sync`;
|
||||
|
||||
/**
|
||||
* Get course unit.
|
||||
* @param {string} unitId
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getCourseUnitData(unitId) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getCourseUnitApiUrl(unitId));
|
||||
|
||||
const result = camelCaseObject(data);
|
||||
result.readOnly = isUnitReadOnly(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit course unit display name.
|
||||
* @param {string} unitId
|
||||
@@ -47,15 +32,18 @@ export async function editUnitDisplayName(unitId, displayName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object containing course section vertical data.
|
||||
* Fetch vertical block data from the container_handler endpoint.
|
||||
* @param {string} unitId
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getCourseSectionVerticalData(unitId) {
|
||||
export async function getVerticalData(unitId) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getCourseSectionVerticalApiUrl(unitId));
|
||||
|
||||
return normalizeCourseSectionVerticalData(data);
|
||||
const courseSectionVerticalData = normalizeCourseSectionVerticalData(data);
|
||||
courseSectionVerticalData.xblockInfo.readOnly = isUnitReadOnly(courseSectionVerticalData.xblockInfo);
|
||||
|
||||
return courseSectionVerticalData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RequestStatus } from 'CourseAuthoring/data/constants';
|
||||
|
||||
export const getCourseUnitData = (state) => state.courseUnit.unit;
|
||||
export const getCourseUnitData = (state) => state.courseUnit.courseSectionVertical.xblockInfo ?? {};
|
||||
export const getCanEdit = (state) => state.courseUnit.canEdit;
|
||||
export const getStaticFileNotices = (state) => state.courseUnit.staticFileNotices;
|
||||
export const getCourseUnit = (state) => state.courseUnit;
|
||||
@@ -16,7 +16,7 @@ export const getCourseVerticalChildren = (state) => state.courseUnit.courseVerti
|
||||
export const getCourseOutlineInfo = (state) => state.courseUnit.courseOutlineInfo;
|
||||
export const getCourseOutlineInfoLoadingStatus = (state) => state.courseUnit.courseOutlineInfoLoadingStatus;
|
||||
export const getMovedXBlockParams = (state) => state.courseUnit.movedXBlockParams;
|
||||
const getLoadingStatuses = (state) => state.courseUnit.loadingStatus;
|
||||
export const getLoadingStatuses = (state) => state.courseUnit.loadingStatus;
|
||||
export const getIsLoading = createSelector(
|
||||
[getLoadingStatuses],
|
||||
loadingStatus => Object.values(loadingStatus)
|
||||
|
||||
@@ -12,11 +12,9 @@ const slice = createSlice({
|
||||
isTitleEditFormOpen: false,
|
||||
canEdit: true,
|
||||
loadingStatus: {
|
||||
fetchUnitLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
courseSectionVerticalLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
courseVerticalChildrenLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
},
|
||||
unit: {},
|
||||
courseSectionVertical: {},
|
||||
courseVerticalChildren: { children: [], isPublished: true },
|
||||
staticFileNotices: {},
|
||||
@@ -31,15 +29,6 @@ const slice = createSlice({
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
fetchCourseItemSuccess: (state, { payload }) => {
|
||||
state.unit = payload;
|
||||
},
|
||||
updateLoadingCourseUnitStatus: (state, { payload }) => {
|
||||
state.loadingStatus = {
|
||||
...state.loadingStatus,
|
||||
fetchUnitLoadingStatus: payload.status,
|
||||
};
|
||||
},
|
||||
updateQueryPendingStatus: (state, { payload }) => {
|
||||
state.isQueryPending = payload;
|
||||
},
|
||||
@@ -81,12 +70,6 @@ const slice = createSlice({
|
||||
createUnitXblockLoadingStatus: payload.status,
|
||||
};
|
||||
},
|
||||
addNewUnitStatus: (state, { payload }) => {
|
||||
state.loadingStatus = {
|
||||
...state.loadingStatus,
|
||||
fetchUnitLoadingStatus: payload.status,
|
||||
};
|
||||
},
|
||||
updateCourseVerticalChildren: (state, { payload }) => {
|
||||
state.courseVerticalChildren = payload;
|
||||
},
|
||||
@@ -109,8 +92,6 @@ const slice = createSlice({
|
||||
});
|
||||
|
||||
export const {
|
||||
fetchCourseItemSuccess,
|
||||
updateLoadingCourseUnitStatus,
|
||||
updateSavingStatus,
|
||||
updateModel,
|
||||
fetchSequenceRequest,
|
||||
|
||||
@@ -10,9 +10,8 @@ import { NOTIFICATION_MESSAGES } from '../../constants';
|
||||
import { updateModel, updateModels } from '../../generic/model-store';
|
||||
import { messageTypes } from '../constants';
|
||||
import {
|
||||
getCourseUnitData,
|
||||
editUnitDisplayName,
|
||||
getCourseSectionVerticalData,
|
||||
getVerticalData,
|
||||
createCourseXblock,
|
||||
getCourseVerticalChildren,
|
||||
handleCourseUnitVisibilityAndData,
|
||||
@@ -22,8 +21,6 @@ import {
|
||||
patchUnitItem,
|
||||
} from './api';
|
||||
import {
|
||||
updateLoadingCourseUnitStatus,
|
||||
fetchCourseItemSuccess,
|
||||
updateSavingStatus,
|
||||
fetchSequenceRequest,
|
||||
fetchSequenceFailure,
|
||||
@@ -40,30 +37,13 @@ import {
|
||||
} from './slice';
|
||||
import { getNotificationMessage } from './utils';
|
||||
|
||||
export function fetchCourseUnitQuery(courseId) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateLoadingCourseUnitStatus({ status: RequestStatus.IN_PROGRESS }));
|
||||
|
||||
try {
|
||||
const courseUnit = await getCourseUnitData(courseId);
|
||||
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
dispatch(updateLoadingCourseUnitStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
return true;
|
||||
} catch (error) {
|
||||
dispatch(updateLoadingCourseUnitStatus({ status: RequestStatus.FAILED }));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchCourseSectionVerticalData(courseId, sequenceId) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateLoadingCourseSectionVerticalDataStatus({ status: RequestStatus.IN_PROGRESS }));
|
||||
dispatch(fetchSequenceRequest({ sequenceId }));
|
||||
|
||||
try {
|
||||
const courseSectionVerticalData = await getCourseSectionVerticalData(courseId);
|
||||
const courseSectionVerticalData = await getVerticalData(courseId);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
dispatch(updateLoadingCourseSectionVerticalDataStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
dispatch(updateModel({
|
||||
@@ -94,8 +74,7 @@ export function editCourseItemQuery(itemId, displayName, sequenceId) {
|
||||
try {
|
||||
await editUnitDisplayName(itemId, displayName).then(async (result) => {
|
||||
if (result) {
|
||||
const courseUnit = await getCourseUnitData(itemId);
|
||||
const courseSectionVerticalData = await getCourseSectionVerticalData(itemId);
|
||||
const courseSectionVerticalData = await getVerticalData(itemId);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
dispatch(updateLoadingCourseSectionVerticalDataStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
dispatch(updateModel({
|
||||
@@ -107,7 +86,6 @@ export function editCourseItemQuery(itemId, displayName, sequenceId) {
|
||||
models: courseSectionVerticalData.units || [],
|
||||
}));
|
||||
dispatch(fetchSequenceSuccess({ sequenceId }));
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
dispatch(hideProcessingNotification());
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
}
|
||||
@@ -146,8 +124,8 @@ export function editCourseUnitVisibilityAndData(
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
const courseUnit = await getCourseUnitData(blockId);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
const courseSectionVerticalData = await getVerticalData(blockId);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
const courseVerticalChildrenData = await getCourseVerticalChildren(blockId);
|
||||
dispatch(updateCourseVerticalChildren(courseVerticalChildrenData));
|
||||
dispatch(hideProcessingNotification());
|
||||
@@ -174,7 +152,7 @@ export function createNewCourseXBlock(body, callback, blockId, sendMessageToIfra
|
||||
if (result) {
|
||||
const formattedResult = camelCaseObject(result);
|
||||
if (body.category === 'vertical') {
|
||||
const courseSectionVerticalData = await getCourseSectionVerticalData(formattedResult.locator);
|
||||
const courseSectionVerticalData = await getVerticalData(formattedResult.locator);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
}
|
||||
if (body.stagedContent) {
|
||||
@@ -194,8 +172,8 @@ export function createNewCourseXBlock(body, callback, blockId, sendMessageToIfra
|
||||
sendMessageToIframe(messageTypes.addXBlock, { data: result });
|
||||
}
|
||||
const currentBlockId = body.category === 'vertical' ? formattedResult.locator : blockId;
|
||||
const courseUnit = await getCourseUnitData(currentBlockId);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
const courseSectionVerticalData = await getVerticalData(currentBlockId);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -240,8 +218,8 @@ export function deleteUnitItemQuery(itemId, xblockId, sendMessageToIframe) {
|
||||
try {
|
||||
await deleteUnitItem(xblockId);
|
||||
sendMessageToIframe(messageTypes.completeXBlockDeleting, { locator: xblockId });
|
||||
const courseUnit = await getCourseUnitData(itemId);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
const courseSectionVerticalData = await getVerticalData(itemId);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
dispatch(hideProcessingNotification());
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
} catch (error) {
|
||||
@@ -259,8 +237,8 @@ export function duplicateUnitItemQuery(itemId, xblockId, callback) {
|
||||
try {
|
||||
const { courseKey, locator } = await duplicateUnitItem(itemId, xblockId);
|
||||
callback(courseKey, locator);
|
||||
const courseUnit = await getCourseUnitData(itemId);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
const courseSectionVerticalData = await getVerticalData(itemId);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
const courseVerticalChildrenData = await getCourseVerticalChildren(itemId);
|
||||
dispatch(updateCourseVerticalChildren(courseVerticalChildrenData));
|
||||
dispatch(hideProcessingNotification());
|
||||
@@ -316,8 +294,8 @@ export function patchUnitItemQuery({
|
||||
dispatch(updateCourseOutlineInfoLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
|
||||
callbackFn(sourceLocator);
|
||||
try {
|
||||
const courseUnit = await getCourseUnitData(currentParentLocator);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
const courseSectionVerticalData = await getVerticalData(currentParentLocator);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
} catch (error) {
|
||||
handleResponseErrors(error, dispatch, updateSavingStatus);
|
||||
}
|
||||
@@ -335,8 +313,8 @@ export function updateCourseUnitSidebar(itemId) {
|
||||
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.saving));
|
||||
|
||||
try {
|
||||
const courseUnit = await getCourseUnitData(itemId);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
const courseSectionVerticalData = await getVerticalData(itemId);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
dispatch(hideProcessingNotification());
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
} catch (error) {
|
||||
|
||||
@@ -8,9 +8,9 @@ import { initializeMockApp } from '@edx/frontend-platform';
|
||||
|
||||
import initializeStore from '../../store';
|
||||
import { executeThunk } from '../../utils';
|
||||
import { getCourseUnitApiUrl } from '../data/api';
|
||||
import { fetchCourseUnitQuery } from '../data/thunk';
|
||||
import { courseUnitIndexMock } from '../__mocks__';
|
||||
import { getCourseSectionVerticalApiUrl } from '../data/api';
|
||||
import { fetchCourseSectionVerticalData } from '../data/thunk';
|
||||
import { courseSectionVerticalMock } from '../__mocks__';
|
||||
import HeaderTitle from './HeaderTitle';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -52,9 +52,9 @@ describe('<HeaderTitle />', () => {
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.reply(200, courseUnitIndexMock);
|
||||
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, courseSectionVerticalMock);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
});
|
||||
|
||||
it('render HeaderTitle component correctly', () => {
|
||||
@@ -80,14 +80,18 @@ describe('<HeaderTitle />', () => {
|
||||
// Override mock unit with one sourced from an upstream library
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
upstreamInfo: {
|
||||
upstreamRef: 'lct:org:lib:unit:unit-1',
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
upstreamInfo: {
|
||||
...courseSectionVerticalMock.xblock_info.upstreamInfo,
|
||||
upstreamRef: 'lct:org:lib:unit:unit-1',
|
||||
},
|
||||
},
|
||||
});
|
||||
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
|
||||
const { getByRole } = renderComponent();
|
||||
|
||||
@@ -122,16 +126,19 @@ describe('<HeaderTitle />', () => {
|
||||
|
||||
it('displays a visibility message with the selected groups for the unit', async () => {
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
user_partition_info: {
|
||||
...courseUnitIndexMock.user_partition_info,
|
||||
selected_partition_index: 1,
|
||||
selected_groups_label: 'Visibility group 1',
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
user_partition_info: {
|
||||
...courseSectionVerticalMock.xblock_info.user_partition_info,
|
||||
selected_partition_index: 1,
|
||||
selected_groups_label: 'Visibility group 1',
|
||||
},
|
||||
},
|
||||
});
|
||||
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
const { getByText } = renderComponent();
|
||||
const visibilityMessage = messages.definedVisibilityMessage.defaultMessage
|
||||
.replace('{selectedGroupsLabel}', 'Visibility group 1');
|
||||
@@ -143,12 +150,15 @@ describe('<HeaderTitle />', () => {
|
||||
|
||||
it('displays a visibility message with the selected groups for some of xblock', async () => {
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(blockId))
|
||||
.onGet(getCourseSectionVerticalApiUrl(blockId))
|
||||
.reply(200, {
|
||||
...courseUnitIndexMock,
|
||||
has_partition_group_components: true,
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
has_partition_group_components: true,
|
||||
},
|
||||
});
|
||||
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
|
||||
const { getByText } = renderComponent();
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
editCourseItemQuery,
|
||||
editCourseUnitVisibilityAndData,
|
||||
fetchCourseSectionVerticalData,
|
||||
fetchCourseUnitQuery,
|
||||
fetchCourseVerticalChildrenData,
|
||||
getCourseOutlineInfoQuery,
|
||||
patchUnitItemQuery,
|
||||
updateCourseUnitSidebar,
|
||||
} from './data/thunk';
|
||||
import {
|
||||
getCanEdit,
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
getSavingStatus,
|
||||
getSequenceStatus,
|
||||
getStaticFileNotices,
|
||||
getLoadingStatuses,
|
||||
} from './data/selectors';
|
||||
import {
|
||||
changeEditTitleFormOpen,
|
||||
@@ -51,6 +52,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
|
||||
const [isMoveModalOpen, openMoveModal, closeMoveModal] = useToggle(false);
|
||||
|
||||
const courseUnit = useSelector(getCourseUnitData);
|
||||
const courseUnitLoadingStatus = useSelector(getLoadingStatuses);
|
||||
const savingStatus = useSelector(getSavingStatus);
|
||||
const isLoading = useSelector(getIsLoading);
|
||||
const errorMessage = useSelector(getErrorMessage);
|
||||
@@ -196,7 +198,6 @@ export const useCourseUnit = ({ courseId, blockId }) => {
|
||||
}, [savingStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchCourseUnitQuery(blockId));
|
||||
dispatch(fetchCourseSectionVerticalData(blockId, sequenceId));
|
||||
dispatch(fetchCourseVerticalChildrenData(blockId, isSplitTestType));
|
||||
handleNavigate(sequenceId);
|
||||
@@ -215,9 +216,27 @@ export const useCourseUnit = ({ courseId, blockId }) => {
|
||||
}
|
||||
}, [isMoveModalOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePageRefreshUsingStorage = (event) => {
|
||||
// ignoring tests for if block, because it triggers when someone
|
||||
// edits the component using editor which has a separate store
|
||||
/* istanbul ignore next */
|
||||
if (event.key === 'courseRefreshTriggerOnComponentEditSave') {
|
||||
dispatch(updateCourseUnitSidebar(blockId));
|
||||
localStorage.removeItem(event.key);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('storage', handlePageRefreshUsingStorage);
|
||||
return () => {
|
||||
window.removeEventListener('storage', handlePageRefreshUsingStorage);
|
||||
};
|
||||
}, [blockId, sequenceId, isSplitTestType]);
|
||||
|
||||
return {
|
||||
sequenceId,
|
||||
courseUnit,
|
||||
courseUnitLoadingStatus,
|
||||
unitTitle,
|
||||
unitCategory,
|
||||
errorMessage,
|
||||
|
||||
@@ -10,10 +10,10 @@ import userEvent from '@testing-library/user-event';
|
||||
import initializeStore from '../../../../store';
|
||||
import { executeThunk } from '../../../../utils';
|
||||
import { clipboardUnit } from '../../../../__mocks__';
|
||||
import { getCourseUnitApiUrl } from '../../../data/api';
|
||||
import { getCourseSectionVerticalApiUrl } from '../../../data/api';
|
||||
import { getClipboardUrl } from '../../../../generic/data/api';
|
||||
import { fetchCourseUnitQuery } from '../../../data/thunk';
|
||||
import { courseUnitIndexMock } from '../../../__mocks__';
|
||||
import { fetchCourseSectionVerticalData } from '../../../data/thunk';
|
||||
import { courseSectionVerticalMock } from '../../../__mocks__';
|
||||
import messages from '../../messages';
|
||||
import ActionButtons from './ActionButtons';
|
||||
|
||||
@@ -46,8 +46,14 @@ describe('<ActionButtons />', () => {
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock
|
||||
.onGet(getCourseUnitApiUrl(courseId))
|
||||
.reply(200, { ...courseUnitIndexMock, enable_copy_paste_units: true });
|
||||
.onGet(getCourseSectionVerticalApiUrl(courseId))
|
||||
.reply(200, {
|
||||
...courseSectionVerticalMock,
|
||||
xblock_info: {
|
||||
...courseSectionVerticalMock.xblock_info,
|
||||
enable_copy_paste_units: true,
|
||||
},
|
||||
});
|
||||
axiosMock
|
||||
.onPost(getClipboardUrl())
|
||||
.reply(200, clipboardUnit);
|
||||
@@ -57,7 +63,7 @@ describe('<ActionButtons />', () => {
|
||||
|
||||
queryClient = new QueryClient();
|
||||
|
||||
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
|
||||
await executeThunk(fetchCourseSectionVerticalData(courseId), store.dispatch);
|
||||
});
|
||||
|
||||
it('render ActionButtons component with Copy to clipboard', () => {
|
||||
@@ -74,7 +80,9 @@ describe('<ActionButtons />', () => {
|
||||
|
||||
userEvent.click(copyXBlockBtn);
|
||||
expect(axiosMock.history.post.length).toBe(1);
|
||||
expect(axiosMock.history.post[0].data).toBe(JSON.stringify({ usage_key: courseUnitIndexMock.id }));
|
||||
expect(axiosMock.history.post[0].data).toBe(
|
||||
JSON.stringify({ usage_key: courseSectionVerticalMock.xblock_info.id }),
|
||||
);
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,4 +99,4 @@ export const getIconVariant = (visibilityState, published, hasChanges) => {
|
||||
* @param {string} id - The course unit ID.
|
||||
* @returns {string} The clear course unit ID extracted from the provided data.
|
||||
*/
|
||||
export const extractCourseUnitId = (id) => id.match(/block@(.+)$/)[1];
|
||||
export const extractCourseUnitId = (id) => id?.match(/block@(.+)$/)[1];
|
||||
|
||||
@@ -37,9 +37,16 @@ import { useIframeContent } from '../../generic/hooks/useIframeContent';
|
||||
import { useIframeMessages } from '../../generic/hooks/useIframeMessages';
|
||||
import VideoSelectorPage from '../../editors/VideoSelectorPage';
|
||||
import EditorPage from '../../editors/EditorPage';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
|
||||
const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
courseId, blockId, unitXBlockActions, courseVerticalChildren, handleConfigureSubmit, isUnitVerticalType,
|
||||
courseId,
|
||||
blockId,
|
||||
unitXBlockActions,
|
||||
courseVerticalChildren,
|
||||
handleConfigureSubmit,
|
||||
isUnitVerticalType,
|
||||
courseUnitLoadingStatus,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
@@ -70,6 +77,23 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
setIframeRef(iframeRef);
|
||||
}, [setIframeRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const iframe = iframeRef?.current;
|
||||
if (!iframe) { return undefined; }
|
||||
|
||||
const handleIframeLoad = () => {
|
||||
if (courseUnitLoadingStatus.fetchUnitLoadingStatus === RequestStatus.FAILED) {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
iframe.addEventListener('load', handleIframeLoad);
|
||||
|
||||
return () => {
|
||||
iframe.removeEventListener('load', handleIframeLoad);
|
||||
};
|
||||
}, [iframeRef]);
|
||||
|
||||
const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
|
||||
closeXBlockEditorModal();
|
||||
closeVideoSelectorModal();
|
||||
|
||||
@@ -42,6 +42,11 @@ export interface XBlockContainerIframeProps {
|
||||
courseId: string;
|
||||
blockId: string;
|
||||
isUnitVerticalType: boolean,
|
||||
courseUnitLoadingStatus: {
|
||||
fetchUnitLoadingStatus: string;
|
||||
fetchVerticalChildrenLoadingStatus: string;
|
||||
fetchXBlockDataLoadingStatus: string;
|
||||
};
|
||||
unitXBlockActions: {
|
||||
handleDelete: (XBlockId: string | null) => void;
|
||||
handleDuplicate: (XBlockId: string | null) => void;
|
||||
|
||||
@@ -125,6 +125,16 @@ export const saveBlock = (content, returnToUnit) => (dispatch) => {
|
||||
content,
|
||||
onSuccess: (response) => {
|
||||
dispatch(actions.app.setSaveResponse(response));
|
||||
const parsedData = JSON.parse(response.config.data);
|
||||
if (parsedData?.has_changes || !('has_changes' in parsedData)) {
|
||||
const storageKey = 'courseRefreshTriggerOnComponentEditSave';
|
||||
sessionStorage.setItem(storageKey, Date.now());
|
||||
|
||||
window.dispatchEvent(new StorageEvent('storage', {
|
||||
key: storageKey,
|
||||
newValue: Date.now().toString(),
|
||||
}));
|
||||
}
|
||||
returnToUnit(response.data);
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -352,7 +352,11 @@ describe('app thunkActions', () => {
|
||||
});
|
||||
it('dispatches actions.app.setSaveResponse with response and then calls returnToUnit', () => {
|
||||
dispatch.mockClear();
|
||||
const response = 'testRESPONSE';
|
||||
const mockParsedData = { has_changes: true };
|
||||
const response = {
|
||||
config: { data: JSON.stringify(mockParsedData) },
|
||||
data: {},
|
||||
};
|
||||
calls[1][0].saveBlock.onSuccess(response);
|
||||
expect(dispatch).toHaveBeenCalledWith(actions.app.setSaveResponse(response));
|
||||
expect(returnToUnit).toHaveBeenCalled();
|
||||
|
||||
@@ -70,15 +70,6 @@ const mockStore = async (
|
||||
}
|
||||
renderComponent();
|
||||
await executeThunk(fetchAssets(courseId), store.dispatch);
|
||||
|
||||
// Finish loading the expected files into the data table before returning,
|
||||
// because loading new files can disrupt things like accessing file menus.
|
||||
if (status === RequestStatus.SUCCESSFUL) {
|
||||
const numFiles = skipNextPageFetch ? 13 : 15;
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(`Showing ${numFiles} of ${numFiles}`)).toBeInTheDocument();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const emptyMockStore = async (status) => {
|
||||
|
||||
@@ -28,7 +28,7 @@ const slice = createSlice({
|
||||
if (isEmpty(state.assetIds)) {
|
||||
state.assetIds = payload.assetIds;
|
||||
} else {
|
||||
state.assetIds = [...state.assetIds, ...payload.assetIds];
|
||||
state.assetIds = [...new Set([...state.assetIds, ...payload.assetIds])];
|
||||
}
|
||||
},
|
||||
setSortedAssetIds: (state, { payload }) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
AlertModal,
|
||||
Button,
|
||||
Collapsible,
|
||||
DataTableContext,
|
||||
Hyperlink,
|
||||
Truncate,
|
||||
} from '@openedx/paragon';
|
||||
@@ -22,6 +23,13 @@ const DeleteConfirmationModal = ({
|
||||
// injected
|
||||
intl,
|
||||
}) => {
|
||||
const { clearSelection } = useContext(DataTableContext);
|
||||
|
||||
const handleConfirmDeletion = () => {
|
||||
handleBulkDelete();
|
||||
clearSelection();
|
||||
};
|
||||
|
||||
const firstSelectedRow = selectedRows[0]?.original;
|
||||
let activeContentRows = [];
|
||||
if (Array.isArray(selectedRows)) {
|
||||
@@ -73,7 +81,7 @@ const DeleteConfirmationModal = ({
|
||||
<Button variant="tertiary" onClick={closeDeleteConfirmation}>
|
||||
{intl.formatMessage(messages.cancelButtonLabel)}
|
||||
</Button>
|
||||
<Button onClick={handleBulkDelete}>
|
||||
<Button onClick={handleConfirmDeletion}>
|
||||
{intl.formatMessage(messages.deleteFileButtonLabel)}
|
||||
</Button>
|
||||
</ActionRow>
|
||||
|
||||
@@ -273,6 +273,16 @@ const FileTable = ({
|
||||
setSelectedRows={setSelectedRows}
|
||||
fileType={fileType}
|
||||
/>
|
||||
|
||||
<DeleteConfirmationModal
|
||||
{...{
|
||||
isDeleteConfirmationOpen,
|
||||
closeDeleteConfirmation,
|
||||
handleBulkDelete,
|
||||
selectedRows,
|
||||
fileType,
|
||||
}}
|
||||
/>
|
||||
</DataTable>
|
||||
<FileInput key="generic-file-upload" fileInput={fileInputControl} supportedFileFormats={supportedFileFormats} />
|
||||
{!isEmpty(selectedRows) && (
|
||||
@@ -286,15 +296,7 @@ const FileTable = ({
|
||||
sidebar={infoModalSidebar}
|
||||
/>
|
||||
)}
|
||||
<DeleteConfirmationModal
|
||||
{...{
|
||||
isDeleteConfirmationOpen,
|
||||
closeDeleteConfirmation,
|
||||
handleBulkDelete,
|
||||
selectedRows,
|
||||
fileType,
|
||||
}}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,13 +26,18 @@ const TableActions = ({
|
||||
intl,
|
||||
}) => {
|
||||
const [isSortOpen, openSort, closeSort] = useToggle(false);
|
||||
const { state } = useContext(DataTableContext);
|
||||
const { state, clearSelection } = useContext(DataTableContext);
|
||||
|
||||
// This useEffect saves DataTable state so it can persist after table re-renders due to data reload.
|
||||
useEffect(() => {
|
||||
setInitialState(state);
|
||||
}, [state]);
|
||||
|
||||
const handleOpenFileSelector = () => {
|
||||
fileInputControl.click();
|
||||
clearSelection();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="outline-primary" onClick={openSort} iconBefore={Tune}>
|
||||
@@ -71,7 +76,7 @@ const TableActions = ({
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
<Button iconBefore={Add} onClick={fileInputControl.click}>
|
||||
<Button iconBefore={Add} onClick={handleOpenFileSelector}>
|
||||
{intl.formatMessage(messages.addFilesButtonLabel, { fileType })}
|
||||
</Button>
|
||||
<SortAndFilterModal {...{ isSortOpen, closeSort, handleSort }} />
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import React from 'react';
|
||||
import { screen, fireEvent } from '@testing-library/react';
|
||||
import { DataTableContext } from '@openedx/paragon';
|
||||
import { initializeMocks, render } from '../../../testUtils';
|
||||
import TableActions from './TableActions';
|
||||
import messages from '../messages';
|
||||
|
||||
const defaultProps = {
|
||||
selectedFlatRows: [],
|
||||
fileInputControl: { click: jest.fn() },
|
||||
handleOpenDeleteConfirmation: jest.fn(),
|
||||
handleBulkDownload: jest.fn(),
|
||||
encodingsDownloadUrl: null,
|
||||
handleSort: jest.fn(),
|
||||
fileType: 'video',
|
||||
setInitialState: jest.fn(),
|
||||
intl: {
|
||||
formatMessage: (msg, values) => msg.defaultMessage.replace('{fileType}', values?.fileType ?? ''),
|
||||
},
|
||||
};
|
||||
|
||||
const mockColumns = [
|
||||
{
|
||||
id: 'wrapperType',
|
||||
Header: 'Type',
|
||||
accessor: 'wrapperType',
|
||||
filter: 'includes',
|
||||
},
|
||||
];
|
||||
|
||||
const renderWithContext = (props = {}, contextOverrides = {}) => {
|
||||
const contextValue = {
|
||||
state: {
|
||||
selectedRowIds: {},
|
||||
filters: [],
|
||||
...contextOverrides.state,
|
||||
},
|
||||
clearSelection: jest.fn(),
|
||||
gotoPage: jest.fn(),
|
||||
setAllFilters: jest.fn(),
|
||||
columns: mockColumns,
|
||||
...contextOverrides,
|
||||
};
|
||||
|
||||
return render(
|
||||
<DataTableContext.Provider value={contextValue}>
|
||||
<TableActions {...defaultProps} {...props} />
|
||||
</DataTableContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
describe('TableActions', () => {
|
||||
beforeEach(() => {
|
||||
initializeMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('renders buttons and dropdown', () => {
|
||||
renderWithContext();
|
||||
|
||||
expect(screen.getByRole('button', { name: messages.sortButtonLabel.defaultMessage })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: messages.actionsButtonLabel.defaultMessage })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: messages.addFilesButtonLabel.defaultMessage.replace('{fileType}', 'video') })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('disables bulk and delete actions if no rows selected', () => {
|
||||
renderWithContext();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: messages.actionsButtonLabel.defaultMessage }));
|
||||
|
||||
const downloadOption = screen.getByText(messages.downloadTitle.defaultMessage);
|
||||
const deleteButton = screen.getByTestId('open-delete-confirmation-button');
|
||||
|
||||
expect(downloadOption).toHaveAttribute('aria-disabled', 'true');
|
||||
expect(downloadOption).toHaveClass('disabled');
|
||||
|
||||
expect(deleteButton).toHaveAttribute('aria-disabled', 'true');
|
||||
expect(deleteButton).toHaveClass('disabled');
|
||||
});
|
||||
|
||||
test('enables bulk and delete actions when rows are selected', () => {
|
||||
renderWithContext({
|
||||
selectedFlatRows: [{ original: { id: '1', displayName: 'Video 1', wrapperType: 'video' } }],
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: messages.actionsButtonLabel.defaultMessage }));
|
||||
expect(screen.getByText(messages.downloadTitle.defaultMessage)).not.toBeDisabled();
|
||||
expect(screen.getByTestId('open-delete-confirmation-button')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test('calls file input click and clears selection when add button clicked', () => {
|
||||
const mockClick = jest.fn();
|
||||
const mockClear = jest.fn();
|
||||
|
||||
renderWithContext({ fileInputControl: { click: mockClick } }, {}, mockClear);
|
||||
fireEvent.click(screen.getByRole('button', { name: messages.addFilesButtonLabel.defaultMessage.replace('{fileType}', 'video') }));
|
||||
expect(mockClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('opens sort modal when sort button clicked', () => {
|
||||
renderWithContext();
|
||||
fireEvent.click(screen.getByRole('button', { name: messages.sortButtonLabel.defaultMessage }));
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('calls handleBulkDownload when selected and clicked', () => {
|
||||
const handleBulkDownload = jest.fn();
|
||||
renderWithContext({
|
||||
selectedFlatRows: [{ original: { id: '1', displayName: 'Video 1', wrapperType: 'video' } }],
|
||||
handleBulkDownload,
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: messages.actionsButtonLabel.defaultMessage }));
|
||||
fireEvent.click(screen.getByText(messages.downloadTitle.defaultMessage));
|
||||
expect(handleBulkDownload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('calls handleOpenDeleteConfirmation when clicked', () => {
|
||||
const handleOpenDeleteConfirmation = jest.fn();
|
||||
const selectedFlatRows = [{ original: { id: '1', displayName: 'Video 1', wrapperType: 'video' } }];
|
||||
renderWithContext({
|
||||
selectedFlatRows,
|
||||
handleOpenDeleteConfirmation,
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: messages.actionsButtonLabel.defaultMessage }));
|
||||
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
|
||||
expect(handleOpenDeleteConfirmation).toHaveBeenCalledWith(selectedFlatRows);
|
||||
});
|
||||
|
||||
test('shows encoding download link when provided', () => {
|
||||
const encodingsDownloadUrl = '/some/path/to/encoding.zip';
|
||||
renderWithContext({ encodingsDownloadUrl });
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: messages.actionsButtonLabel.defaultMessage }));
|
||||
expect(screen.getByRole('link', { name: messages.downloadEncodingsTitle.defaultMessage })).toHaveAttribute('href', expect.stringContaining(encodingsDownloadUrl));
|
||||
});
|
||||
});
|
||||
@@ -48,21 +48,30 @@ const VideoThumbnail = ({
|
||||
const isFailed = VIDEO_FAILURE_STATUSES.includes(status);
|
||||
const failedMessage = intl.formatMessage(messages.failedCheckboxLabel);
|
||||
|
||||
const showThumbnail = allowThumbnailUpload && thumbnail && isUploaded;
|
||||
const showThumbnail = allowThumbnailUpload && isUploaded;
|
||||
|
||||
return (
|
||||
<div className="video-thumbnail row justify-content-center align-itmes-center">
|
||||
{allowThumbnailUpload && showThumbnail && <div className="thumbnail-overlay" />}
|
||||
{allowThumbnailUpload && isUploaded && <div className="thumbnail-overlay" />}
|
||||
{showThumbnail && !thumbnailError && pageLoadStatus === RequestStatus.SUCCESSFUL ? (
|
||||
<>
|
||||
<div className="border rounded">
|
||||
<Image
|
||||
style={imageSize}
|
||||
className="m-1 bg-light-300"
|
||||
src={thumbnail}
|
||||
alt={intl.formatMessage(messages.thumbnailAltMessage, { displayName })}
|
||||
onError={() => setThumbnailError(true)}
|
||||
/>
|
||||
{ thumbnail ? (
|
||||
<Image
|
||||
style={imageSize}
|
||||
className="m-1 bg-light-300"
|
||||
src={thumbnail}
|
||||
alt={intl.formatMessage(messages.thumbnailAltMessage, { displayName })}
|
||||
onError={() => setThumbnailError(true)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="row justify-content-center align-items-center m-0"
|
||||
style={imageSize}
|
||||
>
|
||||
<Icon src={VideoFile} style={{ height: '48px', width: '48px' }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="add-thumbnail" data-testid={`video-thumbnail-${id}`}>
|
||||
<Button
|
||||
|
||||
32
src/files-and-videos/videos-page/VideoThumbnail.test.jsx
Normal file
32
src/files-and-videos/videos-page/VideoThumbnail.test.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import VideoThumbnail from './VideoThumbnail';
|
||||
import { VIDEO_SUCCESS_STATUSES } from './data/constants';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
|
||||
it('shows fallback icon if thumbnail fails to load', () => {
|
||||
const { container } = render(
|
||||
<IntlProvider locale="en">
|
||||
<VideoThumbnail
|
||||
thumbnail="http://bad-url/image.png"
|
||||
displayName="Test Video"
|
||||
id="vid1"
|
||||
imageSize={{ width: '100px', height: '100px' }}
|
||||
handleAddThumbnail={jest.fn()}
|
||||
videoImageSettings={{ videoImageUploadEnabled: true, supportedFileFormats: { jpg: 'image/jpg' } }}
|
||||
status={VIDEO_SUCCESS_STATUSES[0]}
|
||||
pageLoadStatus={RequestStatus.SUCCESSFUL}
|
||||
/>
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const image = screen.getByRole('img', { name: /video thumbnail/i });
|
||||
expect(image).toBeInTheDocument();
|
||||
fireEvent.error(image);
|
||||
|
||||
expect(screen.queryByRole('img', { name: /video thumbnail/i })).toBeNull();
|
||||
|
||||
const fallbackSvg = container.querySelector('svg[role="img"]');
|
||||
expect(fallbackSvg).toBeInTheDocument();
|
||||
});
|
||||
@@ -19,7 +19,7 @@ const messages = defineMessages({
|
||||
},
|
||||
previewNotAvailable: {
|
||||
id: 'course-authoring.library-authoring.component-comparison.preview-not-available',
|
||||
defaultMessage: 'Preview not available',
|
||||
defaultMessage: 'Preview not available for unit changes at this time',
|
||||
description: 'Message shown when preview is not available.',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -92,7 +92,7 @@ const PagesAndResources = ({ courseId }) => {
|
||||
<Route path=":appId/settings" element={<PageWrap><SettingsComponent url={redirectUrl} /></PageWrap>} />
|
||||
</Routes>
|
||||
|
||||
<PageGrid pages={pages} pluginSlotComponent={AdditionalCoursePluginSlot} courseId={courseId} />
|
||||
<PageGrid pages={pages} pluginSlotComponent={<AdditionalCoursePluginSlot />} courseId={courseId} />
|
||||
{
|
||||
(contentPermissionsPages.length > 0 || hasAdditionalCoursePlugin)
|
||||
&& (
|
||||
@@ -100,7 +100,7 @@ const PagesAndResources = ({ courseId }) => {
|
||||
<div className="d-flex justify-content-between my-4 my-md-5 align-items-center">
|
||||
<h3 className="m-0">{intl.formatMessage(messages.contentPermissions)}</h3>
|
||||
</div>
|
||||
<PageGrid pages={contentPermissionsPages} pluginSlotComponent={AdditionalCourseContentPluginSlot} />
|
||||
<PageGrid pages={contentPermissionsPages} pluginSlotComponent={<AdditionalCourseContentPluginSlot />} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,39 @@
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import { getConfig, setConfig } from '@edx/frontend-platform';
|
||||
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';
|
||||
import { PagesAndResources } from '.';
|
||||
import { render } from './utils.test';
|
||||
|
||||
const mockPlugin = (identifier) => ({
|
||||
plugins: [
|
||||
{
|
||||
op: PLUGIN_OPERATIONS.Insert,
|
||||
widget: {
|
||||
id: 'mock-plugin-1',
|
||||
type: DIRECT_PLUGIN,
|
||||
priority: 1,
|
||||
RenderWidget: () => <div data-testid={identifier}>HELLO</div>,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||
|
||||
describe('PagesAndResources', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setConfig({
|
||||
...getConfig(),
|
||||
pluginSlots: {
|
||||
'org.openedx.frontend.authoring.additional_course_plugin.v1': mockPlugin('additional_course_plugin'),
|
||||
'org.openedx.frontend.authoring.additional_course_content_plugin.v1': mockPlugin('additional_course_content_plugin'),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('doesn\'t show content permissions section if relevant apps are not enabled', () => {
|
||||
it('doesn\'t show content permissions section if relevant apps are not enabled', async () => {
|
||||
const initialState = {
|
||||
models: {
|
||||
courseApps: {},
|
||||
@@ -25,8 +48,11 @@ describe('PagesAndResources', () => {
|
||||
{ preloadedState: initialState },
|
||||
);
|
||||
|
||||
expect(screen.queryByRole('heading', { name: 'Content permissions' })).not.toBeInTheDocument();
|
||||
await waitFor(() => expect(screen.queryByRole('heading', { name: 'Content permissions' })).not.toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.queryByTestId('additional_course_plugin')).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.queryByTestId('additional_course_content_plugin')).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('show content permissions section if Learning Assistant app is enabled', async () => {
|
||||
const initialState = {
|
||||
models: {
|
||||
@@ -56,6 +82,8 @@ describe('PagesAndResources', () => {
|
||||
|
||||
await waitFor(() => expect(screen.getByRole('heading', { name: 'Content permissions' })).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.getByText('Learning Assistant')).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.queryByTestId('additional_course_plugin')).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.queryByTestId('additional_course_content_plugin')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('show content permissions section if Xpert learning summaries app is enabled', async () => {
|
||||
@@ -89,5 +117,7 @@ describe('PagesAndResources', () => {
|
||||
|
||||
await waitFor(() => expect(screen.getByRole('heading', { name: 'Content permissions' })).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.getByText('Xpert unit summaries')).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.queryByTestId('additional_course_plugin')).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.queryByTestId('additional_course_content_plugin')).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
### Slot ID: `org.openedx.frontend.authoring.additional_course_content_plugin.v1`
|
||||
|
||||
### Slot ID Aliases
|
||||
* `additional_course_content_plugin`
|
||||
* `additional_course_content_plugin`
|
||||
@@ -1,5 +1,4 @@
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework/dist';
|
||||
import React from 'react';
|
||||
|
||||
export const AdditionalCourseContentPluginSlot = () => (
|
||||
<PluginSlot
|
||||
|
||||
@@ -1,6 +1,64 @@
|
||||
# AdditionalCoursePluginSlot
|
||||
# Additional Course Plugin Slot
|
||||
|
||||
### Slot ID: `org.openedx.frontend.authoring.additional_course_plugin.v1`
|
||||
|
||||
### Slot ID Aliases
|
||||
* `additional_course_plugin`
|
||||
|
||||
## Description
|
||||
|
||||
This slot is used to add a custom card on the the page & resources page.
|
||||
|
||||
## Example
|
||||
|
||||
The following `env.config.jsx` will add a custom card at the end of the page & resources section.
|
||||
|
||||

|
||||
|
||||
```jsx
|
||||
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||
import { Badge, Card } from '@openedx/paragon';
|
||||
import { Settings } from '@openedx/paragon/icons';
|
||||
|
||||
const config = {
|
||||
pluginSlots: {
|
||||
'org.openedx.frontend.authoring.additional_course_plugin.v1': {
|
||||
plugins: [
|
||||
{
|
||||
op: PLUGIN_OPERATIONS.Hide,
|
||||
widgetId: 'default_contents',
|
||||
},
|
||||
{
|
||||
op: PLUGIN_OPERATIONS.Insert,
|
||||
widget: {
|
||||
id: 'custom_additional_course',
|
||||
type: DIRECT_PLUGIN,
|
||||
RenderWidget: () => (
|
||||
<Card className={'shadow justify-content-between'} >
|
||||
<Card.Header
|
||||
title={'Additional Course'}
|
||||
subtitle={(
|
||||
<Badge variant="success" className="mt-1">
|
||||
slot props course
|
||||
</Badge>
|
||||
)}
|
||||
actions={<Settings />}
|
||||
size="sm"
|
||||
/>
|
||||
<Card.Body>
|
||||
<Card.Section>
|
||||
Additional course from slot props description.
|
||||
Or anything else.
|
||||
</Card.Section>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default config;
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 324 KiB |
@@ -1,5 +1,4 @@
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework/dist';
|
||||
import React from 'react';
|
||||
|
||||
export const AdditionalCoursePluginSlot = () => (
|
||||
<PluginSlot
|
||||
|
||||
@@ -13,3 +13,54 @@
|
||||
* `additionalProps` - Object
|
||||
* `transcriptType` - String
|
||||
* `isAiTranslationsEnabled` - Boolean
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
This slot is used to add a custom block in the **Video Transcription Settings** drawer.
|
||||
|
||||
## Example
|
||||
|
||||
The following `env.config.jsx` will add a custom transcript option in the Transcript Settings drawer.
|
||||
|
||||

|
||||
|
||||
```jsx
|
||||
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||
import { Collapsible, Icon } from '@openedx/paragon';
|
||||
import { ChevronRight } from '@openedx/paragon/icons';
|
||||
|
||||
const TranslationsBlock = ({ setIsAiTranslations, courseId }) => (
|
||||
<div key="transcript-type-selection" className="mt-3">
|
||||
<Collapsible.Advanced
|
||||
onOpen={() => setIsAiTranslations(courseId === 'anyId')}
|
||||
>
|
||||
<Collapsible.Trigger
|
||||
className="row m-0 justify-content-between align-items-center"
|
||||
>
|
||||
Custom transcript 💬
|
||||
<Icon src={ChevronRight} />
|
||||
</Collapsible.Trigger>
|
||||
</Collapsible.Advanced>
|
||||
</div>
|
||||
);
|
||||
|
||||
const config = {
|
||||
pluginSlots: {
|
||||
'org.openedx.frontend.authoring.video_transcript_additional_translations_component.v1': {
|
||||
plugins: [
|
||||
{
|
||||
op: PLUGIN_OPERATIONS.Insert,
|
||||
widget: {
|
||||
id: 'custom_additional_translation_id',
|
||||
type: DIRECT_PLUGIN,
|
||||
RenderWidget: TranslationsBlock,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Reference in New Issue
Block a user