- {showOpenUnitButton && (
+ {showOpenButton && (
)}
{!componentPickerMode && !readOnly && (
@@ -148,9 +166,9 @@ const UnitInfo = () => {
{intl.formatMessage(messages.publishContainerButton)}
)}
- {showOpenUnitButton && ( // Check: should we still show this on the unit page?
-
)}
@@ -158,20 +176,28 @@ const UnitInfo = () => {
{renderTab(
- UNIT_INFO_TABS.Preview,
- ,
+ CONTAINER_INFO_TABS.Preview,
intl.formatMessage(messages.previewTabTitle),
+ ,
+ )}
+ {renderTab(
+ CONTAINER_INFO_TABS.Manage,
+ intl.formatMessage(messages.manageTabTitle),
+ ,
+ )}
+ {renderTab(
+ CONTAINER_INFO_TABS.Settings,
+ intl.formatMessage(messages.settingsTabTitle),
+ // TODO: container settings component
)}
- {renderTab(UNIT_INFO_TABS.Manage, , intl.formatMessage(messages.manageTabTitle))}
- {renderTab(UNIT_INFO_TABS.Settings, 'Unit Settings', intl.formatMessage(messages.settingsTabTitle))}
);
};
-export default UnitInfo;
+export default ContainerInfo;
diff --git a/src/library-authoring/containers/ContainerInfoHeader.test.tsx b/src/library-authoring/containers/ContainerInfoHeader.test.tsx
index 6349bfcbf..516cbae69 100644
--- a/src/library-authoring/containers/ContainerInfoHeader.test.tsx
+++ b/src/library-authoring/containers/ContainerInfoHeader.test.tsx
@@ -9,7 +9,7 @@ import {
waitFor,
} from '../../testUtils';
import { LibraryProvider } from '../common/context/LibraryContext';
-import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
+import { SidebarBodyItemId, SidebarProvider } from '../common/context/SidebarContext';
import { mockContentLibrary, mockGetContainerMetadata } from '../data/api.mocks';
import * as api from '../data/api';
import ContainerInfoHeader from './ContainerInfoHeader';
@@ -25,15 +25,15 @@ const {
libraryIdReadOnly,
} = mockContentLibrary;
-const { containerId } = mockGetContainerMetadata;
+const { unitId: containerId } = mockGetContainerMetadata;
const render = (libraryId: string = mockLibraryId) => baseRender(
, {
extraWrapper: ({ children }) => (
{ children }
diff --git a/src/library-authoring/containers/ContainerInfoHeader.tsx b/src/library-authoring/containers/ContainerInfoHeader.tsx
index a53be2065..2e5c78316 100644
--- a/src/library-authoring/containers/ContainerInfoHeader.tsx
+++ b/src/library-authoring/containers/ContainerInfoHeader.tsx
@@ -12,9 +12,9 @@ const ContainerInfoHeader = () => {
const intl = useIntl();
const { readOnly } = useLibraryContext();
- const { sidebarComponentInfo } = useSidebarContext();
+ const { sidebarItemInfo } = useSidebarContext();
- const containerId = sidebarComponentInfo?.id;
+ const containerId = sidebarItemInfo?.id;
// istanbul ignore if: this should never happen
if (!containerId) {
throw new Error('containerId is required');
diff --git a/src/library-authoring/containers/ContainerOrganize.test.tsx b/src/library-authoring/containers/ContainerOrganize.test.tsx
index 8fb366fa1..3e5c33f97 100644
--- a/src/library-authoring/containers/ContainerOrganize.test.tsx
+++ b/src/library-authoring/containers/ContainerOrganize.test.tsx
@@ -8,7 +8,7 @@ import {
waitFor,
} from '../../testUtils';
import { LibraryProvider } from '../common/context/LibraryContext';
-import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
+import { SidebarBodyItemId, SidebarProvider } from '../common/context/SidebarContext';
import { mockContentLibrary, mockGetContainerMetadata } from '../data/api.mocks';
import ContainerOrganize from './ContainerOrganize';
@@ -25,7 +25,7 @@ mockContentTaxonomyTagsData.applyMock();
const render = ({
libraryId = mockContentLibrary.libraryId,
- containerId = mockGetContainerMetadata.containerId,
+ containerId = mockGetContainerMetadata.unitId,
}: {
libraryId?: string;
containerId?: string;
@@ -33,9 +33,9 @@ const render = ({
extraWrapper: ({ children }) => (
{children}
@@ -77,12 +77,12 @@ describe('', () => {
...getConfig(),
ENABLE_TAGGING_TAXONOMY_PAGES: 'true',
});
- render({ containerId: mockGetContainerMetadata.containerIdForTags });
+ render({ containerId: mockGetContainerMetadata.unitIdForTags });
expect(await screen.findByText('Tags (6)')).toBeInTheDocument();
});
it('should render collection count in collection info section', async () => {
- render({ containerId: mockGetContainerMetadata.containerIdWithCollections });
+ render({ containerId: mockGetContainerMetadata.unitIdWithCollections });
expect(await screen.findByText('Collections (1)')).toBeInTheDocument();
});
});
diff --git a/src/library-authoring/containers/ContainerOrganize.tsx b/src/library-authoring/containers/ContainerOrganize.tsx
index 6419bfd43..b76670efd 100644
--- a/src/library-authoring/containers/ContainerOrganize.tsx
+++ b/src/library-authoring/containers/ContainerOrganize.tsx
@@ -27,10 +27,10 @@ const ContainerOrganize = () => {
const [collectionsCollapseIsOpen, setCollectionsCollapseOpen, , toggleCollections] = useToggle(true);
const { readOnly } = useLibraryContext();
- const { sidebarComponentInfo, sidebarAction } = useSidebarContext();
+ const { sidebarItemInfo, sidebarAction } = useSidebarContext();
const jumpToCollections = sidebarAction === SidebarActions.JumpToManageCollections;
- const containerId = sidebarComponentInfo?.id;
+ const containerId = sidebarItemInfo?.id;
// istanbul ignore if: this should never happen
if (!containerId) {
throw new Error('containerId is required');
diff --git a/src/library-authoring/containers/HeaderActions.tsx b/src/library-authoring/containers/HeaderActions.tsx
index 63e0a526d..be1871f71 100644
--- a/src/library-authoring/containers/HeaderActions.tsx
+++ b/src/library-authoring/containers/HeaderActions.tsx
@@ -1,50 +1,40 @@
import { Button } from '@openedx/paragon';
import { Add, InfoOutline } from '@openedx/paragon/icons';
import { useCallback } from 'react';
-import { ContainerType } from '../../generic/key-utils';
import { useLibraryContext } from '../common/context/LibraryContext';
import { useSidebarContext } from '../common/context/SidebarContext';
import { useLibraryRoutes } from '../routes';
interface HeaderActionsProps {
containerKey: string;
- containerType: ContainerType;
infoBtnText: string;
addContentBtnText: string;
}
export const HeaderActions = ({
containerKey,
- containerType,
infoBtnText,
addContentBtnText,
}: HeaderActionsProps) => {
const { readOnly } = useLibraryContext();
const {
closeLibrarySidebar,
- sidebarComponentInfo,
- openUnitInfoSidebar,
+ sidebarItemInfo,
+ openContainerInfoSidebar,
openAddContentSidebar,
} = useSidebarContext();
const { navigateTo } = useLibraryRoutes();
- const infoSidebarIsOpen = sidebarComponentInfo?.id === containerKey;
+ const infoSidebarIsOpen = sidebarItemInfo?.id === containerKey;
const handleOnClickInfoSidebar = useCallback(() => {
if (infoSidebarIsOpen) {
closeLibrarySidebar();
} else {
- switch (containerType) {
- case ContainerType.Unit:
- openUnitInfoSidebar(containerKey);
- break;
- /* istanbul ignore next */
- default:
- break;
- }
+ openContainerInfoSidebar(containerKey);
}
- navigateTo({ [`${containerType}Id`]: containerKey });
- }, [containerKey, infoSidebarIsOpen, navigateTo]);
+ navigateTo({ containerId: containerKey });
+ }, [containerKey, infoSidebarIsOpen, navigateTo, openContainerInfoSidebar]);
return (
diff --git a/src/library-authoring/containers/UnitInfo.test.tsx b/src/library-authoring/containers/UnitInfo.test.tsx
deleted file mode 100644
index e20e27f51..000000000
--- a/src/library-authoring/containers/UnitInfo.test.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import userEvent from '@testing-library/user-event';
-import type MockAdapter from 'axios-mock-adapter';
-
-import {
- initializeMocks, render as baseRender, screen, waitFor,
- fireEvent,
-} from '../../testUtils';
-import { mockContentLibrary, mockGetContainerChildren, mockGetContainerMetadata } from '../data/api.mocks';
-import { LibraryProvider } from '../common/context/LibraryContext';
-import UnitInfo from './UnitInfo';
-import { getLibraryContainerApiUrl, getLibraryContainerPublishApiUrl } from '../data/api';
-import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
-
-mockGetContainerMetadata.applyMock();
-mockContentLibrary.applyMock();
-mockGetContainerMetadata.applyMock();
-mockGetContainerChildren.applyMock();
-
-const { libraryId } = mockContentLibrary;
-const { containerId } = mockGetContainerMetadata;
-
-const render = (showOnlyPublished: boolean = false) => {
- const params: { libraryId: string, unitId?: string } = { libraryId, unitId: containerId };
- return baseRender(
, {
- path: '/library/:libraryId/:unitId?',
- params,
- extraWrapper: ({ children }) => (
-
-
- {children}
-
-
- ),
- });
-};
-let axiosMock: MockAdapter;
-let mockShowToast;
-
-describe('
', () => {
- beforeEach(() => {
- ({ axiosMock, mockShowToast } = initializeMocks());
- });
-
- it('should delete the unit using the menu', async () => {
- axiosMock.onDelete(getLibraryContainerApiUrl(containerId)).reply(200);
- render();
-
- // Open menu
- expect(await screen.findByTestId('unit-info-menu-toggle')).toBeInTheDocument();
- userEvent.click(screen.getByTestId('unit-info-menu-toggle'));
-
- // Click on Delete Item
- const deleteMenuItem = screen.getByRole('button', { name: 'Delete' });
- expect(deleteMenuItem).toBeInTheDocument();
- fireEvent.click(deleteMenuItem);
-
- // Confirm delete Modal is open
- expect(screen.getByText('Delete Unit'));
- const deleteButton = screen.getByRole('button', { name: /delete/i });
- fireEvent.click(deleteButton);
-
- await waitFor(() => {
- expect(axiosMock.history.delete.length).toBe(1);
- });
- expect(mockShowToast).toHaveBeenCalled();
- });
-
- it('can publish the container', async () => {
- axiosMock.onPost(getLibraryContainerPublishApiUrl(containerId)).reply(200);
- render();
-
- // Click on Publish button
- const publishButton = await screen.findByRole('button', { name: 'Publish' });
- expect(publishButton).toBeInTheDocument();
- userEvent.click(publishButton);
-
- await waitFor(() => {
- expect(axiosMock.history.post.length).toBe(1);
- });
- expect(mockShowToast).toHaveBeenCalledWith('All changes published');
- });
-
- it('shows an error if publishing the container fails', async () => {
- axiosMock.onPost(getLibraryContainerPublishApiUrl(containerId)).reply(500);
- render();
-
- // Click on Publish button
- const publishButton = await screen.findByRole('button', { name: 'Publish' });
- expect(publishButton).toBeInTheDocument();
- userEvent.click(publishButton);
-
- await waitFor(() => {
- expect(axiosMock.history.post.length).toBe(1);
- });
- expect(mockShowToast).toHaveBeenCalledWith('Failed to publish changes');
- });
-
- it('show only published content', async () => {
- render(true);
- expect(await screen.findByTestId('unit-info-menu-toggle')).toBeInTheDocument();
- expect(screen.getByText(/text block published 1/i)).toBeInTheDocument();
- });
-
- it('shows the preview tab by default and the component are readonly', async () => {
- render();
- const previewTab = await screen.findByText('Preview');
- expect(previewTab).toBeInTheDocument();
- expect(previewTab).toHaveAttribute('aria-selected', 'true');
-
- // Check that there are no edit buttons for components titles
- expect(screen.queryAllByRole('button', { name: /edit/i }).length).toBe(0);
-
- // Check that there are no drag handle for components
- expect(screen.queryAllByRole('button', { name: 'Drag to reorder' }).length).toBe(0);
-
- // Check that there are no menu buttons for components
- expect(screen.queryAllByRole('button', { name: /component actions menu/i }).length).toBe(0);
- });
-});
diff --git a/src/library-authoring/containers/index.tsx b/src/library-authoring/containers/index.tsx
index 66449377e..69a1596f5 100644
--- a/src/library-authoring/containers/index.tsx
+++ b/src/library-authoring/containers/index.tsx
@@ -1,4 +1,4 @@
-export { default as UnitInfo } from './UnitInfo';
+export { default as ContainerInfo } from './ContainerInfo';
export { default as ContainerInfoHeader } from './ContainerInfoHeader';
export { ContainerEditableTitle } from './ContainerEditableTitle';
export { HeaderActions } from './HeaderActions';
diff --git a/src/library-authoring/containers/messages.ts b/src/library-authoring/containers/messages.ts
index fe8e0139e..1dec3cafc 100644
--- a/src/library-authoring/containers/messages.ts
+++ b/src/library-authoring/containers/messages.ts
@@ -1,10 +1,10 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
- openUnitButton: {
+ openButton: {
id: 'course-authoring.library-authoring.container-sidebar.open-button',
defaultMessage: 'Open',
- description: 'Button text to open unit',
+ description: 'Button text to open container',
},
previewTabTitle: {
id: 'course-authoring.library-authoring.container-sidebar.preview-tab.title',
diff --git a/src/library-authoring/create-container/CreateContainerModal.tsx b/src/library-authoring/create-container/CreateContainerModal.tsx
index dab088e9e..a5fd56b61 100644
--- a/src/library-authoring/create-container/CreateContainerModal.tsx
+++ b/src/library-authoring/create-container/CreateContainerModal.tsx
@@ -87,7 +87,7 @@ const CreateContainerModal = () => {
await updateItemsMutation.mutateAsync([container.id]);
}
// Navigate to the new container
- navigateTo({ [`${containerType}Id`]: container.id });
+ navigateTo({ containerId: container.id });
showToast(labels.successMsg);
} catch (error) {
showToast(labels.errorMsg);
diff --git a/src/library-authoring/data/api.mocks.ts b/src/library-authoring/data/api.mocks.ts
index 9f5a4c975..0e70b148e 100644
--- a/src/library-authoring/data/api.mocks.ts
+++ b/src/library-authoring/data/api.mocks.ts
@@ -472,7 +472,7 @@ mockGetCollectionMetadata.applyMock = () => {
*/
export async function mockGetContainerMetadata(containerId: string): Promise
{
switch (containerId) {
- case mockGetContainerMetadata.containerIdError:
+ case mockGetContainerMetadata.unitIdError:
case mockGetContainerMetadata.sectionIdError:
case mockGetContainerMetadata.subsectionIdError:
throw createAxiosError({
@@ -480,11 +480,11 @@ export async function mockGetContainerMetadata(containerId: string): Promise { });
- case mockGetContainerMetadata.containerIdWithCollections:
+ case mockGetContainerMetadata.unitIdWithCollections:
return Promise.resolve(mockGetContainerMetadata.containerDataWithCollections);
case mockGetContainerMetadata.sectionId:
case mockGetContainerMetadata.sectionIdEmpty:
@@ -496,19 +496,19 @@ export async function mockGetContainerMetadata(containerId: string): Promise {
export async function mockGetContainerChildren(containerId: string): Promise {
let numChildren: number;
switch (containerId) {
- case mockGetContainerMetadata.containerId:
+ case mockGetContainerMetadata.unitId:
case mockGetContainerMetadata.sectionId:
case mockGetContainerMetadata.subsectionId:
numChildren = 3;
diff --git a/src/library-authoring/generic/manage-collections/ManageCollections.test.tsx b/src/library-authoring/generic/manage-collections/ManageCollections.test.tsx
index 7f7b79d43..b90a29ded 100644
--- a/src/library-authoring/generic/manage-collections/ManageCollections.test.tsx
+++ b/src/library-authoring/generic/manage-collections/ManageCollections.test.tsx
@@ -85,10 +85,10 @@ describe('', () => {
});
it('should show all collections in library and allow users to select for the current container', async () => {
- const url = getLibraryContainerCollectionsUrl(mockGetContainerMetadata.containerIdWithCollections);
+ const url = getLibraryContainerCollectionsUrl(mockGetContainerMetadata.unitIdWithCollections);
axiosMock.onPatch(url).reply(200);
render();
diff --git a/src/library-authoring/library-sidebar/LibrarySidebar.tsx b/src/library-authoring/library-sidebar/LibrarySidebar.tsx
index ddcddfafd..1d6a0e0ed 100644
--- a/src/library-authoring/library-sidebar/LibrarySidebar.tsx
+++ b/src/library-authoring/library-sidebar/LibrarySidebar.tsx
@@ -9,9 +9,9 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { AddContent, AddContentHeader } from '../add-content';
import { CollectionInfo, CollectionInfoHeader } from '../collections';
-import { ContainerInfoHeader, UnitInfo } from '../containers';
+import { ContainerInfoHeader, ContainerInfo } from '../containers';
import {
- COMPONENT_INFO_TABS, SidebarActions, SidebarBodyComponentId, useSidebarContext,
+ COMPONENT_INFO_TABS, SidebarActions, SidebarBodyItemId, useSidebarContext,
} from '../common/context/SidebarContext';
import { ComponentInfo, ComponentInfoHeader } from '../component-info';
import { LibraryInfo, LibraryInfoHeader } from '../library-info';
@@ -21,7 +21,7 @@ import messages from '../messages';
* Sidebar container for library pages.
*
* It's designed to "squash" the page when open.
- * Uses `sidebarComponentInfo.type` of the `context` to
+ * Uses `sidebarItemInfo.type` of the `context` to
* choose which component is rendered.
* You can add more components in `bodyComponentMap`.
* Use the returned actions to open and close this sidebar.
@@ -31,7 +31,7 @@ const LibrarySidebar = () => {
const {
sidebarAction,
setSidebarTab,
- sidebarComponentInfo,
+ sidebarItemInfo,
closeLibrarySidebar,
} = useSidebarContext();
const jumpToCollections = sidebarAction === SidebarActions.JumpToManageCollections;
@@ -41,31 +41,31 @@ const LibrarySidebar = () => {
// Show Manage tab if JumpToManageCollections or JumpToManageTags action is set
if (jumpToCollections || jumpToTags) {
// COMPONENT_INFO_TABS.Manage works for containers as well as its value
- // is same as UNIT_INFO_TABS.Manage.
+ // is same as CONTAINER_INFO_TABS.Manage.
setSidebarTab(COMPONENT_INFO_TABS.Manage);
}
}, [jumpToCollections, setSidebarTab, jumpToTags]);
const bodyComponentMap = {
- [SidebarBodyComponentId.AddContent]: ,
- [SidebarBodyComponentId.Info]: ,
- [SidebarBodyComponentId.ComponentInfo]: ,
- [SidebarBodyComponentId.CollectionInfo]: ,
- [SidebarBodyComponentId.UnitInfo]: ,
+ [SidebarBodyItemId.AddContent]: ,
+ [SidebarBodyItemId.Info]: ,
+ [SidebarBodyItemId.ComponentInfo]: ,
+ [SidebarBodyItemId.CollectionInfo]: ,
+ [SidebarBodyItemId.ContainerInfo]: ,
unknown: null,
};
const headerComponentMap = {
- [SidebarBodyComponentId.AddContent]: ,
- [SidebarBodyComponentId.Info]: ,
- [SidebarBodyComponentId.ComponentInfo]: ,
- [SidebarBodyComponentId.CollectionInfo]: ,
- [SidebarBodyComponentId.UnitInfo]: ,
+ [SidebarBodyItemId.AddContent]: ,
+ [SidebarBodyItemId.Info]: ,
+ [SidebarBodyItemId.ComponentInfo]: ,
+ [SidebarBodyItemId.CollectionInfo]: ,
+ [SidebarBodyItemId.ContainerInfo]: ,
unknown: null,
};
- const buildBody = () : React.ReactNode => bodyComponentMap[sidebarComponentInfo?.type || 'unknown'];
- const buildHeader = (): React.ReactNode => headerComponentMap[sidebarComponentInfo?.type || 'unknown'];
+ const buildBody = () : React.ReactNode => bodyComponentMap[sidebarItemInfo?.type || 'unknown'];
+ const buildHeader = (): React.ReactNode => headerComponentMap[sidebarItemInfo?.type || 'unknown'];
return (
diff --git a/src/library-authoring/routes.test.tsx b/src/library-authoring/routes.test.tsx
index 57aff79cf..cc819602d 100644
--- a/src/library-authoring/routes.test.tsx
+++ b/src/library-authoring/routes.test.tsx
@@ -141,7 +141,7 @@ describe('Library Authoring routes', () => {
},
destination: {
params: {
- unitId: 'lct:org:lib:unit:unitId',
+ containerId: 'lct:org:lib:unit:unitId',
},
path: '/unit/lct:org:lib:unit:unitId',
},
@@ -503,7 +503,7 @@ describe('Library Authoring routes', () => {
path: `/library/:libraryId${origin.path}/*`,
params: {
libraryId: mockContentLibrary.libraryId,
- unitId: '',
+ containerId: '',
collectionId: '',
selectedItemId: '',
...origin.params,
diff --git a/src/library-authoring/routes.ts b/src/library-authoring/routes.ts
index 7906cb641..57e5791cb 100644
--- a/src/library-authoring/routes.ts
+++ b/src/library-authoring/routes.ts
@@ -11,6 +11,7 @@ import {
useSearchParams,
type PathMatch,
} from 'react-router-dom';
+import { ContainerType, getBlockType } from '../generic/key-utils';
export const BASE_ROUTE = '/library/:libraryId';
@@ -20,11 +21,11 @@ export const ROUTES = {
COMPONENTS: '/components/:selectedItemId?',
// * Collections tab, with an optionally selected collectionId in the sidebar.
COLLECTIONS: '/collections/:selectedItemId?',
- // * Sections tab, with an optionally selected sectionId in the sidebar.
+ // * Sections tab, with an optionally selected section in the sidebar.
SECTIONS: '/sections/:selectedItemId?',
- // * Subsections tab, with an optionally selected subsectionId in the sidebar.
+ // * Subsections tab, with an optionally selected subsection in the sidebar.
SUBSECTIONS: '/subsections/:selectedItemId?',
- // * Units tab, with an optionally selected unitId in the sidebar.
+ // * Units tab, with an optionally selected unit in the sidebar.
UNITS: '/units/:selectedItemId?',
// * All Content tab, with an optionally selected collection or unit in the sidebar.
HOME: '/:selectedItemId?',
@@ -32,14 +33,14 @@ export const ROUTES = {
// * with a selected collectionId and/or an optionally selected componentId.
COLLECTION: '/collection/:collectionId/:selectedItemId?',
// LibrarySectionPage route:
- // * with a selected sectionId and/or an optionally selected subsectionId.
- SECTION: '/section/:sectionId/:selectedItemId?',
+ // * with a selected containerId and an optionally selected subsection.
+ SECTION: '/section/:containerId/:selectedItemId?',
// LibrarySubsectionPage route:
- // * with a selected subsectionId and/or an optionally selected unitId.
- SUBSECTION: '/subsection/:subsectionId/:selectedItemId?',
+ // * with a selected containerId and an optionally selected unit.
+ SUBSECTION: '/subsection/:containerId/:selectedItemId?',
// LibraryUnitPage route:
- // * with a selected unitId and/or an optionally selected componentId.
- UNIT: '/unit/:unitId/:selectedItemId?',
+ // * with a selected containerId and/or an optionally selected componentId.
+ UNIT: '/unit/:containerId/:selectedItemId?',
};
export enum ContentType {
@@ -56,10 +57,8 @@ export const allLibraryPageTabs: ContentType[] = Object.values(ContentType);
export type NavigateToData = {
selectedItemId?: string,
collectionId?: string,
+ containerId?: string,
contentType?: ContentType,
- sectionId?: string,
- subsectionId?: string,
- unitId?: string,
};
export type LibraryRoutesData = {
@@ -118,18 +117,14 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
const navigateTo = useCallback(({
selectedItemId,
collectionId,
- sectionId,
- subsectionId,
- unitId,
+ containerId,
contentType,
}: NavigateToData = {}) => {
const routeParams = {
...params,
// Overwrite the params with the provided values.
...((selectedItemId !== undefined) && { selectedItemId }),
- ...((sectionId !== undefined) && { sectionId }),
- ...((subsectionId !== undefined) && { subsectionId }),
- ...((unitId !== undefined) && { unitId }),
+ ...((containerId !== undefined) && { containerId }),
...((collectionId !== undefined) && { collectionId }),
};
let route: string;
@@ -140,24 +135,22 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
routeParams.selectedItemId = undefined;
}
- // Update sectionId/subsectionId/unitId/collectionId in library context if is not undefined.
+ // Update containerId/collectionId in library context if is not undefined.
// Ids can be cleared from route by passing in empty string so we need to set it.
- if (unitId !== undefined || sectionId !== undefined || subsectionId !== undefined) {
+ if (containerId !== undefined) {
routeParams.selectedItemId = undefined;
- // If we can have a unitId/subsectionId/sectionId alongside a routeParams.collectionId,
+ // If we can have a containerId alongside a routeParams.collectionId,
// it means we are inside a collection trying to navigate to a unit/section/subsection,
- // so we want to clear the collectionId to not have ambiquity.
+ // so we want to clear the collectionId to not have ambiguity.
if (routeParams.collectionId !== undefined) {
routeParams.collectionId = undefined;
}
} else if (collectionId !== undefined) {
routeParams.selectedItemId = undefined;
} else if (contentType) {
- // We are navigating to the library home, so we need to clear the sectionId, subsectionId, unitId and collectionId
- routeParams.unitId = undefined;
- routeParams.sectionId = undefined;
- routeParams.subsectionId = undefined;
+ // We are navigating to the library home, so we need to clear the containerId and collectionId
+ routeParams.containerId = undefined;
routeParams.collectionId = undefined;
}
@@ -174,9 +167,7 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
// FIXME: We are using the Collection key, not the full OpaqueKey. So we
// can't directly use the selectedItemId to determine if it's a collection.
// We need to change this to use the full OpaqueKey in the future.
- if (routeParams.selectedItemId?.includes(':unit:')
- || routeParams.selectedItemId?.includes(':subsection:')
- || routeParams.selectedItemId?.includes(':section:')
+ if (routeParams.selectedItemId?.startsWith('lct:')
|| routeParams.selectedItemId?.startsWith('lb:')) {
routeParams.selectedItemId = undefined;
}
@@ -201,12 +192,24 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
route = ROUTES.SECTIONS;
} else if (contentType === ContentType.home) {
route = ROUTES.HOME;
- } else if (routeParams.unitId) {
- route = ROUTES.UNIT;
- } else if (routeParams.subsectionId) {
- route = ROUTES.SUBSECTION;
- } else if (routeParams.sectionId) {
- route = ROUTES.SECTION;
+ } else if (routeParams.containerId) {
+ const containerType = getBlockType(routeParams.containerId);
+ switch (containerType) {
+ case ContainerType.Unit:
+ route = ROUTES.UNIT;
+ break;
+ case ContainerType.Subsection:
+ route = ROUTES.SUBSECTION;
+ break;
+ case ContainerType.Section:
+ route = ROUTES.SECTION;
+ break;
+ default:
+ // Fall back to home if unrecognized container type
+ route = ROUTES.HOME;
+ routeParams.containerId = undefined;
+ break;
+ }
} else if (routeParams.collectionId) {
route = ROUTES.COLLECTION;
// From here, we will just stay in the current route
diff --git a/src/library-authoring/section-subsections/LibraryContainerChildren.tsx b/src/library-authoring/section-subsections/LibraryContainerChildren.tsx
index 3953a80df..70913afd6 100644
--- a/src/library-authoring/section-subsections/LibraryContainerChildren.tsx
+++ b/src/library-authoring/section-subsections/LibraryContainerChildren.tsx
@@ -84,11 +84,12 @@ const ContainerRow = ({ container, readOnly }: ContainerRowProps) => {
)}
-
+ {!readOnly && (
+
+ )}
>
);
@@ -99,8 +100,8 @@ export const LibraryContainerChildren = ({ containerKey, readOnly }: LibraryCont
const intl = useIntl();
const [orderedChildren, setOrderedChildren] = useState([]);
const { showOnlyPublished, readOnly: libReadOnly } = useLibraryContext();
- const { navigateTo, insideSection, insideSubsection } = useLibraryRoutes();
- const { sidebarComponentInfo } = useSidebarContext();
+ const { navigateTo, insideSection } = useLibraryRoutes();
+ const { sidebarItemInfo } = useSidebarContext();
const [activeDraggingId, setActiveDraggingId] = useState(null);
const orderMutator = useUpdateContainerChildren(containerKey);
const { showToast } = useContext(ToastContext);
@@ -142,10 +143,8 @@ export const LibraryContainerChildren = ({ containerKey, readOnly }: LibraryCont
const doubleClicked = numberOfClicks > 1;
if (!doubleClicked) {
navigateTo({ selectedItemId: child.originalId });
- } else if (insideSection) {
- navigateTo({ subsectionId: child.originalId });
- } else if (insideSubsection) {
- navigateTo({ unitId: child.originalId });
+ } else {
+ navigateTo({ containerId: child.originalId });
}
}, [navigateTo]);
@@ -200,10 +199,10 @@ export const LibraryContainerChildren = ({ containerKey, readOnly }: LibraryCont
borderRadius: '8px',
borderLeft: '8px solid #E1DDDB',
}}
- isClickable
- onClick={(e) => handleChildClick(child, e.detail)}
+ isClickable={!readOnly}
+ onClick={(e) => !readOnly && handleChildClick(child, e.detail)}
disabled={readOnly || libReadOnly}
- cardClassName={sidebarComponentInfo?.id === child.originalId ? 'selected' : undefined}
+ cardClassName={sidebarItemInfo?.id === child.originalId ? 'selected' : undefined}
actions={(
{
const intl = useIntl();
- const { libraryId, sectionId } = useLibraryContext();
+ const { libraryId, containerId } = useLibraryContext();
const {
- sidebarComponentInfo,
+ sidebarItemInfo,
} = useSidebarContext();
- if (!sectionId || !libraryId) {
+ if (!containerId || !libraryId) {
// istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker.
- throw new Error('Rendered without sectionId or libraryId URL parameter');
+ throw new Error('Rendered without containerId or libraryId URL parameter');
}
const { data: libraryData, isLoading: isLibLoading } = useContentLibrary(libraryId);
@@ -36,9 +35,9 @@ export const LibrarySectionPage = () => {
isLoading,
isError,
error,
- } = useContainer(sectionId);
+ } = useContainer(containerId);
- // show loading if sectionId or libraryId is not set or section or library data is not fetched from index yet
+ // show loading if containerId or libraryId is not set or section or library data is not fetched from index yet
if (isLibLoading || isLoading) {
return ;
}
@@ -91,12 +90,11 @@ export const LibrarySectionPage = () => {
} />}
+ title={} />}
breadcrumbs={breadcrumbs}
headerActions={(
@@ -105,7 +103,7 @@ export const LibrarySectionPage = () => {
/>
-
+
{
- {!!sidebarComponentInfo?.type && (
+ {!!sidebarItemInfo?.type && (
', () => {
expect(await screen.findByRole('button', { name: new RegExp(`${cType} Info`, 'i') })).toBeInTheDocument();
expect((await screen.findAllByRole('button', { name: 'Drag to reorder' })).length).toEqual(3);
// check all children components are rendered.
- expect(await screen.findByText(`${childType} block 0`)).toBeInTheDocument();
- expect(await screen.findByText(`${childType} block 1`)).toBeInTheDocument();
- expect(await screen.findByText(`${childType} block 2`)).toBeInTheDocument();
+ expect((await screen.findAllByText(`${childType} block 0`))[0]).toBeInTheDocument();
+ expect((await screen.findAllByText(`${childType} block 1`))[0]).toBeInTheDocument();
+ expect((await screen.findAllByText(`${childType} block 2`))[0]).toBeInTheDocument();
});
it(`shows ${cType} data with no children`, async () => {
@@ -148,7 +148,7 @@ describe('', () => {
// unit info button
expect(await screen.findByRole('button', { name: new RegExp(`${cType} Info`, 'i') })).toBeInTheDocument();
// check all children components are rendered.
- expect(await screen.findByText(`This ${cType} is empty`)).toBeInTheDocument();
+ expect((await screen.findAllByText(`This ${cType} is empty`))[0]).toBeInTheDocument();
});
it(`can rename ${cType}`, async () => {
@@ -157,7 +157,7 @@ describe('', () => {
: mockGetContainerMetadata.subsectionId;
renderLibrarySectionPage(cId, undefined, cType);
expect((await screen.findAllByText(libraryTitle))[0]).toBeInTheDocument();
- expect(await screen.findByText(`Test ${cType}`)).toBeInTheDocument();
+ expect((await screen.findAllByText(`Test ${cType}`))[0]).toBeInTheDocument();
const editContainerTitleButton = (await screen.findAllByRole(
'button',
@@ -190,7 +190,7 @@ describe('', () => {
: mockGetContainerMetadata.subsectionId;
renderLibrarySectionPage(cId, undefined, cType);
expect((await screen.findAllByText(libraryTitle))[0]).toBeInTheDocument();
- expect(await screen.findByText(`Test ${cType}`)).toBeInTheDocument();
+ expect((await screen.findAllByText(`Test ${cType}`))[0]).toBeInTheDocument();
const editContainerTitleButton = (await screen.findAllByRole(
'button',
@@ -224,8 +224,8 @@ describe('', () => {
axiosMock.onPatch(url).reply(200);
renderLibrarySectionPage(undefined, undefined, cType);
- // Wait loading of the component
- await screen.findByText(`${childType} block 0`);
+ // Wait loading of the component (on page and in sidebar)
+ await screen.findAllByText(`${childType} block 0`);
const editButton = (await screen.findAllByRole(
'button',
@@ -257,8 +257,8 @@ describe('', () => {
axiosMock.onPatch(url).reply(400);
renderLibrarySectionPage(undefined, undefined, cType);
- // Wait loading of the component
- await screen.findByText(`${childType} block 0`);
+ // Wait loading of the component (on page and in sidebar)
+ await screen.findAllByText(`${childType} block 0`);
const editButton = screen.getAllByRole(
'button',
@@ -339,7 +339,7 @@ describe('', () => {
it(`should open ${childType} page on double click`, async () => {
renderLibrarySectionPage(undefined, undefined, cType);
- const subsection = await screen.findByText(`${childType} block 0`);
+ const subsection = (await screen.findAllByText(`${childType} block 0`))[0];
// trigger double click
userEvent.click(subsection.parentElement!, undefined, { clickCount: 2 });
expect((await screen.findAllByText(new RegExp(`Test ${childType}`, 'i')))[0]).toBeInTheDocument();
diff --git a/src/library-authoring/section-subsections/LibrarySubsectionPage.tsx b/src/library-authoring/section-subsections/LibrarySubsectionPage.tsx
index 5362f42d1..1b60d5d3a 100644
--- a/src/library-authoring/section-subsections/LibrarySubsectionPage.tsx
+++ b/src/library-authoring/section-subsections/LibrarySubsectionPage.tsx
@@ -18,7 +18,6 @@ import { messages, subsectionMessages } from './messages';
import { LibrarySidebar } from '../library-sidebar';
import { LibraryContainerChildren } from './LibraryContainerChildren';
import { ContainerEditableTitle, FooterActions, HeaderActions } from '../containers';
-import { ContainerType } from '../../generic/key-utils';
import { ContainerHit } from '../../search-manager';
interface OverflowLinksProps {
@@ -54,16 +53,14 @@ const OverflowLinks = ({ children, to }: OverflowLinksProps) => {
/** Full library subsection page */
export const LibrarySubsectionPage = () => {
const intl = useIntl();
- const { libraryId, subsectionId } = useLibraryContext();
- const {
- sidebarComponentInfo,
- } = useSidebarContext();
+ const { libraryId, containerId } = useLibraryContext();
+ const { sidebarItemInfo } = useSidebarContext();
const { data: libraryData, isLoading: isLibLoading } = useContentLibrary(libraryId);
// fetch subsectionData from index as it includes its parent sections as well.
const {
hits, isLoading, isError, error,
- } = useContentFromSearchIndex(subsectionId ? [subsectionId] : []);
+ } = useContentFromSearchIndex(containerId ? [containerId] : []);
const subsectionData = (hits as ContainerHit[])?.[0];
const breadcrumbs = useMemo(() => {
@@ -102,9 +99,9 @@ export const LibrarySubsectionPage = () => {
);
}, [libraryData, subsectionData, libraryId]);
- if (!subsectionId || !libraryId) {
+ if (!containerId || !libraryId) {
// istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker.
- throw new Error('Rendered without subsectionId or libraryId URL parameter');
+ throw new Error('Rendered without containerId or libraryId URL parameter');
}
// Only show loading if section or library data is not fetched from index yet
@@ -142,12 +139,11 @@ export const LibrarySubsectionPage = () => {
} />}
+ title={} />}
breadcrumbs={breadcrumbs}
headerActions={(
@@ -156,7 +152,7 @@ export const LibrarySubsectionPage = () => {
/>
-
+
{
- {!!sidebarComponentInfo?.type && (
+ {!!sidebarItemInfo?.type && (
const { navigateTo } = useLibraryRoutes();
const { openComponentEditor } = useLibraryContext();
- const { sidebarComponentInfo } = useSidebarContext();
+ const { sidebarItemInfo } = useSidebarContext();
const handleComponentSelection = useCallback((numberOfClicks: number) => {
navigateTo({ selectedItemId: block.originalId });
@@ -184,9 +184,9 @@ const ComponentBlock = ({ block, readOnly, isDragging }: ComponentBlockProps) =>
borderBottom: 'solid 1px #E1DDDB',
}}
isClickable={!readOnly}
- onClick={!readOnly ? (e) => handleComponentSelection(e.detail) : undefined}
+ onClick={(e) => !readOnly && handleComponentSelection(e.detail)}
disabled={readOnly}
- cardClassName={sidebarComponentInfo?.id === block.originalId ? 'selected' : undefined}
+ cardClassName={sidebarItemInfo?.id === block.originalId ? 'selected' : undefined}
>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
', () => {
const renderLibraryUnitPage = (unitId?: string, libraryId?: string) => {
const libId = libraryId || mockContentLibrary.libraryId;
- const uId = unitId || mockGetContainerMetadata.containerId;
+ const uId = unitId || mockGetContainerMetadata.unitId;
render(
, {
path,
routerProps: {
@@ -75,14 +75,14 @@ describe('
', () => {
it('shows the spinner before the query is complete', async () => {
// This mock will never return data about the collection (it loads forever):
- renderLibraryUnitPage(mockGetContainerMetadata.containerIdLoading);
+ renderLibraryUnitPage(mockGetContainerMetadata.unitIdLoading);
const spinner = screen.getByRole('status');
expect(spinner.textContent).toEqual('Loading...');
});
it('shows an error component if no unit returned', async () => {
// This mock will simulate incorrect unit id
- renderLibraryUnitPage(mockGetContainerMetadata.containerIdError);
+ renderLibraryUnitPage(mockGetContainerMetadata.unitIdError);
const errorMessage = 'Not found';
expect(await screen.findByRole('alert')).toHaveTextContent(errorMessage);
});
@@ -113,7 +113,7 @@ describe('
', () => {
)[0]; // 0 is the Unit Title, 1 is the first component on the list
fireEvent.click(editUnitTitleButton);
- const url = getLibraryContainerApiUrl(mockGetContainerMetadata.containerId);
+ const url = getLibraryContainerApiUrl(mockGetContainerMetadata.unitId);
axiosMock.onPatch(url).reply(200);
await waitFor(() => {
@@ -144,7 +144,7 @@ describe('
', () => {
)[0]; // 0 is the Unit Title, 1 is the first component on the list
fireEvent.click(editUnitTitleButton);
- const url = getLibraryContainerApiUrl(mockGetContainerMetadata.containerId);
+ const url = getLibraryContainerApiUrl(mockGetContainerMetadata.unitId);
axiosMock.onPatch(url).reply(400);
await waitFor(() => {
@@ -275,7 +275,7 @@ describe('
', () => {
renderLibraryUnitPage();
const firstDragHandle = (await screen.findAllByRole('button', { name: 'Drag to reorder' }))[0];
axiosMock
- .onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId))
+ .onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.unitId))
.reply(200);
verticalSortableListCollisionDetection.mockReturnValue([{ id: 'lb:org1:Demo_course:html:text-1----1' }]);
await act(async () => {
@@ -289,7 +289,7 @@ describe('
', () => {
renderLibraryUnitPage();
const firstDragHandle = (await screen.findAllByRole('button', { name: 'Drag to reorder' }))[0];
axiosMock
- .onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId))
+ .onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.unitId))
.reply(200);
verticalSortableListCollisionDetection.mockReturnValue([{ id: 'lb:org1:Demo_course:html:text-1----1' }]);
await act(async () => {
@@ -303,7 +303,7 @@ describe('
', () => {
renderLibraryUnitPage();
const firstDragHandle = (await screen.findAllByRole('button', { name: 'Drag to reorder' }))[0];
axiosMock
- .onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId))
+ .onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.unitId))
.reply(500);
verticalSortableListCollisionDetection.mockReturnValue([{ id: 'lb:org1:Demo_course:html:text-1----1' }]);
await act(async () => {
@@ -314,7 +314,7 @@ describe('
', () => {
});
it('should remove a component & restore from component card', async () => {
- const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.unitId);
axiosMock.onDelete(url).reply(200);
renderLibraryUnitPage();
@@ -334,7 +334,7 @@ describe('
', () => {
// @ts-ignore
const restoreFn = mockShowToast.mock.calls[0][1].onClick;
- const restoreUrl = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ const restoreUrl = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.unitId);
axiosMock.onPost(restoreUrl).reply(200);
// restore collection
restoreFn();
@@ -345,7 +345,7 @@ describe('
', () => {
});
it('should show error on remove a component', async () => {
- const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.unitId);
axiosMock.onDelete(url).reply(404);
renderLibraryUnitPage();
@@ -363,7 +363,7 @@ describe('
', () => {
});
it('should show error on restore removed component', async () => {
- const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.unitId);
axiosMock.onDelete(url).reply(200);
renderLibraryUnitPage();
@@ -383,7 +383,7 @@ describe('
', () => {
// @ts-ignore
const restoreFn = mockShowToast.mock.calls[0][1].onClick;
- const restoreUrl = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ const restoreUrl = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.unitId);
axiosMock.onPost(restoreUrl).reply(404);
// restore collection
restoreFn();
@@ -394,7 +394,7 @@ describe('
', () => {
});
it('should remove a component from component sidebar', async () => {
- const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.unitId);
axiosMock.onDelete(url).reply(200);
renderLibraryUnitPage();
diff --git a/src/library-authoring/units/LibraryUnitPage.tsx b/src/library-authoring/units/LibraryUnitPage.tsx
index 6fdd14257..1ab46f7b5 100644
--- a/src/library-authoring/units/LibraryUnitPage.tsx
+++ b/src/library-authoring/units/LibraryUnitPage.tsx
@@ -1,4 +1,3 @@
-import { useEffect } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Breadcrumb,
@@ -13,52 +12,28 @@ import SubHeader from '../../generic/sub-header/SubHeader';
import ErrorAlert from '../../generic/alert-error';
import Header from '../../header';
import { useLibraryContext } from '../common/context/LibraryContext';
-import {
- COLLECTION_INFO_TABS, COMPONENT_INFO_TABS, UNIT_INFO_TABS, useSidebarContext,
-} from '../common/context/SidebarContext';
+import { useSidebarContext } from '../common/context/SidebarContext';
import { useContainer, useContentLibrary } from '../data/apiHooks';
import { LibrarySidebar } from '../library-sidebar';
import { SubHeaderTitle } from '../LibraryAuthoringPage';
import { LibraryUnitBlocks } from './LibraryUnitBlocks';
import messages from './messages';
import { ContainerEditableTitle, FooterActions, HeaderActions } from '../containers';
-import { ContainerType } from '../../generic/key-utils';
export const LibraryUnitPage = () => {
const intl = useIntl();
const {
libraryId,
- unitId,
+ containerId,
} = useLibraryContext();
// istanbul ignore if: this should never happen
- if (!unitId) {
- throw new Error('unitId is required');
+ if (!containerId) {
+ throw new Error('containerId is required');
}
- const {
- sidebarComponentInfo,
- setDefaultTab,
- setHiddenTabs,
- } = useSidebarContext();
-
- useEffect(() => {
- setDefaultTab({
- collection: COLLECTION_INFO_TABS.Details,
- component: COMPONENT_INFO_TABS.Manage,
- unit: UNIT_INFO_TABS.Manage,
- });
- setHiddenTabs([COMPONENT_INFO_TABS.Preview, UNIT_INFO_TABS.Preview]);
- return () => {
- setDefaultTab({
- component: COMPONENT_INFO_TABS.Preview,
- unit: UNIT_INFO_TABS.Preview,
- collection: COLLECTION_INFO_TABS.Manage,
- });
- setHiddenTabs([]);
- };
- }, [setDefaultTab, setHiddenTabs]);
+ const { sidebarItemInfo } = useSidebarContext();
const { data: libraryData, isLoading: isLibLoading } = useContentLibrary(libraryId);
const {
@@ -66,11 +41,11 @@ export const LibraryUnitPage = () => {
isLoading,
isError,
error,
- } = useContainer(unitId);
+ } = useContainer(containerId);
- if (!unitId || !libraryId) {
+ if (!containerId || !libraryId) {
// istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker.
- throw new Error('Rendered without unitId or libraryId URL parameter');
+ throw new Error('Rendered without containerId or libraryId URL parameter');
}
// Only show loading if unit or library data is not fetched from index yet
@@ -122,11 +97,10 @@ export const LibraryUnitPage = () => {
} />}
+ title={} />}
headerActions={(
@@ -136,7 +110,7 @@ export const LibraryUnitPage = () => {
/>
-
+
{
- {!!sidebarComponentInfo?.type && (
+ {!!sidebarItemInfo?.type && (