diff --git a/src/library-authoring/collections/LibraryCollectionPage.test.tsx b/src/library-authoring/collections/LibraryCollectionPage.test.tsx
index 17d5a4b5b..3c3bf4547 100644
--- a/src/library-authoring/collections/LibraryCollectionPage.test.tsx
+++ b/src/library-authoring/collections/LibraryCollectionPage.test.tsx
@@ -380,6 +380,25 @@ describe('', () => {
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
});
+ it('should show error when remove component from collection', async () => {
+ const url = getLibraryCollectionItemsApiUrl(
+ mockContentLibrary.libraryId,
+ mockCollection.collectionId,
+ );
+ axiosMock.onDelete(url).reply(404);
+ await renderLibraryCollectionPage();
+
+ const menuBtns = await screen.findAllByRole('button', { name: 'Component actions menu' });
+ // open menu
+ fireEvent.click(menuBtns[0]);
+
+ fireEvent.click(await screen.findByText('Remove from collection'));
+ await waitFor(() => {
+ expect(axiosMock.history.delete.length).toEqual(1);
+ });
+ expect(mockShowToast).toHaveBeenCalledWith('Failed to remove item');
+ });
+
it('should remove unit from collection and hides sidebar', async () => {
const url = getLibraryCollectionItemsApiUrl(
mockContentLibrary.libraryId,
diff --git a/src/library-authoring/components/ComponentCard.tsx b/src/library-authoring/components/ComponentCard.tsx
index 54380c9b3..86dccbfd4 100644
--- a/src/library-authoring/components/ComponentCard.tsx
+++ b/src/library-authoring/components/ComponentCard.tsx
@@ -1,110 +1,21 @@
-import { useCallback, useContext } from 'react';
-import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
+import { useCallback } from 'react';
import {
ActionRow,
- Dropdown,
- Icon,
- IconButton,
- useToggle,
} from '@openedx/paragon';
-import { MoreVert } from '@openedx/paragon/icons';
-import { useClipboard } from '../../generic/clipboard';
-import { ToastContext } from '../../generic/toast-context';
import { type ContentHit, PublishStatus } from '../../search-manager';
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
import { useLibraryContext } from '../common/context/LibraryContext';
-import { SidebarActions, useSidebarContext } from '../common/context/SidebarContext';
-import { useRemoveItemsFromCollection } from '../data/apiHooks';
+import { useSidebarContext } from '../common/context/SidebarContext';
import { useLibraryRoutes } from '../routes';
-
import AddComponentWidget from './AddComponentWidget';
import BaseCard from './BaseCard';
-import { canEditComponent } from './ComponentEditorModal';
-import ComponentDeleter from './ComponentDeleter';
-import messages from './messages';
+import { ComponentMenu } from './ComponentMenu';
type ComponentCardProps = {
hit: ContentHit,
};
-export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
- const intl = useIntl();
- const {
- libraryId,
- collectionId,
- openComponentEditor,
- } = useLibraryContext();
-
- const {
- sidebarComponentInfo,
- openComponentInfoSidebar,
- closeLibrarySidebar,
- setSidebarAction,
- } = useSidebarContext();
-
- const canEdit = usageKey && canEditComponent(usageKey);
- const { showToast } = useContext(ToastContext);
- const removeComponentsMutation = useRemoveItemsFromCollection(libraryId, collectionId);
- const [isConfirmingDelete, confirmDelete, cancelDelete] = useToggle(false);
- const { copyToClipboard } = useClipboard();
-
- const updateClipboardClick = () => {
- copyToClipboard(usageKey);
- };
-
- const removeFromCollection = () => {
- removeComponentsMutation.mutateAsync([usageKey]).then(() => {
- if (sidebarComponentInfo?.id === usageKey) {
- // Close sidebar if current component is open
- closeLibrarySidebar();
- }
- showToast(intl.formatMessage(messages.removeComponentSucess));
- }).catch(() => {
- showToast(intl.formatMessage(messages.removeComponentFailure));
- });
- };
-
- const showManageCollections = useCallback(() => {
- setSidebarAction(SidebarActions.JumpToAddCollections);
- openComponentInfoSidebar(usageKey);
- }, [setSidebarAction, openComponentInfoSidebar, usageKey]);
-
- return (
-
-
-
- openComponentEditor(usageKey) } : { disabled: true })}>
-
-
-
-
-
-
-
-
- {collectionId && (
-
-
-
- )}
-
-
-
-
-
-
- );
-};
-
const ComponentCard = ({ hit }: ComponentCardProps) => {
const { showOnlyPublished } = useLibraryContext();
const { openComponentInfoSidebar } = useSidebarContext();
diff --git a/src/library-authoring/components/ComponentMenu.tsx b/src/library-authoring/components/ComponentMenu.tsx
new file mode 100644
index 000000000..589901326
--- /dev/null
+++ b/src/library-authoring/components/ComponentMenu.tsx
@@ -0,0 +1,137 @@
+import { useCallback, useContext } from 'react';
+import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
+import {
+ Dropdown,
+ Icon,
+ IconButton,
+ useToggle,
+} from '@openedx/paragon';
+import { MoreVert } from '@openedx/paragon/icons';
+
+import { useLibraryContext } from '../common/context/LibraryContext';
+import { SidebarActions, useSidebarContext } from '../common/context/SidebarContext';
+import { useClipboard } from '../../generic/clipboard';
+import { ToastContext } from '../../generic/toast-context';
+import {
+ useAddComponentsToContainer,
+ useRemoveContainerChildren,
+ useRemoveItemsFromCollection,
+} from '../data/apiHooks';
+import { canEditComponent } from './ComponentEditorModal';
+import ComponentDeleter from './ComponentDeleter';
+import messages from './messages';
+
+export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
+ const intl = useIntl();
+ const {
+ libraryId,
+ collectionId,
+ unitId,
+ openComponentEditor,
+ } = useLibraryContext();
+
+ const {
+ sidebarComponentInfo,
+ openComponentInfoSidebar,
+ closeLibrarySidebar,
+ setSidebarAction,
+ } = useSidebarContext();
+
+ const canEdit = usageKey && canEditComponent(usageKey);
+ const { showToast } = useContext(ToastContext);
+ const addComponentToContainerMutation = useAddComponentsToContainer(unitId);
+ const removeCollectionComponentsMutation = useRemoveItemsFromCollection(libraryId, collectionId);
+ const removeContainerComponentsMutation = useRemoveContainerChildren(unitId);
+ const [isConfirmingDelete, confirmDelete, cancelDelete] = useToggle(false);
+ const { copyToClipboard } = useClipboard();
+
+ const updateClipboardClick = () => {
+ copyToClipboard(usageKey);
+ };
+
+ const removeFromCollection = () => {
+ removeCollectionComponentsMutation.mutateAsync([usageKey]).then(() => {
+ if (sidebarComponentInfo?.id === usageKey) {
+ // Close sidebar if current component is open
+ closeLibrarySidebar();
+ }
+ showToast(intl.formatMessage(messages.removeComponentFromCollectionSuccess));
+ }).catch(() => {
+ showToast(intl.formatMessage(messages.removeComponentFromCollectionFailure));
+ });
+ };
+
+ const removeFromContainer = () => {
+ const restoreComponent = () => {
+ addComponentToContainerMutation.mutateAsync([usageKey]).then(() => {
+ showToast(intl.formatMessage(messages.undoRemoveComponentFromContainerToastSuccess));
+ }).catch(() => {
+ showToast(intl.formatMessage(messages.undoRemoveComponentFromContainerToastFailed));
+ });
+ };
+
+ removeContainerComponentsMutation.mutateAsync([usageKey]).then(() => {
+ if (sidebarComponentInfo?.id === usageKey) {
+ // Close sidebar if current component is open
+ closeLibrarySidebar();
+ }
+ showToast(
+ intl.formatMessage(messages.removeComponentFromContainerSuccess),
+ {
+ label: intl.formatMessage(messages.undoRemoveComponentFromContainerToastAction),
+ onClick: restoreComponent,
+ },
+ );
+ }).catch(() => {
+ showToast(intl.formatMessage(messages.removeComponentFromContainerFailure));
+ });
+ };
+
+ const showManageCollections = useCallback(() => {
+ setSidebarAction(SidebarActions.JumpToAddCollections);
+ openComponentInfoSidebar(usageKey);
+ }, [setSidebarAction, openComponentInfoSidebar, usageKey]);
+
+ return (
+
+
+
+ openComponentEditor(usageKey) } : { disabled: true })}>
+
+
+
+
+
+ {unitId && (
+
+
+
+ )}
+
+
+
+ {collectionId && (
+
+
+
+ )}
+ {!unitId && (
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default ComponentMenu;
diff --git a/src/library-authoring/components/ContainerCard.tsx b/src/library-authoring/components/ContainerCard.tsx
index d25c1c560..45268aa15 100644
--- a/src/library-authoring/components/ContainerCard.tsx
+++ b/src/library-authoring/components/ContainerCard.tsx
@@ -53,9 +53,9 @@ const ContainerMenu = ({ hit } : ContainerMenuProps) => {
// Close sidebar if current component is open
closeLibrarySidebar();
}
- showToast(intl.formatMessage(messages.removeComponentSucess));
+ showToast(intl.formatMessage(messages.removeComponentFromCollectionSuccess));
}).catch(() => {
- showToast(intl.formatMessage(messages.removeComponentFailure));
+ showToast(intl.formatMessage(messages.removeComponentFromCollectionFailure));
});
};
diff --git a/src/library-authoring/components/index.ts b/src/library-authoring/components/index.ts
index 119d3de91..29fcca8a2 100644
--- a/src/library-authoring/components/index.ts
+++ b/src/library-authoring/components/index.ts
@@ -1 +1 @@
-export { ComponentMenu as default } from './ComponentCard';
+export { ComponentMenu as default } from './ComponentMenu';
diff --git a/src/library-authoring/components/messages.ts b/src/library-authoring/components/messages.ts
index 6e897b950..250793077 100644
--- a/src/library-authoring/components/messages.ts
+++ b/src/library-authoring/components/messages.ts
@@ -46,12 +46,12 @@ const messages = defineMessages({
defaultMessage: 'Remove from collection',
description: 'Menu item for remove an item from collection.',
},
- removeComponentSucess: {
+ removeComponentFromCollectionSuccess: {
id: 'course-authoring.library-authoring.component.remove-from-collection-success',
defaultMessage: 'Item successfully removed',
description: 'Message for successful removal of an item from collection.',
},
- removeComponentFailure: {
+ removeComponentFromCollectionFailure: {
id: 'course-authoring.library-authoring.component.remove-from-collection-failure',
defaultMessage: 'Failed to remove item',
description: 'Message for failure of removal of an item from collection.',
@@ -231,5 +231,35 @@ const messages = defineMessages({
defaultMessage: '+{count}',
description: 'Count shown when a container has more blocks than will fit on the card preview.',
},
+ removeComponentFromUnitMenu: {
+ id: 'course-authoring.library-authoring.unit.component.remove.button',
+ defaultMessage: 'Remove from unit',
+ description: 'Text of the menu item to remove a component from a unit',
+ },
+ removeComponentFromContainerSuccess: {
+ id: 'course-authoring.library-authoring.component.remove-from-container-success',
+ defaultMessage: 'Component successfully removed',
+ description: 'Message for successful removal of a component from container.',
+ },
+ removeComponentFromContainerFailure: {
+ id: 'course-authoring.library-authoring.component.remove-from-container-failure',
+ defaultMessage: 'Failed to remove component',
+ description: 'Message for failure of removal of a component from container.',
+ },
+ undoRemoveComponentFromContainerToastAction: {
+ id: 'course-authoring.library-authoring.component.undo-remove-from-container-toast-button',
+ defaultMessage: 'Undo',
+ description: 'Toast message to undo remove a component from container.',
+ },
+ undoRemoveComponentFromContainerToastSuccess: {
+ id: 'course-authoring.library-authoring.component.undo-remove-component-from-container-toast-text',
+ defaultMessage: 'Undo successful',
+ description: 'Message to display on undo delete component success',
+ },
+ undoRemoveComponentFromContainerToastFailed: {
+ id: 'course-authoring.library-authoring.component.undo-remove-component-from-container-failed',
+ defaultMessage: 'Failed to undo remove component operation',
+ description: 'Message to display on failure to undo delete component',
+ },
});
export default messages;
diff --git a/src/library-authoring/data/api.test.ts b/src/library-authoring/data/api.test.ts
index d1796c43f..f5882ee67 100644
--- a/src/library-authoring/data/api.test.ts
+++ b/src/library-authoring/data/api.test.ts
@@ -1,10 +1,15 @@
import { initializeMocks } from '../../testUtils';
import * as api from './api';
+let axiosMock;
+
describe('library data API', () => {
+ beforeEach(() => {
+ ({ axiosMock } = initializeMocks());
+ });
+
describe('createLibraryBlock', () => {
it('should create library block', async () => {
- const { axiosMock } = initializeMocks();
const libraryId = 'lib:org:1';
const url = api.getCreateLibraryBlockUrl(libraryId);
axiosMock.onPost(url).reply(200);
@@ -20,7 +25,6 @@ describe('library data API', () => {
describe('deleteLibraryBlock', () => {
it('should delete a library block', async () => {
- const { axiosMock } = initializeMocks();
const usageKey = 'lib:org:1';
const url = api.getLibraryBlockMetadataUrl(usageKey);
axiosMock.onDelete(url).reply(200);
@@ -31,7 +35,6 @@ describe('library data API', () => {
describe('restoreLibraryBlock', () => {
it('should restore a soft-deleted library block', async () => {
- const { axiosMock } = initializeMocks();
const usageKey = 'lib:org:1';
const url = api.getLibraryBlockRestoreUrl(usageKey);
axiosMock.onPost(url).reply(200);
@@ -42,7 +45,6 @@ describe('library data API', () => {
describe('commitLibraryChanges', () => {
it('should commit library changes', async () => {
- const { axiosMock } = initializeMocks();
const libraryId = 'lib:org:1';
const url = api.getCommitLibraryChangesUrl(libraryId);
axiosMock.onPost(url).reply(200);
@@ -55,7 +57,6 @@ describe('library data API', () => {
describe('revertLibraryChanges', () => {
it('should revert library changes', async () => {
- const { axiosMock } = initializeMocks();
const libraryId = 'lib:org:1';
const url = api.getCommitLibraryChangesUrl(libraryId);
axiosMock.onDelete(url).reply(200);
@@ -68,7 +69,6 @@ describe('library data API', () => {
describe('getBlockTypes', () => {
it('should get block types metadata', async () => {
- const { axiosMock } = initializeMocks();
const libraryId = 'lib:org:1';
const url = api.getBlockTypesMetaDataUrl(libraryId);
axiosMock.onGet(url).reply(200);
@@ -80,7 +80,6 @@ describe('library data API', () => {
});
it('should create collection', async () => {
- const { axiosMock } = initializeMocks();
const libraryId = 'lib:org:1';
const url = api.getLibraryCollectionsApiUrl(libraryId);
@@ -95,7 +94,6 @@ describe('library data API', () => {
});
it('should delete a container', async () => {
- const { axiosMock } = initializeMocks();
const containerId = 'lct:org:lib1';
const url = api.getLibraryContainerApiUrl(containerId);
@@ -106,7 +104,6 @@ describe('library data API', () => {
});
it('should restore a container', async () => {
- const { axiosMock } = initializeMocks();
const containerId = 'lct:org:lib1';
const url = api.getLibraryContainerRestoreApiUrl(containerId);
@@ -116,7 +113,6 @@ describe('library data API', () => {
});
it('should add components to unit', async () => {
- const { axiosMock } = initializeMocks();
const componentId = 'lb:org:lib:html:1';
const containerId = 'lct:org:lib:unit:1';
const url = api.getLibraryContainerChildrenApiUrl(containerId);
@@ -128,7 +124,6 @@ describe('library data API', () => {
});
it('should update container children', async () => {
- const { axiosMock } = initializeMocks();
const containerId = 'lct:org:lib1';
const url = api.getLibraryContainerChildrenApiUrl(containerId);
@@ -137,4 +132,14 @@ describe('library data API', () => {
await api.updateLibraryContainerChildren(containerId, ['test']);
expect(axiosMock.history.patch[0].url).toEqual(url);
});
+
+ it('should remove container children', async () => {
+ const containerId = 'lct:org:lib1';
+ const url = api.getLibraryContainerChildrenApiUrl(containerId);
+
+ axiosMock.onDelete(url).reply(200);
+
+ await api.removeLibraryContainerChildren(containerId, ['test']);
+ expect(axiosMock.history.delete[0].url).toEqual(url);
+ });
});
diff --git a/src/library-authoring/data/api.ts b/src/library-authoring/data/api.ts
index 1eabb381e..9c6f5eaa1 100644
--- a/src/library-authoring/data/api.ts
+++ b/src/library-authoring/data/api.ts
@@ -684,3 +684,19 @@ export async function updateLibraryContainerChildren(
);
return camelCaseObject(data);
}
+
+/**
+ * Remove components in `children` from library container.
+ */
+export async function removeLibraryContainerChildren(
+ containerId: string,
+ children: string[],
+): Promise {
+ const { data } = await getAuthenticatedHttpClient().delete(
+ getLibraryContainerChildrenApiUrl(containerId),
+ {
+ data: { usage_keys: children },
+ },
+ );
+ return camelCaseObject(data);
+}
diff --git a/src/library-authoring/data/apiHooks.test.tsx b/src/library-authoring/data/apiHooks.test.tsx
index f026829cb..3f2a1b5d1 100644
--- a/src/library-authoring/data/apiHooks.test.tsx
+++ b/src/library-authoring/data/apiHooks.test.tsx
@@ -30,6 +30,7 @@ import {
useContainerChildren,
useAddComponentsToContainer,
useUpdateContainerChildren,
+ useRemoveContainerChildren,
} from './apiHooks';
let axiosMock;
@@ -287,4 +288,24 @@ describe('library api hooks', () => {
expect(axiosMock.history.patch.length).toEqual(0);
});
});
+
+ it('should remove container children', async () => {
+ const containerId = 'lct:org:lib1';
+ const url = getLibraryContainerChildrenApiUrl(containerId);
+
+ axiosMock.onDelete(url).reply(200);
+ const { result } = renderHook(() => useRemoveContainerChildren(containerId), { wrapper });
+ await result.current.mutateAsync([]);
+ await waitFor(() => {
+ expect(axiosMock.history.delete[0].url).toEqual(url);
+ });
+ });
+
+ it('should not attempt request if containerId is not defined in remove children from container', async () => {
+ const { result } = renderHook(() => useRemoveContainerChildren(), { wrapper });
+ await result.current.mutateAsync([]);
+ await waitFor(() => {
+ expect(axiosMock.history.patch.length).toEqual(0);
+ });
+ });
});
diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts
index 0bf7fe86a..317bf43a8 100644
--- a/src/library-authoring/data/apiHooks.ts
+++ b/src/library-authoring/data/apiHooks.ts
@@ -57,6 +57,7 @@ import {
getLibraryContainerChildren,
updateContainerCollections,
updateLibraryContainerChildren,
+ removeLibraryContainerChildren,
} from './api';
import { VersionSpec } from '../LibraryBlock';
@@ -732,3 +733,28 @@ export const useUpdateContainerChildren = (containerId?: string) => {
},
});
};
+
+/**
+ * Remove components from container
+ */
+export const useRemoveContainerChildren = (containerId?: string) => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: async (usageKeys: string[]) => {
+ if (!containerId) {
+ return undefined;
+ }
+ return removeLibraryContainerChildren(containerId, usageKeys);
+ },
+ onSettled: () => {
+ if (!containerId) {
+ return;
+ }
+ // NOTE: We invalidate the library query here because we need to update the container
+ // count in the library
+ const libraryId = getLibraryId(containerId);
+ queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) });
+ queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.container(containerId) });
+ },
+ });
+};
diff --git a/src/library-authoring/units/LibraryUnitPage.test.tsx b/src/library-authoring/units/LibraryUnitPage.test.tsx
index cee3503d6..03ac5b566 100644
--- a/src/library-authoring/units/LibraryUnitPage.test.tsx
+++ b/src/library-authoring/units/LibraryUnitPage.test.tsx
@@ -50,9 +50,7 @@ jest.mock('@dnd-kit/core', () => ({
describe('', () => {
beforeEach(() => {
- const mocks = initializeMocks();
- axiosMock = mocks.axiosMock;
- mockShowToast = mocks.mockShowToast;
+ ({ axiosMock, mockShowToast } = initializeMocks());
});
afterEach(() => {
@@ -183,7 +181,7 @@ describe('', () => {
expect(await screen.findByTestId('library-sidebar')).toBeInTheDocument();
});
- it('should open and component sidebar on component selection', async () => {
+ it('should open and close component sidebar on component selection', async () => {
renderLibraryUnitPage();
const component = await screen.findByText('text block 0');
@@ -232,6 +230,109 @@ describe('', () => {
await waitFor(() => expect(mockShowToast).toHaveBeenLastCalledWith('Failed to update components order'));
});
+ it('should remove a component & restore from component card', async () => {
+ const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ axiosMock.onDelete(url).reply(200);
+ renderLibraryUnitPage();
+
+ expect(await screen.findByText('text block 0')).toBeInTheDocument();
+ const menu = screen.getAllByRole('button', { name: /component actions menu/i })[0];
+ fireEvent.click(menu);
+
+ const removeButton = await screen.getByText('Remove from unit');
+ fireEvent.click(removeButton);
+
+ await waitFor(() => {
+ expect(axiosMock.history.delete[0].url).toEqual(url);
+ });
+ await waitFor(() => expect(mockShowToast).toHaveBeenCalled());
+
+ // Get restore / undo func from the toast
+ // @ts-ignore
+ const restoreFn = mockShowToast.mock.calls[0][1].onClick;
+
+ const restoreUrl = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ axiosMock.onPost(restoreUrl).reply(200);
+ // restore collection
+ restoreFn();
+ await waitFor(() => {
+ expect(axiosMock.history.post.length).toEqual(1);
+ });
+ expect(mockShowToast).toHaveBeenCalledWith('Undo successful');
+ });
+
+ it('should show error on remove a component', async () => {
+ const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ axiosMock.onDelete(url).reply(404);
+ renderLibraryUnitPage();
+
+ expect(await screen.findByText('text block 0')).toBeInTheDocument();
+ const menu = screen.getAllByRole('button', { name: /component actions menu/i })[0];
+ fireEvent.click(menu);
+
+ const removeButton = await screen.getByText('Remove from unit');
+ fireEvent.click(removeButton);
+
+ await waitFor(() => {
+ expect(axiosMock.history.delete[0].url).toEqual(url);
+ });
+ expect(mockShowToast).toHaveBeenCalledWith('Failed to remove component');
+ });
+
+ it('should show error on restore removed component', async () => {
+ const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ axiosMock.onDelete(url).reply(200);
+ renderLibraryUnitPage();
+
+ expect(await screen.findByText('text block 0')).toBeInTheDocument();
+ const menu = screen.getAllByRole('button', { name: /component actions menu/i })[0];
+ fireEvent.click(menu);
+
+ const removeButton = await screen.getByText('Remove from unit');
+ fireEvent.click(removeButton);
+
+ await waitFor(() => {
+ expect(axiosMock.history.delete[0].url).toEqual(url);
+ });
+ await waitFor(() => expect(mockShowToast).toHaveBeenCalled());
+
+ // Get restore / undo func from the toast
+ // @ts-ignore
+ const restoreFn = mockShowToast.mock.calls[0][1].onClick;
+
+ const restoreUrl = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ axiosMock.onPost(restoreUrl).reply(404);
+ // restore collection
+ restoreFn();
+ await waitFor(() => {
+ expect(axiosMock.history.post.length).toEqual(1);
+ });
+ expect(mockShowToast).toHaveBeenCalledWith('Failed to undo remove component operation');
+ });
+
+ it('should remove a component from component sidebar', async () => {
+ const url = getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId);
+ axiosMock.onDelete(url).reply(200);
+ renderLibraryUnitPage();
+
+ const component = await screen.findByText('text block 0');
+ userEvent.click(component);
+ const sidebar = await screen.findByTestId('library-sidebar');
+
+ const { findByRole, findByText } = within(sidebar);
+
+ const menu = await findByRole('button', { name: /component actions menu/i });
+ fireEvent.click(menu);
+
+ const removeButton = await findByText('Remove from unit');
+ fireEvent.click(removeButton);
+
+ await waitFor(() => {
+ expect(axiosMock.history.delete[0].url).toEqual(url);
+ });
+ await waitFor(() => expect(mockShowToast).toHaveBeenCalled());
+ });
+
it('should show editor on double click', async () => {
renderLibraryUnitPage();
const component = await screen.findByText('text block 0');