diff --git a/src/library-authoring/add-content/AddContentContainer.test.tsx b/src/library-authoring/add-content/AddContentContainer.test.tsx index e8f53c3fd..95e9e4ccb 100644 --- a/src/library-authoring/add-content/AddContentContainer.test.tsx +++ b/src/library-authoring/add-content/AddContentContainer.test.tsx @@ -6,7 +6,7 @@ import { initializeMocks, } from '../../testUtils'; import { mockContentLibrary } from '../data/api.mocks'; -import { getCreateLibraryBlockUrl, getLibraryPasteClipboardUrl } from '../data/api'; +import { getCreateLibraryBlockUrl, getLibraryCollectionComponentApiUrl, getLibraryPasteClipboardUrl } from '../data/api'; import { mockBroadcastChannel, mockClipboardEmpty, mockClipboardHtml } from '../../generic/data/api.mock'; import { LibraryProvider } from '../common/context'; import AddContentContainer from './AddContentContainer'; @@ -14,11 +14,23 @@ import AddContentContainer from './AddContentContainer'; mockBroadcastChannel(); const { libraryId } = mockContentLibrary; -const render = () => baseRender(, { - path: '/library/:libraryId/*', - params: { libraryId }, - extraWrapper: ({ children }) => { children }, -}); +const render = (collectionId?: string) => { + const params: { libraryId: string, collectionId?: string } = { libraryId }; + if (collectionId) { + params.collectionId = collectionId; + } + return baseRender(, { + path: '/library/:libraryId/*', + params, + extraWrapper: ({ children }) => ( + { children } + + ), + }); +}; describe('', () => { it('should render content buttons', () => { @@ -47,6 +59,29 @@ describe('', () => { fireEvent.click(textButton); await waitFor(() => expect(axiosMock.history.post[0].url).toEqual(url)); + await waitFor(() => expect(axiosMock.history.patch.length).toEqual(0)); + }); + + it('should create a content in a collection', async () => { + const { axiosMock } = initializeMocks(); + mockClipboardEmpty.applyMock(); + const collectionId = 'some-collection-id'; + const url = getCreateLibraryBlockUrl(libraryId); + const collectionComponentUrl = getLibraryCollectionComponentApiUrl( + libraryId, + collectionId, + ); + axiosMock.onPost(url).reply(200, { id: 'some-component-id' }); + axiosMock.onPatch(collectionComponentUrl).reply(200); + + render(collectionId); + + const textButton = screen.getByRole('button', { name: /text/i }); + fireEvent.click(textButton); + + await waitFor(() => expect(axiosMock.history.post[0].url).toEqual(url)); + await waitFor(() => expect(axiosMock.history.patch.length).toEqual(1)); + await waitFor(() => expect(axiosMock.history.patch[0].url).toEqual(collectionComponentUrl)); }); it('should render paste button if clipboard contains pastable xblock', async () => { @@ -76,6 +111,59 @@ describe('', () => { await waitFor(() => expect(axiosMock.history.post[0].url).toEqual(pasteUrl)); }); + it('should paste content inside a collection', async () => { + const { axiosMock } = initializeMocks(); + // Simulate having an HTML block in the clipboard: + const getClipboardSpy = mockClipboardHtml.applyMock(); + + const pasteUrl = getLibraryPasteClipboardUrl(libraryId); + const collectionId = 'some-collection-id'; + const collectionComponentUrl = getLibraryCollectionComponentApiUrl( + libraryId, + collectionId, + ); + axiosMock.onPatch(collectionComponentUrl).reply(200); + axiosMock.onPost(pasteUrl).reply(200, { id: 'some-component-id' }); + + render(collectionId); + + expect(getClipboardSpy).toHaveBeenCalled(); // Hmm, this is getting called four times! Refactor to use react-query. + + const pasteButton = await screen.findByRole('button', { name: /paste from clipboard/i }); + fireEvent.click(pasteButton); + + await waitFor(() => expect(axiosMock.history.post[0].url).toEqual(pasteUrl)); + await waitFor(() => expect(axiosMock.history.patch.length).toEqual(1)); + await waitFor(() => expect(axiosMock.history.patch[0].url).toEqual(collectionComponentUrl)); + }); + + it('should show error toast on linking failure', async () => { + const { axiosMock, mockShowToast } = initializeMocks(); + // Simulate having an HTML block in the clipboard: + const getClipboardSpy = mockClipboardHtml.applyMock(); + + const pasteUrl = getLibraryPasteClipboardUrl(libraryId); + const collectionId = 'some-collection-id'; + const collectionComponentUrl = getLibraryCollectionComponentApiUrl( + libraryId, + collectionId, + ); + axiosMock.onPatch(collectionComponentUrl).reply(500); + axiosMock.onPost(pasteUrl).reply(200, { id: 'some-component-id' }); + + render(collectionId); + + expect(getClipboardSpy).toHaveBeenCalled(); // Hmm, this is getting called four times! Refactor to use react-query. + + const pasteButton = await screen.findByRole('button', { name: /paste from clipboard/i }); + fireEvent.click(pasteButton); + + await waitFor(() => expect(axiosMock.history.post[0].url).toEqual(pasteUrl)); + await waitFor(() => expect(axiosMock.history.patch.length).toEqual(1)); + await waitFor(() => expect(axiosMock.history.patch[0].url).toEqual(collectionComponentUrl)); + expect(mockShowToast).toHaveBeenCalledWith('There was an error linking the content to this collection.'); + }); + it('should handle failure to paste content', async () => { const { axiosMock, mockShowToast } = initializeMocks(); // Simulate having an HTML block in the clipboard: diff --git a/src/library-authoring/add-content/AddContentContainer.tsx b/src/library-authoring/add-content/AddContentContainer.tsx index 27a5bed84..3ae948b8a 100644 --- a/src/library-authoring/add-content/AddContentContainer.tsx +++ b/src/library-authoring/add-content/AddContentContainer.tsx @@ -158,6 +158,14 @@ const AddContentContainer = () => { contentTypes.push(pasteButton); } + const linkComponent = (usageKey: string) => { + updateComponentsMutation.mutateAsync([usageKey]).then(() => { + showToast(intl.formatMessage(messages.successAssociateComponentMessage)); + }).catch(() => { + showToast(intl.formatMessage(messages.errorAssociateComponentMessage)); + }); + }; + const onPaste = () => { if (!isBlockTypeEnabled(sharedClipboardData.content?.blockType)) { showToast(intl.formatMessage(messages.unsupportedBlockPasteClipboardMessage)); @@ -166,7 +174,8 @@ const AddContentContainer = () => { pasteClipboardMutation.mutateAsync({ libraryId, blockId: `${uuid4()}`, - }).then(() => { + }).then((data) => { + linkComponent(data.id); showToast(intl.formatMessage(messages.successPasteClipboardMessage)); }).catch((error) => { showToast(parsePasteErrorMsg(error)); @@ -179,10 +188,8 @@ const AddContentContainer = () => { blockType, definitionId: `${uuid4()}`, }).then((data) => { + linkComponent(data.id); const hasEditor = canEditComponent(data.id); - updateComponentsMutation.mutateAsync([data.id]).catch(() => { - showToast(intl.formatMessage(messages.errorAssociateComponentMessage)); - }); if (hasEditor) { openComponentEditor(data.id); } else { @@ -210,6 +217,10 @@ const AddContentContainer = () => { showToast(intl.formatMessage(messages.pastingClipboardMessage)); } + if (updateComponentsMutation.isLoading) { + showToast(intl.formatMessage(messages.linkingComponentMessage)); + } + return ( {collectionId ? ( diff --git a/src/library-authoring/add-content/messages.ts b/src/library-authoring/add-content/messages.ts index 6971849d7..b1be04e89 100644 --- a/src/library-authoring/add-content/messages.ts +++ b/src/library-authoring/add-content/messages.ts @@ -66,6 +66,11 @@ const messages = defineMessages({ defaultMessage: 'There was an error creating the content.', description: 'Message when creation of content in library is on error', }, + linkingComponentMessage: { + id: 'course-authoring.library-authoring.linking-collection-content.progress.text', + defaultMessage: 'Adding component to collection...', + description: 'Message when component is being linked to collection in library', + }, successAssociateComponentMessage: { id: 'course-authoring.library-authoring.associate-collection-content.success.text', defaultMessage: 'Content linked successfully.',