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
This commit is contained in:
committed by
GitHub
parent
4df44ab6cf
commit
9c70fd9216
@@ -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 }) => (
|
||||
<LibraryProvider
|
||||
libraryId="lib:Axim:TEST"
|
||||
libraryId={libId}
|
||||
showOnlyPublished={showOnlyPublished}
|
||||
>
|
||||
{children}
|
||||
@@ -63,6 +68,7 @@ describe('<CollectionCard />', () => {
|
||||
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('<CollectionCard />', () => {
|
||||
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(<CollectionCard hit={collectionHitSample} />, 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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
<Dropdown.Item onClick={openCollection}>
|
||||
<FormattedMessage {...messages.menuOpen} />
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item onClick={openDeleteModal}>
|
||||
<FormattedMessage {...messages.deleteCollection} />
|
||||
</Dropdown.Item>
|
||||
{!readOnly && (
|
||||
<Dropdown.Item onClick={openDeleteModal}>
|
||||
<FormattedMessage {...messages.deleteCollection} />
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
<DeleteModal
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { mockContentLibrary } from '@src/library-authoring/data/api.mocks';
|
||||
import {
|
||||
fireEvent,
|
||||
render as baseRender,
|
||||
@@ -48,12 +49,12 @@ const contentHit: ContentHit = {
|
||||
publishStatus: PublishStatus.Published,
|
||||
};
|
||||
|
||||
const libraryId = 'lib:org1:Demo_Course';
|
||||
const render = () => baseRender(<ComponentCard hit={contentHit} />, {
|
||||
const { libraryId } = mockContentLibrary;
|
||||
const render = (libId: string = libraryId) => baseRender(<ComponentCard hit={contentHit} />, {
|
||||
path: '/library/:libraryId',
|
||||
params: { libraryId },
|
||||
params: { libraryId: libId },
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider libraryId={libraryId}>
|
||||
<LibraryProvider libraryId={libId}>
|
||||
<SidebarProvider>
|
||||
{ children }
|
||||
</SidebarProvider>
|
||||
@@ -62,6 +63,9 @@ const render = () => baseRender(<ComponentCard hit={contentHit} />, {
|
||||
});
|
||||
|
||||
describe('<ComponentCard />', () => {
|
||||
beforeEach(() => {
|
||||
mockContentLibrary.applyMock();
|
||||
});
|
||||
it('should render the card with title and description', () => {
|
||||
initializeMocks();
|
||||
render();
|
||||
@@ -127,7 +131,7 @@ describe('<ComponentCard />', () => {
|
||||
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('<ComponentCard />', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item {...(canEdit ? { onClick: handleEdit } : { disabled: true })}>
|
||||
<FormattedMessage {...messages.menuEdit} />
|
||||
</Dropdown.Item>
|
||||
{!readOnly && (
|
||||
<Dropdown.Item {...(canEdit ? { onClick: handleEdit } : { disabled: true })}>
|
||||
<FormattedMessage {...messages.menuEdit} />
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
<Dropdown.Item onClick={updateClipboardClick}>
|
||||
<FormattedMessage {...messages.menuCopyToClipboard} />
|
||||
</Dropdown.Item>
|
||||
@@ -114,10 +117,12 @@ export const ComponentMenu = ({ usageKey, index }: Props) => {
|
||||
<FormattedMessage {...messages.removeComponentFromUnitMenu} />
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
<Dropdown.Item onClick={openDeleteModal}>
|
||||
<FormattedMessage {...messages.menuDelete} />
|
||||
</Dropdown.Item>
|
||||
{insideCollection && (
|
||||
{!readOnly && (
|
||||
<Dropdown.Item onClick={openDeleteModal}>
|
||||
<FormattedMessage {...messages.menuDelete} />
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
{insideCollection && !readOnly && (
|
||||
<Dropdown.Item onClick={removeFromCollection}>
|
||||
<FormattedMessage
|
||||
{...containerMessages.menuRemoveFromContainer}
|
||||
@@ -127,9 +132,11 @@ export const ComponentMenu = ({ usageKey, index }: Props) => {
|
||||
/>
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
<Dropdown.Item onClick={showManageCollections}>
|
||||
<FormattedMessage {...containerMessages.menuAddToCollection} />
|
||||
</Dropdown.Item>
|
||||
{!readOnly && (
|
||||
<Dropdown.Item onClick={showManageCollections}>
|
||||
<FormattedMessage {...containerMessages.menuAddToCollection} />
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
</Dropdown.Menu>
|
||||
{isDeleteModalOpen && (
|
||||
<ComponentDeleter
|
||||
|
||||
@@ -493,4 +493,40 @@ describe('<ContainerCard />', () => {
|
||||
});
|
||||
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(<ContainerCard hit={containerHit} />, {
|
||||
path: '/library/:libraryId',
|
||||
params: { libraryId: mockContentLibrary.libraryIdReadOnly },
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider libraryId={mockContentLibrary.libraryIdReadOnly}>
|
||||
{children}
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
<Dropdown.Item onClick={handleCopy}>
|
||||
<FormattedMessage {...messages.menuCopyContainer} />
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item onClick={confirmDelete}>
|
||||
<FormattedMessage {...messages.menuDeleteContainer} />
|
||||
</Dropdown.Item>
|
||||
{!readOnly && (
|
||||
<Dropdown.Item onClick={confirmDelete}>
|
||||
<FormattedMessage {...messages.menuDeleteContainer} />
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
{(insideCollection || insideSection || insideSubsection) && (
|
||||
<Dropdown.Item onClick={handleRemove}>
|
||||
<FormattedMessage
|
||||
@@ -129,9 +133,11 @@ export const ContainerMenu = ({ containerKey, displayName, index } : ContainerMe
|
||||
/>
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
<Dropdown.Item onClick={showManageCollections}>
|
||||
<FormattedMessage {...messages.menuAddToCollection} />
|
||||
</Dropdown.Item>
|
||||
{!readOnly && (
|
||||
<Dropdown.Item onClick={showManageCollections}>
|
||||
<FormattedMessage {...messages.menuAddToCollection} />
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
{isConfirmingDelete && (
|
||||
|
||||
Reference in New Issue
Block a user