From 2dfcbab9aff91ee935c5dea778a02519baea6e1e Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Fri, 7 Feb 2025 12:24:03 -0500 Subject: [PATCH] feat: Avoid copy/edit multilevel blocks in libraries --- src/constants.js | 2 ++ .../add-content/AddContentContainer.test.tsx | 21 +++++++++++++++---- .../add-content/AddContentContainer.tsx | 10 +++++++-- .../component-info/ComponentInfo.test.tsx | 11 ++++++++++ .../components/ComponentEditorModal.tsx | 6 ++++++ src/library-authoring/data/api.mocks.ts | 13 ++++++++++++ 6 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/constants.js b/src/constants.js index 80e7cdd778..a23758cefb 100644 --- a/src/constants.js +++ b/src/constants.js @@ -81,3 +81,5 @@ export const REGEX_RULES = { export const IFRAME_FEATURE_POLICY = ( 'microphone *; camera *; midi *; geolocation *; encrypted-media *; clipboard-write *' ); + +export const MULTI_LEVEL_XBLOCKS = ['conditional', 'problem-builder', 'step-builder']; diff --git a/src/library-authoring/add-content/AddContentContainer.test.tsx b/src/library-authoring/add-content/AddContentContainer.test.tsx index 229948c39e..b4c38aa24d 100644 --- a/src/library-authoring/add-content/AddContentContainer.test.tsx +++ b/src/library-authoring/add-content/AddContentContainer.test.tsx @@ -238,10 +238,23 @@ describe('', () => { const pasteButton = await screen.findByRole('button', { name: /paste from clipboard/i }); fireEvent.click(pasteButton); - await waitFor(() => { - expect(axiosMock.history.post.length).toEqual(0); - expect(mockShowToast).toHaveBeenCalledWith(errMsg); - }); + await waitFor(() => expect(axiosMock.history.post.length).toEqual(0)); + await waitFor(() => expect(mockShowToast).toHaveBeenCalledWith(errMsg)); + }); + + it('should stop user from pasting multilevel blocks and show toast', async () => { + // Simulate having an HTML block in the clipboard: + mockClipboardHtml.applyMock('conditional'); + + const errMsg = 'Libraries do not support this type of content yet.'; + + render(); + + const pasteButton = await screen.findByRole('button', { name: /paste from clipboard/i }); + fireEvent.click(pasteButton); + + await waitFor(() => expect(axiosMock.history.post.length).toEqual(0)); + await waitFor(() => expect(mockShowToast).toHaveBeenCalledWith(errMsg)); }); test.each([ diff --git a/src/library-authoring/add-content/AddContentContainer.tsx b/src/library-authoring/add-content/AddContentContainer.tsx index 1a2e28b1a9..995695134e 100644 --- a/src/library-authoring/add-content/AddContentContainer.tsx +++ b/src/library-authoring/add-content/AddContentContainer.tsx @@ -30,6 +30,7 @@ import { canEditComponent } from '../components/ComponentEditorModal'; import { PickLibraryContentModal } from './PickLibraryContentModal'; import messages from './messages'; +import { MULTI_LEVEL_XBLOCKS } from '../../constants'; type ContentType = { name: string, @@ -78,7 +79,6 @@ const AddContentContainer = () => { const { showToast } = useContext(ToastContext); const canEdit = useSelector(getCanEdit); const { showPasteXBlock, sharedClipboardData } = useCopyToClipboard(canEdit); - const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle(); const parseErrorMsg = ( @@ -98,7 +98,13 @@ const AddContentContainer = () => { return intl.formatMessage(defaultMessage); }; - const isBlockTypeEnabled = (blockType: string) => getConfig().LIBRARY_SUPPORTED_BLOCKS.includes(blockType); + const isBlockTypeEnabled = (blockType: string) => { + // For now multilevel blocks are not supported in libraries + if (MULTI_LEVEL_XBLOCKS.includes(blockType)) { + return false; + } + return getConfig().LIBRARY_SUPPORTED_BLOCKS.includes(blockType); + }; const collectionButtonData = { name: intl.formatMessage(messages.collectionButton), diff --git a/src/library-authoring/component-info/ComponentInfo.test.tsx b/src/library-authoring/component-info/ComponentInfo.test.tsx index 6ccebc29fe..fce6f2d21e 100644 --- a/src/library-authoring/component-info/ComponentInfo.test.tsx +++ b/src/library-authoring/component-info/ComponentInfo.test.tsx @@ -60,6 +60,17 @@ describe(' Sidebar', () => { expect(screen.queryByRole('button', { name: /Edit component/ })).not.toBeInTheDocument(); }); + it('should show a disabled "Edit" button when the component type is multilevel', async () => { + initializeMocks(); + render( + , + withLibraryId(mockContentLibrary.libraryId, mockLibraryBlockMetadata.usageKeyMultiLevelXBlock), + ); + + const editButton = await screen.findByRole('button', { name: /Edit component/ }); + expect(editButton).toBeDisabled(); + }); + it('should show a working "Edit" button for a normal component', async () => { initializeMocks(); render( diff --git a/src/library-authoring/components/ComponentEditorModal.tsx b/src/library-authoring/components/ComponentEditorModal.tsx index e139a8c409..fd94b9189e 100644 --- a/src/library-authoring/components/ComponentEditorModal.tsx +++ b/src/library-authoring/components/ComponentEditorModal.tsx @@ -6,6 +6,7 @@ import EditorPage from '../../editors/EditorPage'; import { getBlockType } from '../../generic/key-utils'; import { useLibraryContext } from '../common/context/LibraryContext'; import { invalidateComponentData } from '../data/apiHooks'; +import { MULTI_LEVEL_XBLOCKS } from '../../constants'; export function canEditComponent(usageKey: string): boolean { let blockType: string; @@ -15,6 +16,11 @@ export function canEditComponent(usageKey: string): boolean { return false; } + // For now multilevel blocks are not supported in libraries + if (MULTI_LEVEL_XBLOCKS.includes(blockType)) { + return false; + } + return getConfig().LIBRARY_SUPPORTED_BLOCKS.includes(blockType); } diff --git a/src/library-authoring/data/api.mocks.ts b/src/library-authoring/data/api.mocks.ts index ce4be29168..268565d3ea 100644 --- a/src/library-authoring/data/api.mocks.ts +++ b/src/library-authoring/data/api.mocks.ts @@ -269,6 +269,7 @@ export async function mockXBlockFields(usageKey: string): Promise jest.spyOn(api, 'getXBlockFields').mockImplementation(mockXBlockFields); @@ -378,6 +385,12 @@ mockLibraryBlockMetadata.dataThirdPartyXBlock = { id: mockLibraryBlockMetadata.usageKeyThirdPartyXBlock, blockType: 'third_party', } satisfies api.LibraryBlockMetadata; +mockLibraryBlockMetadata.usageKeyMultiLevelXBlock = mockXBlockFields.usageKeyMultiLevel; +mockLibraryBlockMetadata.dataMultiLevelXBlock = { + ...mockLibraryBlockMetadata.dataPublished, + id: mockLibraryBlockMetadata.usageKeyMultiLevelXBlock, + blockType: 'conditional', +} satisfies api.LibraryBlockMetadata; mockLibraryBlockMetadata.usageKeyForTags = mockContentTaxonomyTagsData.largeTagsId; mockLibraryBlockMetadata.usageKeyWithCollections = 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'; mockLibraryBlockMetadata.dataWithCollections = {