diff --git a/src/library-authoring/components/CollectionCard.test.tsx b/src/library-authoring/components/CollectionCard.test.tsx index 76a240a486..8520827e3c 100644 --- a/src/library-authoring/components/CollectionCard.test.tsx +++ b/src/library-authoring/components/CollectionCard.test.tsx @@ -1,6 +1,7 @@ import userEvent from '@testing-library/user-event'; import type MockAdapter from 'axios-mock-adapter'; +import { mockContentLibrary } from '@src/library-authoring/data/api.mocks'; import { initializeMocks, render as baseRender, screen, waitFor, within, fireEvent, } from '../../testUtils'; @@ -43,14 +44,18 @@ const collectionHitSample: CollectionHit = { let axiosMock: MockAdapter; let mockShowToast; -const libraryId = 'lib:org1:Demo_Course'; +const { libraryId } = mockContentLibrary; -const render = (ui: React.ReactElement, showOnlyPublished: boolean = false) => baseRender(ui, { +const render = ( + ui: React.ReactElement, + showOnlyPublished: boolean = false, + libId: string = libraryId, +) => baseRender(ui, { path: '/library/:libraryId', - params: { libraryId }, + params: { libraryId: libId }, extraWrapper: ({ children }) => ( {children} @@ -63,6 +68,7 @@ describe('', () => { const mocks = initializeMocks(); axiosMock = mocks.axiosMock; mockShowToast = mocks.mockShowToast; + mockContentLibrary.applyMock(); }); it('should render the card with title and description', () => { @@ -193,4 +199,24 @@ describe('', () => { expect(mockShowToast).toHaveBeenCalledWith('Failed to delete collection'); }); }); + + it('should not show delete button when library is read-only', async () => { + const user = userEvent.setup(); + + // Render with read-only library + render(, false, mockContentLibrary.libraryIdReadOnly); + + // Open menu + const menu = await screen.findByTestId('collection-card-menu-toggle'); + expect(menu).toBeInTheDocument(); + await user.click(menu); + + // Delete button should not be visible in readonly mode + const deleteOption = screen.queryByRole('button', { name: 'Delete' }); + expect(deleteOption).not.toBeInTheDocument(); + + // Open button should still be visible + const openOption = screen.queryByRole('button', { name: 'Open' }); + expect(openOption).toBeInTheDocument(); + }); }); diff --git a/src/library-authoring/components/CollectionCard.tsx b/src/library-authoring/components/CollectionCard.tsx index e5c37d9064..2552b611f3 100644 --- a/src/library-authoring/components/CollectionCard.tsx +++ b/src/library-authoring/components/CollectionCard.tsx @@ -28,6 +28,7 @@ const CollectionMenu = ({ hit } : CollectionMenuProps) => { const intl = useIntl(); const { showToast } = useContext(ToastContext); const { navigateTo } = useLibraryRoutes(); + const { readOnly } = useLibraryContext(); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false); const { closeLibrarySidebar, sidebarItemInfo } = useSidebarContext(); const { @@ -90,9 +91,11 @@ const CollectionMenu = ({ hit } : CollectionMenuProps) => { - - - + {!readOnly && ( + + + + )} baseRender(, { +const { libraryId } = mockContentLibrary; +const render = (libId: string = libraryId) => baseRender(, { path: '/library/:libraryId', - params: { libraryId }, + params: { libraryId: libId }, extraWrapper: ({ children }) => ( - + { children } @@ -62,6 +63,9 @@ const render = () => baseRender(, { }); describe('', () => { + beforeEach(() => { + mockContentLibrary.applyMock(); + }); it('should render the card with title and description', () => { initializeMocks(); render(); @@ -127,7 +131,7 @@ describe('', () => { expect(menu).toBeInTheDocument(); fireEvent.click(menu); - // Click copy to clipboard + // Click edit option const editOption = await screen.findByRole('button', { name: 'Edit' }); expect(editOption).toBeInTheDocument(); fireEvent.click(editOption); @@ -137,4 +141,18 @@ describe('', () => { search: '', }); }); + + it('should not show edit button when library is read-only', async () => { + initializeMocks(); + render(mockContentLibrary.libraryIdReadOnly); + + // Open menu + const menu = await screen.findByTestId('component-card-menu-toggle'); + expect(menu).toBeInTheDocument(); + fireEvent.click(menu); + + // Edit button should not be visible in readonly mode + const editOption = screen.queryByRole('button', { name: 'Edit' }); + expect(editOption).not.toBeInTheDocument(); + }); }); diff --git a/src/library-authoring/components/ComponentMenu.tsx b/src/library-authoring/components/ComponentMenu.tsx index 33b67f1e65..af90456cd2 100644 --- a/src/library-authoring/components/ComponentMenu.tsx +++ b/src/library-authoring/components/ComponentMenu.tsx @@ -35,6 +35,7 @@ export const ComponentMenu = ({ usageKey, index }: Props) => { collectionId, containerId, openComponentEditor, + readOnly, } = useLibraryContext(); const { @@ -103,9 +104,11 @@ export const ComponentMenu = ({ usageKey, index }: Props) => { data-testid="component-card-menu-toggle" /> - - - + {!readOnly && ( + + + + )} @@ -114,10 +117,12 @@ export const ComponentMenu = ({ usageKey, index }: Props) => { )} - - - - {insideCollection && ( + {!readOnly && ( + + + + )} + {insideCollection && !readOnly && ( { /> )} - - - + {!readOnly && ( + + + + )} {isDeleteModalOpen && ( ', () => { }); expect(axiosMock.history.post[0].url).toEqual(url); }); + + test.each([ + ContainerType.Unit, + ContainerType.Subsection, + ContainerType.Section, + ])('should not show delete and add to collection buttons when library is read-only for %s', async (containerType) => { + const containerHit = getContainerHitSample(containerType); + const user = userEvent.setup(); + + // Render with read-only library + baseRender(, { + path: '/library/:libraryId', + params: { libraryId: mockContentLibrary.libraryIdReadOnly }, + extraWrapper: ({ children }) => ( + + {children} + + ), + }); + + // Open menu + const menu = await screen.findByTestId('container-card-menu-toggle'); + expect(menu).toBeInTheDocument(); + await user.click(menu); + + // Delete and Add to collection buttons should not be visible in readonly mode + const deleteOption = screen.queryByRole('button', { name: 'Delete' }); + expect(deleteOption).not.toBeInTheDocument(); + + const addToCollectionOption = screen.queryByRole('button', { name: 'Add to collection' }); + expect(addToCollectionOption).not.toBeInTheDocument(); + + // Copy button should still be visible + const copyOption = screen.queryByRole('button', { name: 'Copy to clipboard' }); + expect(copyOption).toBeInTheDocument(); + }); }); diff --git a/src/library-authoring/containers/ContainerCard.tsx b/src/library-authoring/containers/ContainerCard.tsx index 229c22b7f5..00fde2c8da 100644 --- a/src/library-authoring/containers/ContainerCard.tsx +++ b/src/library-authoring/containers/ContainerCard.tsx @@ -36,7 +36,9 @@ type ContainerMenuProps = { export const ContainerMenu = ({ containerKey, displayName, index } : ContainerMenuProps) => { const intl = useIntl(); - const { libraryId, collectionId, containerId } = useLibraryContext(); + const { + libraryId, collectionId, containerId, readOnly, + } = useLibraryContext(); const { sidebarItemInfo, closeLibrarySidebar, @@ -116,9 +118,11 @@ export const ContainerMenu = ({ containerKey, displayName, index } : ContainerMe - - - + {!readOnly && ( + + + + )} {(insideCollection || insideSection || insideSubsection) && ( )} - - - + {!readOnly && ( + + + + )} {isConfirmingDelete && (