Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add component to collection on paste [FC-0062] #1450

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for fixing the "create block in collection" issue here too @navinkarkera ! 🚀

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