Skip to content

Commit 9c70fd9

Browse files
authored
fix: only show library actions available for the user (#2712)
* fix: only show the options available for the user * test: fix and add tests * fix: improve following the best practices * fix: apply the changes for collections and containers
1 parent 4df44ab commit 9c70fd9

6 files changed

Lines changed: 125 additions & 29 deletions

File tree

src/library-authoring/components/CollectionCard.test.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import userEvent from '@testing-library/user-event';
22
import type MockAdapter from 'axios-mock-adapter';
33

4+
import { mockContentLibrary } from '@src/library-authoring/data/api.mocks';
45
import {
56
initializeMocks, render as baseRender, screen, waitFor, within, fireEvent,
67
} from '../../testUtils';
@@ -43,14 +44,18 @@ const collectionHitSample: CollectionHit = {
4344
let axiosMock: MockAdapter;
4445
let mockShowToast;
4546

46-
const libraryId = 'lib:org1:Demo_Course';
47+
const { libraryId } = mockContentLibrary;
4748

48-
const render = (ui: React.ReactElement, showOnlyPublished: boolean = false) => baseRender(ui, {
49+
const render = (
50+
ui: React.ReactElement,
51+
showOnlyPublished: boolean = false,
52+
libId: string = libraryId,
53+
) => baseRender(ui, {
4954
path: '/library/:libraryId',
50-
params: { libraryId },
55+
params: { libraryId: libId },
5156
extraWrapper: ({ children }) => (
5257
<LibraryProvider
53-
libraryId="lib:Axim:TEST"
58+
libraryId={libId}
5459
showOnlyPublished={showOnlyPublished}
5560
>
5661
{children}
@@ -63,6 +68,7 @@ describe('<CollectionCard />', () => {
6368
const mocks = initializeMocks();
6469
axiosMock = mocks.axiosMock;
6570
mockShowToast = mocks.mockShowToast;
71+
mockContentLibrary.applyMock();
6672
});
6773

6874
it('should render the card with title and description', () => {
@@ -193,4 +199,24 @@ describe('<CollectionCard />', () => {
193199
expect(mockShowToast).toHaveBeenCalledWith('Failed to delete collection');
194200
});
195201
});
202+
203+
it('should not show delete button when library is read-only', async () => {
204+
const user = userEvent.setup();
205+
206+
// Render with read-only library
207+
render(<CollectionCard hit={collectionHitSample} />, false, mockContentLibrary.libraryIdReadOnly);
208+
209+
// Open menu
210+
const menu = await screen.findByTestId('collection-card-menu-toggle');
211+
expect(menu).toBeInTheDocument();
212+
await user.click(menu);
213+
214+
// Delete button should not be visible in readonly mode
215+
const deleteOption = screen.queryByRole('button', { name: 'Delete' });
216+
expect(deleteOption).not.toBeInTheDocument();
217+
218+
// Open button should still be visible
219+
const openOption = screen.queryByRole('button', { name: 'Open' });
220+
expect(openOption).toBeInTheDocument();
221+
});
196222
});

src/library-authoring/components/CollectionCard.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const CollectionMenu = ({ hit } : CollectionMenuProps) => {
2828
const intl = useIntl();
2929
const { showToast } = useContext(ToastContext);
3030
const { navigateTo } = useLibraryRoutes();
31+
const { readOnly } = useLibraryContext();
3132
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
3233
const { closeLibrarySidebar, sidebarItemInfo } = useSidebarContext();
3334
const {
@@ -90,9 +91,11 @@ const CollectionMenu = ({ hit } : CollectionMenuProps) => {
9091
<Dropdown.Item onClick={openCollection}>
9192
<FormattedMessage {...messages.menuOpen} />
9293
</Dropdown.Item>
93-
<Dropdown.Item onClick={openDeleteModal}>
94-
<FormattedMessage {...messages.deleteCollection} />
95-
</Dropdown.Item>
94+
{!readOnly && (
95+
<Dropdown.Item onClick={openDeleteModal}>
96+
<FormattedMessage {...messages.deleteCollection} />
97+
</Dropdown.Item>
98+
)}
9699
</Dropdown.Menu>
97100
</Dropdown>
98101
<DeleteModal

src/library-authoring/components/ComponentCard.test.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { mockContentLibrary } from '@src/library-authoring/data/api.mocks';
12
import {
23
fireEvent,
34
render as baseRender,
@@ -48,12 +49,12 @@ const contentHit: ContentHit = {
4849
publishStatus: PublishStatus.Published,
4950
};
5051

51-
const libraryId = 'lib:org1:Demo_Course';
52-
const render = () => baseRender(<ComponentCard hit={contentHit} />, {
52+
const { libraryId } = mockContentLibrary;
53+
const render = (libId: string = libraryId) => baseRender(<ComponentCard hit={contentHit} />, {
5354
path: '/library/:libraryId',
54-
params: { libraryId },
55+
params: { libraryId: libId },
5556
extraWrapper: ({ children }) => (
56-
<LibraryProvider libraryId={libraryId}>
57+
<LibraryProvider libraryId={libId}>
5758
<SidebarProvider>
5859
{ children }
5960
</SidebarProvider>
@@ -62,6 +63,9 @@ const render = () => baseRender(<ComponentCard hit={contentHit} />, {
6263
});
6364

6465
describe('<ComponentCard />', () => {
66+
beforeEach(() => {
67+
mockContentLibrary.applyMock();
68+
});
6569
it('should render the card with title and description', () => {
6670
initializeMocks();
6771
render();
@@ -127,7 +131,7 @@ describe('<ComponentCard />', () => {
127131
expect(menu).toBeInTheDocument();
128132
fireEvent.click(menu);
129133

130-
// Click copy to clipboard
134+
// Click edit option
131135
const editOption = await screen.findByRole('button', { name: 'Edit' });
132136
expect(editOption).toBeInTheDocument();
133137
fireEvent.click(editOption);
@@ -137,4 +141,18 @@ describe('<ComponentCard />', () => {
137141
search: '',
138142
});
139143
});
144+
145+
it('should not show edit button when library is read-only', async () => {
146+
initializeMocks();
147+
render(mockContentLibrary.libraryIdReadOnly);
148+
149+
// Open menu
150+
const menu = await screen.findByTestId('component-card-menu-toggle');
151+
expect(menu).toBeInTheDocument();
152+
fireEvent.click(menu);
153+
154+
// Edit button should not be visible in readonly mode
155+
const editOption = screen.queryByRole('button', { name: 'Edit' });
156+
expect(editOption).not.toBeInTheDocument();
157+
});
140158
});

src/library-authoring/components/ComponentMenu.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const ComponentMenu = ({ usageKey, index }: Props) => {
3535
collectionId,
3636
containerId,
3737
openComponentEditor,
38+
readOnly,
3839
} = useLibraryContext();
3940

4041
const {
@@ -103,9 +104,11 @@ export const ComponentMenu = ({ usageKey, index }: Props) => {
103104
data-testid="component-card-menu-toggle"
104105
/>
105106
<Dropdown.Menu>
106-
<Dropdown.Item {...(canEdit ? { onClick: handleEdit } : { disabled: true })}>
107-
<FormattedMessage {...messages.menuEdit} />
108-
</Dropdown.Item>
107+
{!readOnly && (
108+
<Dropdown.Item {...(canEdit ? { onClick: handleEdit } : { disabled: true })}>
109+
<FormattedMessage {...messages.menuEdit} />
110+
</Dropdown.Item>
111+
)}
109112
<Dropdown.Item onClick={updateClipboardClick}>
110113
<FormattedMessage {...messages.menuCopyToClipboard} />
111114
</Dropdown.Item>
@@ -114,10 +117,12 @@ export const ComponentMenu = ({ usageKey, index }: Props) => {
114117
<FormattedMessage {...messages.removeComponentFromUnitMenu} />
115118
</Dropdown.Item>
116119
)}
117-
<Dropdown.Item onClick={openDeleteModal}>
118-
<FormattedMessage {...messages.menuDelete} />
119-
</Dropdown.Item>
120-
{insideCollection && (
120+
{!readOnly && (
121+
<Dropdown.Item onClick={openDeleteModal}>
122+
<FormattedMessage {...messages.menuDelete} />
123+
</Dropdown.Item>
124+
)}
125+
{insideCollection && !readOnly && (
121126
<Dropdown.Item onClick={removeFromCollection}>
122127
<FormattedMessage
123128
{...containerMessages.menuRemoveFromContainer}
@@ -127,9 +132,11 @@ export const ComponentMenu = ({ usageKey, index }: Props) => {
127132
/>
128133
</Dropdown.Item>
129134
)}
130-
<Dropdown.Item onClick={showManageCollections}>
131-
<FormattedMessage {...containerMessages.menuAddToCollection} />
132-
</Dropdown.Item>
135+
{!readOnly && (
136+
<Dropdown.Item onClick={showManageCollections}>
137+
<FormattedMessage {...containerMessages.menuAddToCollection} />
138+
</Dropdown.Item>
139+
)}
133140
</Dropdown.Menu>
134141
{isDeleteModalOpen && (
135142
<ComponentDeleter

src/library-authoring/containers/ContainerCard.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,4 +493,40 @@ describe('<ContainerCard />', () => {
493493
});
494494
expect(axiosMock.history.post[0].url).toEqual(url);
495495
});
496+
497+
test.each([
498+
ContainerType.Unit,
499+
ContainerType.Subsection,
500+
ContainerType.Section,
501+
])('should not show delete and add to collection buttons when library is read-only for %s', async (containerType) => {
502+
const containerHit = getContainerHitSample(containerType);
503+
const user = userEvent.setup();
504+
505+
// Render with read-only library
506+
baseRender(<ContainerCard hit={containerHit} />, {
507+
path: '/library/:libraryId',
508+
params: { libraryId: mockContentLibrary.libraryIdReadOnly },
509+
extraWrapper: ({ children }) => (
510+
<LibraryProvider libraryId={mockContentLibrary.libraryIdReadOnly}>
511+
{children}
512+
</LibraryProvider>
513+
),
514+
});
515+
516+
// Open menu
517+
const menu = await screen.findByTestId('container-card-menu-toggle');
518+
expect(menu).toBeInTheDocument();
519+
await user.click(menu);
520+
521+
// Delete and Add to collection buttons should not be visible in readonly mode
522+
const deleteOption = screen.queryByRole('button', { name: 'Delete' });
523+
expect(deleteOption).not.toBeInTheDocument();
524+
525+
const addToCollectionOption = screen.queryByRole('button', { name: 'Add to collection' });
526+
expect(addToCollectionOption).not.toBeInTheDocument();
527+
528+
// Copy button should still be visible
529+
const copyOption = screen.queryByRole('button', { name: 'Copy to clipboard' });
530+
expect(copyOption).toBeInTheDocument();
531+
});
496532
});

src/library-authoring/containers/ContainerCard.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ type ContainerMenuProps = {
3636

3737
export const ContainerMenu = ({ containerKey, displayName, index } : ContainerMenuProps) => {
3838
const intl = useIntl();
39-
const { libraryId, collectionId, containerId } = useLibraryContext();
39+
const {
40+
libraryId, collectionId, containerId, readOnly,
41+
} = useLibraryContext();
4042
const {
4143
sidebarItemInfo,
4244
closeLibrarySidebar,
@@ -116,9 +118,11 @@ export const ContainerMenu = ({ containerKey, displayName, index } : ContainerMe
116118
<Dropdown.Item onClick={handleCopy}>
117119
<FormattedMessage {...messages.menuCopyContainer} />
118120
</Dropdown.Item>
119-
<Dropdown.Item onClick={confirmDelete}>
120-
<FormattedMessage {...messages.menuDeleteContainer} />
121-
</Dropdown.Item>
121+
{!readOnly && (
122+
<Dropdown.Item onClick={confirmDelete}>
123+
<FormattedMessage {...messages.menuDeleteContainer} />
124+
</Dropdown.Item>
125+
)}
122126
{(insideCollection || insideSection || insideSubsection) && (
123127
<Dropdown.Item onClick={handleRemove}>
124128
<FormattedMessage
@@ -129,9 +133,11 @@ export const ContainerMenu = ({ containerKey, displayName, index } : ContainerMe
129133
/>
130134
</Dropdown.Item>
131135
)}
132-
<Dropdown.Item onClick={showManageCollections}>
133-
<FormattedMessage {...messages.menuAddToCollection} />
134-
</Dropdown.Item>
136+
{!readOnly && (
137+
<Dropdown.Item onClick={showManageCollections}>
138+
<FormattedMessage {...messages.menuAddToCollection} />
139+
</Dropdown.Item>
140+
)}
135141
</Dropdown.Menu>
136142
</Dropdown>
137143
{isConfirmingDelete && (

0 commit comments

Comments
 (0)