From 3b8b19484e7caef9807bfceefffdc420387c6840 Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Fri, 28 Nov 2025 19:21:32 -0500 Subject: [PATCH 1/4] fix: only show the options available for the user --- .../components/ComponentMenu.tsx | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) 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 && ( Date: Wed, 10 Dec 2025 18:26:25 -0500 Subject: [PATCH 2/4] test: fix and add tests --- .../components/ComponentCard.test.tsx | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/library-authoring/components/ComponentCard.test.tsx b/src/library-authoring/components/ComponentCard.test.tsx index fd0a76e7e6..cf916233ea 100644 --- a/src/library-authoring/components/ComponentCard.test.tsx +++ b/src/library-authoring/components/ComponentCard.test.tsx @@ -11,6 +11,7 @@ import { getClipboardUrl } from '../../generic/data/api'; import { ContentHit } from '../../search-manager'; import ComponentCard from './ComponentCard'; import { PublishStatus } from '../../search-manager/data/api'; +import { mockContentLibrary } from '../data/api.mocks'; const mockNavigate = jest.fn(); @@ -48,12 +49,12 @@ const contentHit: ContentHit = { publishStatus: PublishStatus.Published, }; -const libraryId = 'lib:org1:Demo_Course'; -const render = () => baseRender(, { +const libraryId = mockContentLibrary.libraryId; +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(); + }); }); From 570cb8ae908cfb28a964ac603e2a7a954cde17c8 Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Wed, 10 Dec 2025 19:09:12 -0500 Subject: [PATCH 3/4] fix: improve following the best practices --- src/library-authoring/components/ComponentCard.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/library-authoring/components/ComponentCard.test.tsx b/src/library-authoring/components/ComponentCard.test.tsx index cf916233ea..530b4f4c4f 100644 --- a/src/library-authoring/components/ComponentCard.test.tsx +++ b/src/library-authoring/components/ComponentCard.test.tsx @@ -1,3 +1,4 @@ +import { mockContentLibrary } from '@src/library-authoring/data/api.mocks'; import { fireEvent, render as baseRender, @@ -11,7 +12,6 @@ import { getClipboardUrl } from '../../generic/data/api'; import { ContentHit } from '../../search-manager'; import ComponentCard from './ComponentCard'; import { PublishStatus } from '../../search-manager/data/api'; -import { mockContentLibrary } from '../data/api.mocks'; const mockNavigate = jest.fn(); @@ -49,7 +49,7 @@ const contentHit: ContentHit = { publishStatus: PublishStatus.Published, }; -const libraryId = mockContentLibrary.libraryId; +const { libraryId } = mockContentLibrary; const render = (libId: string = libraryId) => baseRender(, { path: '/library/:libraryId', params: { libraryId: libId }, From 8bb04d9493b07720985c2fae20f29874e507b44d Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Mon, 15 Dec 2025 13:01:55 -0500 Subject: [PATCH 4/4] fix: apply the changes for collections and containers --- .../components/CollectionCard.test.tsx | 34 +++++++++++++++--- .../components/CollectionCard.tsx | 9 +++-- .../containers/ContainerCard.test.tsx | 36 +++++++++++++++++++ .../containers/ContainerCard.tsx | 20 +++++++---- 4 files changed, 85 insertions(+), 14 deletions(-) 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 && ( + + + + )} ', () => { }); 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 && (