Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/course-unit/add-component/AddComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ const AddComponent = ({
>
<ComponentPicker
showOnlyPublished
extraFilter={['NOT block_type = "unit"']}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oo good catches!

componentPickerMode={isAddLibraryContentModalOpen ? 'single' : 'multiple'}
onComponentSelected={handleLibraryV2Selection}
onChangeComponentSelection={setSelectedComponents}
Expand Down
10 changes: 8 additions & 2 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,14 @@ const App = () => {
<Route path="/libraries-v1" element={<StudioHome />} />
<Route path="/library/create" element={<CreateLibrary />} />
<Route path="/library/:libraryId/*" element={<LibraryLayout />} />
<Route path="/component-picker" element={<ComponentPicker />} />
<Route path="/component-picker/multiple" element={<ComponentPicker componentPickerMode="multiple" />} />
<Route
path="/component-picker"
element={<ComponentPicker extraFilter={['NOT block_type = "unit"']} />}
/>
<Route
path="/component-picker/multiple"
element={<ComponentPicker componentPickerMode="multiple" extraFilter={['NOT block_type = "unit"']} />}
/>
<Route path="/legacy/preview-changes/:usageKey" element={<PreviewChangesEmbed />} />
<Route path="/course/:courseId/*" element={<CourseAuthoringRoutes />} />
<Route path="/course_rerun/:courseId" element={<CourseRerun />} />
Expand Down
29 changes: 26 additions & 3 deletions src/library-authoring/LibraryAuthoringPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ describe('<LibraryAuthoringPage />', () => {
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
});

it('should open component sidebar, showing manage tab on clicking add to collection menu item', async () => {
it('should open component sidebar, showing manage tab on clicking add to collection menu item (component)', async () => {
const mockResult0 = { ...mockResult }.results[0].hits[0];
const displayName = 'Introduction to Testing';
expect(mockResult0.display_name).toStrictEqual(displayName);
Expand All @@ -417,6 +417,29 @@ describe('<LibraryAuthoringPage />', () => {
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
});

it('should open component sidebar, showing manage tab on clicking add to collection menu item (unit)', async () => {
const displayName = 'Test Unit';
await renderLibraryPage();

waitFor(() => expect(screen.getAllByTestId('container-card-menu-toggle').length).toBeGreaterThan(0));

// Open menu
fireEvent.click((await screen.findAllByTestId('container-card-menu-toggle'))[0]);
// Click add to collection
fireEvent.click(screen.getByRole('button', { name: 'Add to collection' }));

const sidebar = screen.getByTestId('library-sidebar');

const { getByRole, queryByText } = within(sidebar);

await waitFor(() => expect(queryByText(displayName)).toBeInTheDocument());
expect(getByRole('tab', { selected: true })).toHaveTextContent('Organize');
const closeButton = getByRole('button', { name: /close/i });
fireEvent.click(closeButton);

await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
});

it('should open and close the collection sidebar', async () => {
await renderLibraryPage();

Expand Down Expand Up @@ -732,7 +755,7 @@ describe('<LibraryAuthoringPage />', () => {
fireEvent.click(cancelButton);
expect(unitModalHeading).not.toBeInTheDocument();

// Open new unit modal again and create a collection
// Open new unit modal again and create a unit
fireEvent.click(newUnitButton);
const createButton = screen.getByRole('button', { name: /create/i });
const nameField = screen.getByRole('textbox', { name: /name your unit/i });
Expand Down Expand Up @@ -800,7 +823,7 @@ describe('<LibraryAuthoringPage />', () => {
fireEvent.click(newButton);
expect(screen.getByText(/add content/i)).toBeInTheDocument();

// Open New collection Modal
// Open New Unit Modal
const sidebar = screen.getByTestId('library-sidebar');
const newUnitButton = within(sidebar).getAllByRole('button', { name: /unit/i })[0];
fireEvent.click(newUnitButton);
Expand Down
5 changes: 5 additions & 0 deletions src/library-authoring/LibraryAuthoringPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
libraryData,
isLoadingLibraryData,
showOnlyPublished,
extraFilter: contextExtraFilter,
componentId,
collectionId,
unitId,
Expand Down Expand Up @@ -223,6 +224,10 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
extraFilter.push('last_published IS NOT NULL');
}

if (contextExtraFilter) {
extraFilter.push(...contextExtraFilter);
}

const activeTypeFilters = {
components: 'type = "library_block"',
collections: 'type = "collection"',
Expand Down
39 changes: 38 additions & 1 deletion src/library-authoring/__mocks__/collection-search.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,45 @@
"org": "OpenedX",
"access_id": 16,
"num_children": 1
},
{
"display_name": "Test Unit",
"block_id": "test-unit-9284e2",
"id": "lctAximTESTunittest-unit-9284e2-a9a4386e",
"type": "library_container",
"breadcrumbs": [
{
"display_name": "Test Library"
}
],
"created": 1742221203.895054,
"modified": 1742221203.895054,
"usage_key": "lct:Axim:TEST:unit:test-unit-9284e2",
"block_type": "unit",
"context_key": "lib:Axim:TEST",
"org": "Axim",
"access_id": 15,
"num_children": 0,
"_formatted": {
"display_name": "Test Unit",
"block_id": "test-unit-9284e2",
"id": "lctAximTESTunittest-unit-9284e2-a9a4386e",
"type": "library_container",
"breadcrumbs": [
{
"display_name": "Test Library"
}
],
"created": "1742221203.895054",
"modified": "1742221203.895054",
"usage_key": "lct:Axim:TEST:unit:test-unit-9284e2",
"block_type": "unit",
"context_key": "lib:Axim:TEST",
"org": "Axim",
"access_id": "15",
"num_children": "0"
}
}

],
"query": "",
"processingTimeMs": 1,
Expand Down
14 changes: 9 additions & 5 deletions src/library-authoring/add-content/AddContent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ import {
mockXBlockFields,
} from '../data/api.mocks';
import {
getContentLibraryApiUrl, getCreateLibraryBlockUrl, getLibraryCollectionComponentApiUrl, getLibraryPasteClipboardUrl,
getXBlockFieldsApiUrl, getLibraryContainerChildrenApiUrl,
getContentLibraryApiUrl,
getCreateLibraryBlockUrl,
getLibraryCollectionItemsApiUrl,
getLibraryContainerChildrenApiUrl,
getLibraryPasteClipboardUrl,
getXBlockFieldsApiUrl,
} from '../data/api';
import { mockClipboardEmpty, mockClipboardHtml } from '../../generic/data/api.mock';
import { LibraryProvider } from '../common/context/LibraryContext';
Expand Down Expand Up @@ -151,7 +155,7 @@ describe('<AddContent />', () => {
const url = getCreateLibraryBlockUrl(libraryId);
const usageKey = mockXBlockFields.usageKeyNewHtml;
const updateBlockUrl = getXBlockFieldsApiUrl(usageKey);
const collectionComponentUrl = getLibraryCollectionComponentApiUrl(
const collectionComponentUrl = getLibraryCollectionItemsApiUrl(
libraryId,
collectionId,
);
Expand Down Expand Up @@ -209,7 +213,7 @@ describe('<AddContent />', () => {

const pasteUrl = getLibraryPasteClipboardUrl(libraryId);
const collectionId = 'some-collection-id';
const collectionComponentUrl = getLibraryCollectionComponentApiUrl(
const collectionComponentUrl = getLibraryCollectionItemsApiUrl(
libraryId,
collectionId,
);
Expand All @@ -234,7 +238,7 @@ describe('<AddContent />', () => {

const pasteUrl = getLibraryPasteClipboardUrl(libraryId);
const collectionId = 'some-collection-id';
const collectionComponentUrl = getLibraryCollectionComponentApiUrl(
const collectionComponentUrl = getLibraryCollectionItemsApiUrl(
libraryId,
collectionId,
);
Expand Down
12 changes: 6 additions & 6 deletions src/library-authoring/add-content/AddContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { getCanEdit } from '../../course-unit/data/selectors';
import {
useCreateLibraryBlock,
useLibraryPasteClipboard,
useAddComponentsToCollection,
useBlockTypesMetadata,
useAddItemsToCollection,
useAddComponentsToContainer,
} from '../data/apiHooks';
import { useLibraryContext } from '../common/context/LibraryContext';
Expand Down Expand Up @@ -207,8 +207,8 @@ const AddContent = () => {
openComponentEditor,
unitId,
} = useLibraryContext();
const addComponentsToCollectionMutation = useAddComponentsToCollection(libraryId, collectionId);
const addComponentsToContainerMutation = useAddComponentsToContainer(libraryId, unitId);
const addComponentsToCollectionMutation = useAddItemsToCollection(libraryId, collectionId);
const addComponentsToContainerMutation = useAddComponentsToContainer(unitId);
const createBlockMutation = useCreateLibraryBlock();
const pasteClipboardMutation = useLibraryPasteClipboard();
const { showToast } = useContext(ToastContext);
Expand Down Expand Up @@ -286,14 +286,14 @@ const AddContent = () => {
contentTypes.push(pasteButton);
}

const linkComponent = (usageKey: string) => {
const linkComponent = (opaqueKey: string) => {
if (collectionId) {
addComponentsToCollectionMutation.mutateAsync([usageKey]).catch(() => {
addComponentsToCollectionMutation.mutateAsync([opaqueKey]).catch(() => {
showToast(intl.formatMessage(messages.errorAssociateComponentToCollectionMessage));
});
}
if (unitId) {
addComponentsToContainerMutation.mutateAsync([usageKey]).catch(() => {
addComponentsToContainerMutation.mutateAsync([opaqueKey]).catch(() => {
showToast(intl.formatMessage(messages.errorAssociateComponentToContainerMessage));
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ describe('<PickLibraryContentModal />', () => {
});

it('can pick components from the modal', async () => {
const mockAddComponentsToCollection = jest.fn();
jest.spyOn(api, 'addComponentsToCollection').mockImplementation(mockAddComponentsToCollection);
const mockAddItemsToCollection = jest.fn();
jest.spyOn(api, 'addItemsToCollection').mockImplementation(mockAddItemsToCollection);

render();

Expand All @@ -67,7 +67,7 @@ describe('<PickLibraryContentModal />', () => {
fireEvent.click(screen.queryAllByRole('button', { name: 'Add to Collection' })[0]);

await waitFor(() => {
expect(mockAddComponentsToCollection).toHaveBeenCalledWith(
expect(mockAddItemsToCollection).toHaveBeenCalledWith(
libraryId,
'collectionId',
['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
Expand All @@ -78,8 +78,8 @@ describe('<PickLibraryContentModal />', () => {
});

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);
const mockAddItemsToCollection = jest.fn().mockRejectedValue(new Error('Failed to add components'));
jest.spyOn(api, 'addItemsToCollection').mockImplementation(mockAddItemsToCollection);
render();

// Wait for the content library to load
Expand All @@ -95,7 +95,7 @@ describe('<PickLibraryContentModal />', () => {
fireEvent.click(screen.queryAllByRole('button', { name: 'Add to Collection' })[0]);

await waitFor(() => {
expect(mockAddComponentsToCollection).toHaveBeenCalledWith(
expect(mockAddItemsToCollection).toHaveBeenCalledWith(
libraryId,
'collectionId',
['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
Expand Down
4 changes: 2 additions & 2 deletions src/library-authoring/add-content/PickLibraryContentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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 { useAddItemsToCollection } from '../data/apiHooks';
import messages from './messages';

interface PickLibraryContentModalFooterProps {
Expand Down Expand Up @@ -51,7 +51,7 @@ export const PickLibraryContentModal: React.FC<PickLibraryContentModalProps> = (
throw new Error('libraryId and componentPicker are required');
}

const updateComponentsMutation = useAddComponentsToCollection(libraryId, collectionId);
const updateComponentsMutation = useAddItemsToCollection(libraryId, collectionId);

const { showToast } = useContext(ToastContext);

Expand Down
38 changes: 35 additions & 3 deletions src/library-authoring/collections/LibraryCollectionPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ import {
mockContentLibrary,
mockXBlockFields,
mockGetCollectionMetadata,
mockGetContainerMetadata,
} from '../data/api.mocks';
import { mockContentSearchConfig, mockGetBlockTypes } from '../../search-manager/data/api.mock';
import { mockClipboardEmpty } from '../../generic/data/api.mock';
import { LibraryLayout } from '..';
import { ContentTagsDrawer } from '../../content-tags-drawer';
import { getLibraryCollectionComponentApiUrl } from '../data/api';
import { getLibraryCollectionItemsApiUrl } from '../data/api';

let axiosMock: MockAdapter;
let mockShowToast;
Expand All @@ -31,6 +32,7 @@ mockContentSearchConfig.applyMock();
mockGetBlockTypes.applyMock();
mockContentLibrary.applyMock();
mockXBlockFields.applyMock();
mockGetContainerMetadata.applyMock();

const searchEndpoint = 'http://mock.meilisearch.local/multi-search';
const path = '/library/:libraryId/*';
Expand Down Expand Up @@ -350,7 +352,7 @@ describe('<LibraryCollectionPage />', () => {
});

it('should remove component from collection and hides sidebar', async () => {
const url = getLibraryCollectionComponentApiUrl(
const url = getLibraryCollectionItemsApiUrl(
mockContentLibrary.libraryId,
mockCollection.collectionId,
);
Expand All @@ -369,8 +371,38 @@ describe('<LibraryCollectionPage />', () => {
fireEvent.click(await screen.findByText('Remove from collection'));
await waitFor(() => {
expect(axiosMock.history.delete.length).toEqual(1);
expect(mockShowToast).toHaveBeenCalledWith('Component successfully removed');
});
expect(mockShowToast).toHaveBeenCalledWith('Item successfully removed');
// Should close sidebar as component was removed
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
});

it('should remove unit from collection and hides sidebar', async () => {
const url = getLibraryCollectionItemsApiUrl(
mockContentLibrary.libraryId,
mockCollection.collectionId,
);
axiosMock.onDelete(url).reply(204);
const displayName = 'Test Unit';
await renderLibraryCollectionPage();

// Wait for the unit cards to load
waitFor(() => expect(screen.getAllByTestId('container-card-menu-toggle').length).toBeGreaterThan(0));

// open sidebar
fireEvent.click(await screen.findByText(displayName));
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).toBeInTheDocument());

// Open menu
fireEvent.click((await screen.findAllByTestId('container-card-menu-toggle'))[0]);

// Click remove to collection
fireEvent.click(screen.getByRole('button', { name: 'Remove from collection' }));

await waitFor(() => {
expect(axiosMock.history.delete.length).toEqual(1);
});
expect(mockShowToast).toHaveBeenCalledWith('Item successfully removed');
// Should close sidebar as component was removed
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
});
Expand Down
8 changes: 7 additions & 1 deletion src/library-authoring/collections/LibraryCollectionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ const LibraryCollectionPage = () => {
}

const { componentPickerMode } = useComponentPickerContext();
const { showOnlyPublished, setCollectionId, componentId } = useLibraryContext();
const {
showOnlyPublished, extraFilter: contextExtraFilter, setCollectionId, componentId,
} = useLibraryContext();
const { sidebarComponentInfo, openInfoSidebar } = useSidebarContext();

const {
Expand Down Expand Up @@ -182,6 +184,10 @@ const LibraryCollectionPage = () => {
extraFilter.push('last_published IS NOT NULL');
}

if (contextExtraFilter) {
extraFilter.push(...contextExtraFilter);
}

return (
<div className="d-flex">
<div className="flex-grow-1">
Expand Down
Loading