Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 7 additions & 17 deletions src/library-authoring/component-info/ComponentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,18 @@ import {
Tabs,
Stack,
} from '@openedx/paragon';
import { useContext } from 'react';

import { ToastContext } from '../../generic/toast-context';
import { useLibraryContext } from '../common/context';
import { ComponentMenu } from '../components';
import { canEditComponent } from '../components/ComponentEditorModal';
import { useAddComponentToCourse } from '../data/apiHooks';
import ComponentDetails from './ComponentDetails';
import ComponentManagement from './ComponentManagement';
import ComponentPreview from './ComponentPreview';
import messages from './messages';
import { getBlockType } from '../../generic/key-utils';

const ComponentInfo = () => {
const intl = useIntl();
const { showToast } = useContext(ToastContext);

const {
sidebarComponentUsageKey: usageKey,
Expand All @@ -34,22 +31,15 @@ const ComponentInfo = () => {
throw new Error('usageKey is required');
}

const {
mutateAsync: addComponentToCourse,
reset,
} = useAddComponentToCourse(parentLocator, usageKey);

const canEdit = canEditComponent(usageKey);

const handleAddComponentToCourse = () => {
addComponentToCourse()
.then(() => {
window.parent.postMessage('closeComponentPicker', '*');
})
.catch(() => {
showToast(intl.formatMessage(messages.addComponentToCourseError));
reset();
});
window.parent.postMessage({
parentLocator,
usageKey,
type: 'pickerComponentSelected',
category: getBlockType(usageKey),
}, '*');
};

return (
Expand Down
128 changes: 27 additions & 101 deletions src/library-authoring/component-picker/ComponentPicker.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import type MockAdapter from 'axios-mock-adapter';

import { mockContentSearchConfig, mockSearchResult } from '../../search-manager/data/api.mock';
import {
initializeMocks,
fireEvent,
render,
screen,
waitFor,
within,
} from '../../testUtils';
import mockResult from '../__mocks__/library-search.json';
Expand All @@ -17,7 +14,6 @@ import {
mockGetContentLibraryV2List,
mockLibraryBlockMetadata,
} from '../data/api.mocks';
import { getXBlockBaseApiUrl } from '../data/api';

import { ComponentPicker } from './ComponentPicker';

Expand All @@ -27,8 +23,7 @@ mockGetCollectionMetadata.applyMock();
mockGetContentLibraryV2List.applyMock();
mockLibraryBlockMetadata.applyMock();

let axiosMock: MockAdapter;
let mockShowToast: (message: string) => void;
let postMessageSpy: jest.SpyInstance;

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
Expand All @@ -44,10 +39,8 @@ jest.mock('react-router-dom', () => ({

describe('<ComponentPicker />', () => {
beforeEach(() => {
const mocks = initializeMocks();
axiosMock = mocks.axiosMock;
mockShowToast = mocks.mockShowToast;
axiosMock.onPost(getXBlockBaseApiUrl()).reply(200, {});
initializeMocks();
postMessageSpy = jest.spyOn(window.parent, 'postMessage');

mockSearchResult(mockResult);
});
Expand All @@ -67,41 +60,12 @@ describe('<ComponentPicker />', () => {
// Click the add component from the component card
fireEvent.click(screen.queryAllByRole('button', { name: 'Add' })[0]);

await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);
expect(axiosMock.history.post[0].url).toBe(getXBlockBaseApiUrl());
expect(axiosMock.history.post[0].data).toBe(JSON.stringify({
parent_locator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
library_content_key: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
}));
});
});

it('should show toast if error on api call from the component card button', async () => {
axiosMock.onPost(getXBlockBaseApiUrl()).reply(500, {});
render(<ComponentPicker />);

expect(await screen.findByText('Test Library 1')).toBeInTheDocument();
fireEvent.click(screen.getByDisplayValue(/lib:sampletaxonomyorg1:tl1/i));

fireEvent.click(screen.getByText('Next'));

// Wait for the content library to load
await screen.findByText(/Change Library/i);
expect(await screen.findByText('Test Library 1')).toBeInTheDocument();

// Click the add component from the component card
fireEvent.click(screen.queryAllByRole('button', { name: 'Add' })[0]);

await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);
expect(axiosMock.history.post[0].url).toBe(getXBlockBaseApiUrl());
expect(axiosMock.history.post[0].data).toBe(JSON.stringify({
parent_locator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
library_content_key: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
}));
expect(mockShowToast).toHaveBeenCalledWith('Failed to add component to course');
});
expect(postMessageSpy).toHaveBeenCalledWith({
parentLocator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
usageKey: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
type: 'pickerComponentSelected',
category: 'html',
}, '*');
});

it('should pick component using the component sidebar', async () => {
Expand All @@ -124,46 +88,12 @@ describe('<ComponentPicker />', () => {
// Click the add component from the component sidebar
fireEvent.click(within(sidebar).getByRole('button', { name: 'Add to Course' }));

await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);
expect(axiosMock.history.post[0].url).toBe(getXBlockBaseApiUrl());
expect(axiosMock.history.post[0].data).toBe(JSON.stringify({
parent_locator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
library_content_key: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
}));
});
});

it('should show toast if error on api call from the component sidebar button', async () => {
axiosMock.onPost(getXBlockBaseApiUrl()).reply(500, {});
render(<ComponentPicker />);

expect(await screen.findByText('Test Library 1')).toBeInTheDocument();
fireEvent.click(screen.getByDisplayValue(/lib:sampletaxonomyorg1:tl1/i));

fireEvent.click(screen.getByText('Next'));

// Wait for the content library to load
await screen.findByText(/Change Library/i);
expect(await screen.findByText('Test Library 1')).toBeInTheDocument();

// Click on the component card to open the sidebar
fireEvent.click(screen.queryAllByText('Introduction to Testing')[0]);

const sidebar = await screen.findByTestId('library-sidebar');

// Click the add component from the component sidebar
fireEvent.click(within(sidebar).getByRole('button', { name: 'Add to Course' }));

await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);
expect(axiosMock.history.post[0].url).toBe(getXBlockBaseApiUrl());
expect(axiosMock.history.post[0].data).toBe(JSON.stringify({
parent_locator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
library_content_key: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
}));
expect(mockShowToast).toHaveBeenCalledWith('Failed to add component to course');
});
expect(postMessageSpy).toHaveBeenCalledWith({
parentLocator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
usageKey: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
type: 'pickerComponentSelected',
category: 'html',
}, '*');
});

it('should pick component inside a collection using the card', async () => {
Expand Down Expand Up @@ -196,14 +126,12 @@ describe('<ComponentPicker />', () => {
// Click the add component from the component card
fireEvent.click(screen.queryAllByRole('button', { name: 'Add' })[0]);

await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);
expect(axiosMock.history.post[0].url).toBe(getXBlockBaseApiUrl());
expect(axiosMock.history.post[0].data).toBe(JSON.stringify({
parent_locator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
library_content_key: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
}));
});
expect(postMessageSpy).toHaveBeenCalledWith({
parentLocator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
usageKey: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
type: 'pickerComponentSelected',
category: 'html',
}, '*');
});

it('should pick component inside a collection using the sidebar', async () => {
Expand Down Expand Up @@ -241,14 +169,12 @@ describe('<ComponentPicker />', () => {
// Click the add component from the collection sidebar
fireEvent.click(within(collectionSidebar).getByRole('button', { name: 'Add to Course' }));

await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);
expect(axiosMock.history.post[0].url).toBe(getXBlockBaseApiUrl());
expect(axiosMock.history.post[0].data).toBe(JSON.stringify({
parent_locator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
library_content_key: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
}));
});
expect(postMessageSpy).toHaveBeenCalledWith({
parentLocator: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical1',
usageKey: 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd',
type: 'pickerComponentSelected',
category: 'html',
}, '*');
});

it('should return to library selection', async () => {
Expand Down
24 changes: 7 additions & 17 deletions src/library-authoring/components/ComponentCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { updateClipboard } from '../../generic/data/api';
import { ToastContext } from '../../generic/toast-context';
import { type ContentHit } from '../../search-manager';
import { useLibraryContext } from '../common/context';
import { useAddComponentToCourse, useRemoveComponentsFromCollection } from '../data/apiHooks';
import { useRemoveComponentsFromCollection } from '../data/apiHooks';
import BaseComponentCard from './BaseComponentCard';
import { canEditComponent } from './ComponentEditorModal';
import messages from './messages';
Expand Down Expand Up @@ -90,14 +90,11 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
};

const ComponentCard = ({ contentHit }: ComponentCardProps) => {
const intl = useIntl();

const {
openComponentInfoSidebar,
componentPickerMode,
parentLocator,
} = useLibraryContext();
const { showToast } = useContext(ToastContext);

const {
blockType,
Expand All @@ -112,20 +109,13 @@ const ComponentCard = ({ contentHit }: ComponentCardProps) => {
) ?? '';/* eslint-enable */
const displayName = formatted?.displayName ?? '';

const {
mutateAsync: addComponentToCourse,
reset,
} = useAddComponentToCourse(parentLocator, contentHit.usageKey);

const handleAddComponentToCourse = () => {
addComponentToCourse()
.then(() => {
window.parent.postMessage('closeComponentPicker', '*');
})
.catch(() => {
showToast(intl.formatMessage(messages.addComponentToCourseError));
reset();
});
window.parent.postMessage({
parentLocator,
usageKey,
type: 'pickerComponentSelected',
category: blockType,
}, '*');
};

return (
Expand Down
12 changes: 0 additions & 12 deletions src/library-authoring/data/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,15 +483,3 @@ export async function updateComponentCollections(usageKey: string, collectionKey
collection_keys: collectionKeys,
});
}

/**
* Add a component to a course.
*/
// istanbul ignore next
export async function addComponentToCourse(parentLocator: string, componentUsageKey: string) {
const client = getAuthenticatedHttpClient();
await client.post(getXBlockBaseApiUrl(), {
parent_locator: parentLocator,
library_content_key: componentUsageKey,
});
}
16 changes: 0 additions & 16 deletions src/library-authoring/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import {
getXBlockAssets,
updateComponentCollections,
removeComponentsFromCollection,
addComponentToCourse,
} from './api';

export const libraryQueryPredicate = (query: Query, libraryId: string): boolean => {
Expand Down Expand Up @@ -474,18 +473,3 @@ export const useUpdateComponentCollections = (libraryId: string, usageKey: strin
},
});
};

/**
* Use this mutation to add a component to a course
*/
export const useAddComponentToCourse = (parentLocator: string | undefined, componentUsageKey: string) => (
useMutation({
mutationFn: () => {
// istanbul ignore if: this should never happen
if (!parentLocator) {
throw new Error('parentLocator is required');
}
return addComponentToCourse(parentLocator, componentUsageKey);
},
})
);