diff --git a/src/library-authoring/add-content/AddContent.tsx b/src/library-authoring/add-content/AddContent.tsx
index accb72e9bf..361947cf7b 100644
--- a/src/library-authoring/add-content/AddContent.tsx
+++ b/src/library-authoring/add-content/AddContent.tsx
@@ -116,25 +116,25 @@ const AddContentView = ({
return (
<>
- {upstreamContainerType !== ContainerType.Unit && (
+ {(collectionId || unitId) && componentPicker && (
+ /// Show the "Add Library Content" button for units and collections
<>
- {collectionId ? (
- componentPicker && (
- <>
-
-
- >
- )
- ) : (
-
- )}
-
-
+
+
>
)}
+ {!collectionId && !unitId && (
+ // Doesn't show the "Collection" button if we are in a unit or collection
+
+ )}
+ {upstreamContainerType !== ContainerType.Unit && (
+ // Doesn't show the "Unit" button if we are in a unit
+
+ )}
+
{/* Note: for MVP we are hiding the unuspported types, not just disabling them. */}
{contentTypes.filter(ct => !ct.disabled).map((contentType) => (
void;
-const render = () => baseRender(, {
- path: '/library/:libraryId/collection/:collectionId/*',
- params: { libraryId, collectionId: 'collectionId' },
+const mockAddComponentsToCollection = jest.fn();
+const mockAddComponentsToContainer = jest.fn();
+jest.spyOn(api, 'addComponentsToCollection').mockImplementation(mockAddComponentsToCollection);
+jest.spyOn(api, 'addComponentsToContainer').mockImplementation(mockAddComponentsToContainer);
+
+const render = (context: 'collection' | 'unit') => baseRender(, {
+ path: context === 'collection'
+ ? '/library/:libraryId/collection/:collectionId/*'
+ : '/library/:libraryId/container/:unitId/*',
+ params: {
+ libraryId,
+ ...(context === 'collection' && { collectionId: 'collectionId' }),
+ ...(context === 'unit' && { unitId: 'unitId' }),
+ },
extraWrapper: ({ children }) => (
', () => {
const mocks = initializeMocks();
mockShowToast = mocks.mockShowToast;
mocks.axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
+ jest.clearAllMocks();
});
- it('can pick components from the modal', async () => {
- const mockAddComponentsToCollection = jest.fn();
- jest.spyOn(api, 'addComponentsToCollection').mockImplementation(mockAddComponentsToCollection);
-
- render();
-
- // Wait for the content library to load
- await waitFor(() => {
- expect(screen.getByText('Test Library')).toBeInTheDocument();
- expect(screen.queryAllByText('Introduction to Testing')[0]).toBeInTheDocument();
- });
-
- // Select the first component
- fireEvent.click(screen.queryAllByRole('button', { name: 'Select' })[0]);
- expect(await screen.findByText('1 Selected Component')).toBeInTheDocument();
-
- fireEvent.click(screen.queryAllByRole('button', { name: 'Add to Collection' })[0]);
-
- await waitFor(() => {
- expect(mockAddComponentsToCollection).toHaveBeenCalledWith(
- libraryId,
- 'collectionId',
- ['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
- );
+ ['collection' as const, 'unit' as const].forEach((context) => {
+ it(`can pick components from the modal (${context})`, async () => {
+ render(context);
+
+ // Wait for the content library to load
+ await waitFor(() => {
+ expect(screen.getByText('Test Library')).toBeInTheDocument();
+ expect(screen.queryAllByText('Introduction to Testing')[0]).toBeInTheDocument();
+ });
+
+ // Select the first component
+ fireEvent.click(screen.queryAllByRole('button', { name: 'Select' })[0]);
+ expect(await screen.findByText('1 Selected Component')).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole('button', { name: /add to .*/i }));
+
+ await waitFor(() => {
+ if (context === 'collection') {
+ expect(mockAddComponentsToCollection).toHaveBeenCalledWith(
+ libraryId,
+ 'collectionId',
+ ['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
+ );
+ } else {
+ expect(mockAddComponentsToContainer).toHaveBeenCalledWith(
+ 'unitId',
+ ['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
+ );
+ }
+ });
expect(onClose).toHaveBeenCalled();
expect(mockShowToast).toHaveBeenCalledWith('Content linked successfully.');
});
- });
-
- it('show error when api call fails', async () => {
- const mockAddComponentsToCollection = jest.fn().mockRejectedValue(new Error('Failed to add components'));
- jest.spyOn(api, 'addComponentsToCollection').mockImplementation(mockAddComponentsToCollection);
- render();
-
- // Wait for the content library to load
- await waitFor(() => {
- expect(screen.getByText('Test Library')).toBeInTheDocument();
- expect(screen.queryAllByText('Introduction to Testing')[0]).toBeInTheDocument();
- });
-
- // Select the first component
- fireEvent.click(screen.queryAllByRole('button', { name: 'Select' })[0]);
- expect(await screen.findByText('1 Selected Component')).toBeInTheDocument();
-
- fireEvent.click(screen.queryAllByRole('button', { name: 'Add to Collection' })[0]);
- await waitFor(() => {
- expect(mockAddComponentsToCollection).toHaveBeenCalledWith(
- libraryId,
- 'collectionId',
- ['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
- );
+ it(`show error when api call fails (${context})`, async () => {
+ if (context === 'collection') {
+ mockAddComponentsToCollection.mockRejectedValueOnce(new Error('Error'));
+ } else {
+ mockAddComponentsToContainer.mockRejectedValueOnce(new Error('Error'));
+ }
+ render(context);
+
+ // Wait for the content library to load
+ await waitFor(() => {
+ expect(screen.getByText('Test Library')).toBeInTheDocument();
+ expect(screen.queryAllByText('Introduction to Testing')[0]).toBeInTheDocument();
+ });
+
+ // Select the first component
+ fireEvent.click(screen.queryAllByRole('button', { name: 'Select' })[0]);
+ expect(await screen.findByText('1 Selected Component')).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole('button', { name: /add to .*/i }));
+
+ await waitFor(() => {
+ if (context === 'collection') {
+ expect(mockAddComponentsToCollection).toHaveBeenCalledWith(
+ libraryId,
+ 'collectionId',
+ ['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
+ );
+ } else {
+ expect(mockAddComponentsToContainer).toHaveBeenCalledWith(
+ 'unitId',
+ ['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
+ );
+ }
+ });
expect(onClose).toHaveBeenCalled();
- expect(mockShowToast).toHaveBeenCalledWith('There was an error linking the content to this collection.');
+ const name = context === 'collection' ? 'collection' : 'container';
+ expect(mockShowToast).toHaveBeenCalledWith(`There was an error linking the content to this ${name}.`);
});
});
});
diff --git a/src/library-authoring/add-content/PickLibraryContentModal.tsx b/src/library-authoring/add-content/PickLibraryContentModal.tsx
index 5436bf2d78..4b9cded02f 100644
--- a/src/library-authoring/add-content/PickLibraryContentModal.tsx
+++ b/src/library-authoring/add-content/PickLibraryContentModal.tsx
@@ -5,23 +5,25 @@ import { ActionRow, Button, StandardModal } from '@openedx/paragon';
import { ToastContext } from '../../generic/toast-context';
import { useLibraryContext } from '../common/context/LibraryContext';
import type { SelectedComponent } from '../common/context/ComponentPickerContext';
-import { useAddComponentsToCollection } from '../data/apiHooks';
+import { useAddComponentsToCollection, useAddComponentsToContainer } from '../data/apiHooks';
import messages from './messages';
interface PickLibraryContentModalFooterProps {
onSubmit: () => void;
selectedComponents: SelectedComponent[];
+ buttonText: React.ReactNode;
}
const PickLibraryContentModalFooter: React.FC = ({
onSubmit,
selectedComponents,
+ buttonText,
}) => (
);
@@ -40,6 +42,7 @@ export const PickLibraryContentModal: React.FC = (
const {
libraryId,
collectionId,
+ unitId,
/** We need to get it as a reference instead of directly importing it to avoid the import cycle:
* ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage >
* Sidebar > AddContent > ComponentPicker */
@@ -47,11 +50,12 @@ export const PickLibraryContentModal: React.FC = (
} = useLibraryContext();
// istanbul ignore if: this should never happen
- if (!collectionId || !ComponentPicker) {
- throw new Error('libraryId and componentPicker are required');
+ if (!(collectionId || unitId) || !ComponentPicker) {
+ throw new Error('collectionId/unitId and componentPicker are required');
}
- const updateComponentsMutation = useAddComponentsToCollection(libraryId, collectionId);
+ const updateCollectionItemsMutation = useAddComponentsToCollection(libraryId, collectionId);
+ const updateUnitComponentsMutation = useAddComponentsToContainer(libraryId, unitId);
const { showToast } = useContext(ToastContext);
@@ -60,13 +64,23 @@ export const PickLibraryContentModal: React.FC = (
const onSubmit = useCallback(() => {
const usageKeys = selectedComponents.map(({ usageKey }) => usageKey);
onClose();
- updateComponentsMutation.mutateAsync(usageKeys)
- .then(() => {
- showToast(intl.formatMessage(messages.successAssociateComponentMessage));
- })
- .catch(() => {
- showToast(intl.formatMessage(messages.errorAssociateComponentToCollectionMessage));
- });
+ if (collectionId) {
+ updateCollectionItemsMutation.mutateAsync(usageKeys)
+ .then(() => {
+ showToast(intl.formatMessage(messages.successAssociateComponentMessage));
+ })
+ .catch(() => {
+ showToast(intl.formatMessage(messages.errorAssociateComponentToCollectionMessage));
+ });
+ } else if (unitId) {
+ updateUnitComponentsMutation.mutateAsync(usageKeys)
+ .then(() => {
+ showToast(intl.formatMessage(messages.successAssociateComponentMessage));
+ })
+ .catch(() => {
+ showToast(intl.formatMessage(messages.errorAssociateComponentToContainerMessage));
+ });
+ }
}, [selectedComponents]);
return (
@@ -76,7 +90,16 @@ export const PickLibraryContentModal: React.FC = (
size="xl"
isOpen={isOpen}
onClose={onClose}
- footerNode={}
+ footerNode={(
+
+ )}
>
{
});
it('should add components to container', async () => {
+ const libraryId = 'lib:org:1';
const componentId = 'lb:org:lib:html:1';
const containerId = 'ltc:org:lib:unit:1';
const url = getLibraryContainerChildrenApiUrl(containerId);
axiosMock.onPost(url).reply(200);
- const { result } = renderHook(() => useAddComponentsToContainer(containerId), { wrapper });
+ const { result } = renderHook(() => useAddComponentsToContainer(libraryId, containerId), { wrapper });
await result.current.mutateAsync([componentId]);
expect(axiosMock.history.post[0].url).toEqual(url);