Skip to content

Commit

Permalink
fix: add component to collection on paste [FC-0062] (openedx#1450)
Browse files Browse the repository at this point in the history
Link component to collection if pasted in a collection page.
Fixes: openedx#1435

(cherry picked from commit 549dbaa)
  • Loading branch information
navinkarkera committed Nov 5, 2024
1 parent f10ad9f commit 732092d
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 10 deletions.
100 changes: 94 additions & 6 deletions src/library-authoring/add-content/AddContentContainer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,31 @@ 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';

mockBroadcastChannel();

const { libraryId } = mockContentLibrary;
const render = () => baseRender(<AddContentContainer />, {
path: '/library/:libraryId/*',
params: { libraryId },
extraWrapper: ({ children }) => <LibraryProvider libraryId={libraryId}>{ children }</LibraryProvider>,
});
const render = (collectionId?: string) => {
const params: { libraryId: string, collectionId?: string } = { libraryId };
if (collectionId) {
params.collectionId = collectionId;
}
return baseRender(<AddContentContainer />, {
path: '/library/:libraryId/*',
params,
extraWrapper: ({ children }) => (
<LibraryProvider
libraryId={libraryId}
collectionId={collectionId}
>{ children }
</LibraryProvider>
),
});
};

describe('<AddContentContainer />', () => {
it('should render content buttons', () => {
Expand Down Expand Up @@ -47,6 +59,29 @@ describe('<AddContentContainer />', () => {
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 () => {
Expand Down Expand Up @@ -76,6 +111,59 @@ describe('<AddContentContainer />', () => {
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:
Expand Down
19 changes: 15 additions & 4 deletions src/library-authoring/add-content/AddContentContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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));
Expand All @@ -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 {
Expand Down Expand Up @@ -210,6 +217,10 @@ const AddContentContainer = () => {
showToast(intl.formatMessage(messages.pastingClipboardMessage));
}

if (updateComponentsMutation.isLoading) {
showToast(intl.formatMessage(messages.linkingComponentMessage));
}

return (
<Stack direction="vertical">
{collectionId ? (
Expand Down
5 changes: 5 additions & 0 deletions src/library-authoring/add-content/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down

0 comments on commit 732092d

Please sign in to comment.