+
{intl.formatMessage(messages.moveModalCategoryIndicatorAccessibilityText, { categoryText, displayName })}
@@ -150,9 +150,11 @@ const MoveModal: FC
= ({
{!childrenInfo.children?.length
? getEmptyMessage()
- : childrenInfo.children.map((xBlock: IXBlock | IXBlockInfo, index: number) => {
- return getCourseStructureListItem(xBlock as IXBlock, index);
- })}
+ : childrenInfo.children.map(
+ (xBlock: IXBlock | IXBlockInfo, index: number) => (
+ getCourseStructureListItem(xBlock as IXBlock, index)
+ ),
+ )}
>
diff --git a/src/course-unit/move-modal/interfaces.ts b/src/course-unit/move-modal/interfaces.ts
index d1574053cc..023161b54e 100644
--- a/src/course-unit/move-modal/interfaces.ts
+++ b/src/course-unit/move-modal/interfaces.ts
@@ -1,89 +1,83 @@
export interface IXBlockInfo {
- id: string;
- displayName: string;
- child_info?: {
- children?: IXBlockInfo[];
- };
- category?: string;
- has_children?: boolean;
+ id: string;
+ displayName: string;
+ child_info?: {
+ children?: IXBlockInfo[];
+ };
+ category?: string;
+ has_children?: boolean;
+ hasChildren?: boolean;
}
export interface IUseMoveModalParams {
- isOpenModal: boolean;
- closeModal: () => void;
- openModal: () => void;
- courseId: string;
+ isOpenModal: boolean;
+ closeModal: () => void;
+ openModal: () => void;
+ courseId: string;
}
export interface IUseMoveModalReturn {
- isLoading: boolean;
- isValidMove: boolean;
- isExtraSmall: boolean;
- parentInfo: {
- parent: IXBlockInfo;
- category: string;
- };
- childrenInfo: {
- children: IXBlockInfo[];
- category: string;
- };
- displayName: string;
- sourceXBlockId: string;
- categoryText: string;
- breadcrumbs: string[];
- currentXBlockParentIds: string[];
- handleXBlockClick: (newParentIndex: string|number) => void;
- handleBreadcrumbsClick: (newParentIndex: string|number) => void;
- handleCLoseModal: () => void;
- handleMoveXBlock: () => void;
+ isLoading: boolean;
+ isValidMove: boolean;
+ isExtraSmall: boolean;
+ parentInfo: {
+ parent: IXBlockInfo;
+ category: string;
+ };
+ childrenInfo: {
+ children: IXBlockInfo[];
+ category: string;
+ };
+ displayName: string;
+ sourceXBlockId: string;
+ categoryText: string;
+ breadcrumbs: string[];
+ currentXBlockParentIds: string[];
+ handleXBlockClick: (newParentIndex: string | number) => void;
+ handleBreadcrumbsClick: (newParentIndex: string | number) => void;
+ handleCLoseModal: () => void;
+ handleMoveXBlock: () => void;
}
export interface IState {
- sourceXBlockInfo: {
- current: IXBlockInfo;
- parent: IXBlockInfo;
- };
- childrenInfo: {
- children: IXBlockInfo[];
- category: string;
- };
- parentInfo: {
- parent: IXBlockInfo;
- category: string;
- };
- visitedAncestors: IXBlockInfo[];
- isValidMove: boolean;
+ sourceXBlockInfo: {
+ current: IXBlockInfo;
+ parent: IXBlockInfo;
+ };
+ childrenInfo: {
+ children: IXBlockInfo[];
+ category: string;
+ };
+ parentInfo: {
+ parent: IXBlockInfo;
+ category: string;
+ };
+ visitedAncestors: IXBlockInfo[];
+ isValidMove: boolean;
}
export interface ITreeNode {
- id: string;
- child_info?: {
- children?: ITreeNode[];
- };
-}
-
-export interface IXBlockInfo {
- id: string,
- category?: string;
- hasChildren?: boolean;
- has_children?: boolean;
+ id: string;
+ child_info?: {
+ children?: ITreeNode[];
+ };
}
export interface IAncestor {
- category?: string;
- display_name?: string;
+ category?: string;
+ display_name?: string;
}
export interface IXBlockChildInfo {
- category?: string;
- display_name?: string;
- children?: IXBlock[];
+ category?: string;
+ display_name?: string;
+ children?: IXBlock[];
}
export interface IXBlock {
- id: string;
- display_name: string;
- category: string;
- has_children: boolean;
- child_info?: IXBlockChildInfo;
+ id: string;
+ display_name: string;
+ category: string;
+ has_children: boolean;
+ child_info?: IXBlockChildInfo;
}
diff --git a/src/course-unit/move-modal/moveModal.test.tsx b/src/course-unit/move-modal/moveModal.test.tsx
index b801b7d120..bb759e36c4 100644
--- a/src/course-unit/move-modal/moveModal.test.tsx
+++ b/src/course-unit/move-modal/moveModal.test.tsx
@@ -6,6 +6,7 @@ import { initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { Store } from 'redux';
+import userEvent from '@testing-library/user-event';
import initializeStore from '../../store';
import { getOutlineInfo } from '../data/api';
import { courseOutlineInfoMock } from '../__mocks__';
@@ -14,7 +15,6 @@ import { getCourseOutlineInfoQuery } from '../data/thunk';
import { IframeProvider } from '../context/iFrameContext';
import MoveModal from './index';
import messages from './messages';
-import userEvent from '@testing-library/user-event';
interface CourseOutlineChildInfo {
category: string;
@@ -96,8 +96,12 @@ describe('
', () => {
const categoryIndicator: HTMLElement = getByTestId('move-xblock-modal-category');
expect(getByText(messages.moveModalTitle.defaultMessage.replace(' {displayName}', ''))).toBeInTheDocument();
- expect(within(breadcrumbs).getByText(messages.moveModalBreadcrumbsBaseCategory.defaultMessage)).toBeInTheDocument();
- expect(within(categoryIndicator).getByText(messages.moveModalBreadcrumbsSections.defaultMessage)).toBeInTheDocument();
+ expect(
+ within(breadcrumbs).getByText(messages.moveModalBreadcrumbsBaseCategory.defaultMessage),
+ ).toBeInTheDocument();
+ expect(
+ within(categoryIndicator).getByText(messages.moveModalBreadcrumbsSections.defaultMessage),
+ ).toBeInTheDocument();
expect(getByRole('button', { name: messages.moveModalSubmitButton.defaultMessage })).toBeInTheDocument();
expect(getByRole('button', { name: messages.moveModalCancelButton.defaultMessage })).toBeInTheDocument();
});
@@ -107,35 +111,45 @@ describe('
', () => {
const breadcrumbs: HTMLElement = getByTestId('move-xblock-modal-breadcrumbs');
const categoryIndicator: HTMLElement = getByTestId('move-xblock-modal-category');
- expect(within(breadcrumbs).getByText(messages.moveModalBreadcrumbsBaseCategory.defaultMessage)).toBeInTheDocument();
- expect(within(categoryIndicator).getByText(messages.moveModalBreadcrumbsSections.defaultMessage)).toBeInTheDocument();
+ expect(
+ within(breadcrumbs).getByText(messages.moveModalBreadcrumbsBaseCategory.defaultMessage),
+ ).toBeInTheDocument();
+ expect(
+ within(categoryIndicator).getByText(messages.moveModalBreadcrumbsSections.defaultMessage),
+ ).toBeInTheDocument();
sections.map((section) => (
expect(getByText(section.display_name)).toBeInTheDocument()
));
await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(sections[1].display_name, 'i') })));
await waitFor(() => {
- expect(within(categoryIndicator).getByText(messages.moveModalBreadcrumbsSubsections.defaultMessage)).toBeInTheDocument();
+ expect(
+ within(categoryIndicator).getByText(messages.moveModalBreadcrumbsSubsections.defaultMessage),
+ ).toBeInTheDocument();
expect(within(breadcrumbs).getByText(sections[1].display_name)).toBeInTheDocument();
subsections.map((subsection) => (
- expect(getByRole('button', { name: new RegExp(subsection.display_name, 'i') })).toBeInTheDocument()
+ expect(getByRole('button', { name: new RegExp(subsection.display_name, 'i') })).toBeInTheDocument()
));
});
await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(subsections[1].display_name, 'i') })));
await waitFor(() => {
- expect(within(categoryIndicator).getByText(messages.moveModalBreadcrumbsUnits.defaultMessage)).toBeInTheDocument();
+ expect(
+ within(categoryIndicator).getByText(messages.moveModalBreadcrumbsUnits.defaultMessage),
+ ).toBeInTheDocument();
expect(within(breadcrumbs).getByText(subsections[1].display_name)).toBeInTheDocument();
units.map((unit) => (
- expect(getByRole('button', { name: new RegExp(unit.display_name, 'i') })).toBeInTheDocument()
+ expect(getByRole('button', { name: new RegExp(unit.display_name, 'i') })).toBeInTheDocument()
));
});
await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(units[0].display_name, 'i') })));
await waitFor(() => {
- expect(within(categoryIndicator).getByText(messages.moveModalBreadcrumbsComponents.defaultMessage)).toBeInTheDocument();
+ expect(
+ within(categoryIndicator).getByText(messages.moveModalBreadcrumbsComponents.defaultMessage),
+ ).toBeInTheDocument();
expect(within(breadcrumbs).getByText(units[0].display_name)).toBeInTheDocument();
- components.map((component) => {
+ components.forEach((component) => {
if (component.display_name) {
expect(getByText(component.display_name)).toBeInTheDocument();
}
@@ -148,15 +162,17 @@ describe('
', () => {
const breadcrumbs: HTMLElement = getByTestId('move-xblock-modal-breadcrumbs');
const categoryIndicator: HTMLElement = getByTestId('move-xblock-modal-category');
- await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(sections[1].display_name, 'i') })));
- await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(subsections[1].display_name, 'i') })));
- await waitFor(() => userEvent.click(within(breadcrumbs).getByText(sections[1].display_name)));
+ await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(sections[1].display_name, 'i') })));
+ await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(subsections[1].display_name, 'i') })));
+ await waitFor(() => userEvent.click(within(breadcrumbs).getByText(sections[1].display_name)));
await waitFor(() => {
- expect(within(categoryIndicator).getByText(messages.moveModalBreadcrumbsSubsections.defaultMessage)).toBeInTheDocument();
+ expect(
+ within(categoryIndicator).getByText(messages.moveModalBreadcrumbsSubsections.defaultMessage),
+ ).toBeInTheDocument();
expect(within(breadcrumbs).getByText(sections[1].display_name)).toBeInTheDocument();
subsections.map((subsection) => (
- expect(getByRole('button', { name: new RegExp(subsection.display_name, 'i') })).toBeInTheDocument()
+ expect(getByRole('button', { name: new RegExp(subsection.display_name, 'i') })).toBeInTheDocument()
));
});
});
@@ -164,15 +180,15 @@ describe('
', () => {
it('renders empty message when no components are provided', async () => {
const { getByText, getByRole } = renderComponent();
- await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(sections[1].display_name, 'i') } )));
- await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(subsections[1].display_name, 'i') } )));
- await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(units[7].display_name, 'i') } )));
+ await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(sections[1].display_name, 'i') })));
+ await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(subsections[1].display_name, 'i') })));
+ await waitFor(() => userEvent.click(getByRole('button', { name: new RegExp(units[7].display_name, 'i') })));
await waitFor(() => {
expect(getByText(
- messages.moveModalEmptyCategoryText.defaultMessage
- .replace('{category}', 'unit')
- .replace('{categoryText}', 'components')
+ messages.moveModalEmptyCategoryText.defaultMessage
+ .replace('{category}', 'unit')
+ .replace('{categoryText}', 'components'),
)).toBeInTheDocument();
});
});
diff --git a/src/course-unit/move-modal/utils.ts b/src/course-unit/move-modal/utils.ts
index 4c2e7bb704..decf0c4906 100644
--- a/src/course-unit/move-modal/utils.ts
+++ b/src/course-unit/move-modal/utils.ts
@@ -37,20 +37,20 @@ export const findParentIds = (
): string[] => {
let path: string[] = [];
- function traverse(node: ITreeNode | undefined, targetId: string, currentPath: string[]): boolean {
+ function traverse(node: ITreeNode | undefined, id: string, currentPath: string[]): boolean {
if (!node) {
return false;
}
currentPath.push(node.id);
- if (node.id === targetId) {
+ if (node.id === id) {
path = currentPath.slice();
return true;
}
for (const child of node.child_info?.children ?? []) {
- if (traverse(child, targetId, currentPath)) {
+ if (traverse(child, id, currentPath)) {
return true;
}
}
@@ -73,8 +73,8 @@ export const isValidCategory = (
sourceParentInfo: IXBlockInfo,
targetParentInfo: IXBlockInfo,
): boolean => {
- let { category: sourceParentCategory, hasChildren: sourceParentHasChildren } = sourceParentInfo;
- let { category: targetParentCategory, has_children: targetParentHasChildren } = targetParentInfo;
+ const { category: sourceParentCategory, hasChildren: sourceParentHasChildren } = sourceParentInfo;
+ const { category: targetParentCategory, has_children: targetParentHasChildren } = targetParentInfo;
if (
sourceParentHasChildren
diff --git a/src/course-unit/utils.ts b/src/course-unit/utils.ts
new file mode 100644
index 0000000000..68adaf191a
--- /dev/null
+++ b/src/course-unit/utils.ts
@@ -0,0 +1,33 @@
+import { createCorrectInternalRoute } from '../utils';
+
+/**
+ * Adapts API URL paths to the application's internal URL format based on predefined conditions.
+ *
+ * @param {Object} params - Parameters for URL adaptation.
+ * @param {string} params.url - The original API URL to transform.
+ * @param {string} params.courseId - The course ID.
+ * @param {string} params.sequenceId - The sequence ID.
+ * @returns {string} - A correctly formatted internal route for the application.
+ */
+// eslint-disable-next-line import/prefer-default-export
+export const adoptCourseSectionUrl = (
+ { url, courseId, sequenceId }: { url: string, courseId: string, sequenceId: string },
+): string => {
+ let newUrl = url;
+ const urlConditions = [
+ {
+ regex: /^\/container\/(.+)/,
+ transform: ([, unitId]) => `/course/${courseId}/container/${unitId}/${sequenceId}`,
+ },
+ ];
+
+ for (const { regex, transform } of urlConditions) {
+ const match = RegExp(regex).exec(url);
+ if (match) {
+ newUrl = transform([match[0], match[1]]);
+ break;
+ }
+ }
+
+ return createCorrectInternalRoute(newUrl);
+};
diff --git a/src/course-unit/xblock-container-iframe/index.tsx b/src/course-unit/xblock-container-iframe/index.tsx
index 67d625ab1d..761d637750 100644
--- a/src/course-unit/xblock-container-iframe/index.tsx
+++ b/src/course-unit/xblock-container-iframe/index.tsx
@@ -4,7 +4,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import { IFRAME_FEATURE_POLICY } from '../constants';
-import {useIframe} from '../context/hooks';
+import { useIframe } from '../context/hooks';
import { useIFrameBehavior } from './hooks';
import messages from './messages';
diff --git a/src/course-unit/xblock-container-iframe/tests/XblockContainerIframe.test.tsx b/src/course-unit/xblock-container-iframe/tests/XblockContainerIframe.test.tsx
index 417c0f2180..b3bee233b8 100644
--- a/src/course-unit/xblock-container-iframe/tests/XblockContainerIframe.test.tsx
+++ b/src/course-unit/xblock-container-iframe/tests/XblockContainerIframe.test.tsx
@@ -5,7 +5,7 @@ import { IntlProvider } from '@edx/frontend-platform/i18n';
import { IFRAME_FEATURE_POLICY } from '../../constants';
import { useIFrameBehavior } from '../hooks';
import XBlockContainerIframe from '..';
-import {IframeProvider} from '../../context/iFrameContext';
+import { IframeProvider } from '../../context/iFrameContext';
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(),
diff --git a/src/generic/configure-modal/ConfigureModal.jsx b/src/generic/configure-modal/ConfigureModal.jsx
index 04c82200df..6a4af78153 100644
--- a/src/generic/configure-modal/ConfigureModal.jsx
+++ b/src/generic/configure-modal/ConfigureModal.jsx
@@ -166,6 +166,7 @@ const ConfigureModal = ({
);
break;
case COURSE_BLOCK_NAMES.vertical.id:
+ case COURSE_BLOCK_NAMES.libraryContent.id:
case COURSE_BLOCK_NAMES.component.id:
// groupAccess should be {partitionId: [group1, group2]} or {} if selectedPartitionIndex === -1
if (data.selectedPartitionIndex >= 0) {
@@ -242,10 +243,12 @@ const ConfigureModal = ({
);
case COURSE_BLOCK_NAMES.vertical.id:
+ case COURSE_BLOCK_NAMES.libraryContent.id:
case COURSE_BLOCK_NAMES.component.id:
return (
for Unit', () => {
expect(getByRole('button', { name: messages.cancelButton.defaultMessage })).toBeInTheDocument();
expect(getByRole('button', { name: messages.saveButton.defaultMessage })).toBeInTheDocument();
+
+ expect(queryByText(messages.discussionEnabledSectionTitle.defaultMessage)).toBeInTheDocument();
+ expect(queryByText(messages.discussionEnabledCheckbox.defaultMessage)).toBeInTheDocument();
+ expect(queryByText(messages.discussionEnabledDescription.defaultMessage)).toBeInTheDocument();
});
});
@@ -278,5 +282,9 @@ describe('
for XBlock', () => {
expect(getByRole('button', { name: messages.cancelButton.defaultMessage })).toBeInTheDocument();
expect(getByRole('button', { name: messages.saveButton.defaultMessage })).toBeInTheDocument();
+
+ expect(queryByText(messages.discussionEnabledSectionTitle.defaultMessage)).not.toBeInTheDocument();
+ expect(queryByText(messages.discussionEnabledCheckbox.defaultMessage)).not.toBeInTheDocument();
+ expect(queryByText(messages.discussionEnabledDescription.defaultMessage)).not.toBeInTheDocument();
});
});
diff --git a/src/generic/configure-modal/UnitTab.jsx b/src/generic/configure-modal/UnitTab.jsx
index c55d17a95b..0f65bb19f8 100644
--- a/src/generic/configure-modal/UnitTab.jsx
+++ b/src/generic/configure-modal/UnitTab.jsx
@@ -11,6 +11,7 @@ import messages from './messages';
const UnitTab = ({
isXBlockComponent,
+ isLibraryContent,
values,
setFieldValue,
showWarning,
@@ -61,7 +62,9 @@ const UnitTab = ({
)}
{userPartitionInfo.selectablePartitions.length > 0 && (
-
+
+
+
@@ -130,22 +133,28 @@ const UnitTab = ({
)}
)}
-
-
-
-
-
-
+ {!isXBlockComponent && (
+ <>
+
+
+
+
+
+
+ >
+ )}
>
);
};
UnitTab.defaultProps = {
isXBlockComponent: false,
+ isLibraryContent: false,
};
UnitTab.propTypes = {
isXBlockComponent: PropTypes.bool,
+ isLibraryContent: PropTypes.bool,
values: PropTypes.shape({
isVisibleToStaffOnly: PropTypes.bool.isRequired,
discussionEnabled: PropTypes.bool.isRequired,
diff --git a/src/generic/configure-modal/messages.js b/src/generic/configure-modal/messages.js
index 41ef703bd8..c7da8ff665 100644
--- a/src/generic/configure-modal/messages.js
+++ b/src/generic/configure-modal/messages.js
@@ -46,6 +46,10 @@ const messages = defineMessages({
id: 'course-authoring.course-outline.configure-modal.visibility-tab.unit-access',
defaultMessage: 'Unit access',
},
+ libraryContentAccess: {
+ id: 'course-authoring.course-outline.configure-modal.visibility-tab.unit-access',
+ defaultMessage: 'Library content access',
+ },
discussionEnabledSectionTitle: {
id: 'course-authoring.course-outline.configure-modal.discussion-enabled.section-title',
defaultMessage: 'Discussion',