Skip to content

Commit af4384b

Browse files
committed
test: drag and drop unit components
1 parent b90f6f7 commit af4384b

5 files changed

Lines changed: 76 additions & 10 deletions

File tree

src/generic/DraggableList/DraggableList.jsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from 'react';
1+
import React, { useCallback } from 'react';
22
import PropTypes from 'prop-types';
33
import { createPortal } from 'react-dom';
44

@@ -25,17 +25,17 @@ const DraggableList = ({
2525
updateOrder,
2626
children,
2727
renderOverlay,
28+
activeId,
29+
setActiveId,
2830
}) => {
2931
const sensors = useSensors(
3032
useSensor(PointerSensor),
3133
useSensor(KeyboardSensor, {
3234
coordinateGetter: sortableKeyboardCoordinates,
3335
}),
3436
);
35-
const [activeId, setActiveId] = useState(null);
3637

37-
const handleDragEnd = (event) => {
38-
setActiveId(null);
38+
const handleDragEnd = useCallback((event) => {
3939
const { active, over } = event;
4040
if (active.id !== over.id) {
4141
let updatedArray;
@@ -49,11 +49,12 @@ const DraggableList = ({
4949
});
5050
updateOrder()(updatedArray);
5151
}
52-
};
52+
setActiveId?.(null);
53+
}, [updateOrder, setActiveId]);
5354

54-
const handleDragStart = (event) => {
55-
setActiveId(event.active.id);
56-
};
55+
const handleDragStart = useCallback((event) => {
56+
setActiveId?.(event.active.id);
57+
}, [setActiveId]);
5758

5859
return (
5960
<DndContext
@@ -81,6 +82,8 @@ const DraggableList = ({
8182

8283
DraggableList.defaultProps = {
8384
renderOverlay: undefined,
85+
activeId: null,
86+
setActiveId: () => {},
8487
};
8588

8689
DraggableList.propTypes = {
@@ -91,6 +94,8 @@ DraggableList.propTypes = {
9194
updateOrder: PropTypes.func.isRequired,
9295
children: PropTypes.node.isRequired,
9396
renderOverlay: PropTypes.func,
97+
activeId: PropTypes.string,
98+
onDragEnd: PropTypes.func,
9499
};
95100

96101
export default DraggableList;

src/library-authoring/data/api.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,15 @@ describe('library data API', () => {
126126
await api.addComponentsToContainer(containerId, [componentId]);
127127
expect(axiosMock.history.post[0].url).toEqual(url);
128128
});
129+
130+
it('should update container children', async () => {
131+
const { axiosMock } = initializeMocks();
132+
const containerId = 'lct:org:lib1';
133+
const url = api.getLibraryContainerChildrenApiUrl(containerId);
134+
135+
axiosMock.onPatch(url).reply(200);
136+
137+
await api.updateLibraryContainerChildren(containerId, ['test']);
138+
expect(axiosMock.history.patch[0].url).toEqual(url);
139+
});
129140
});

src/library-authoring/data/apiHooks.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
useRestoreContainer,
3030
useContainerChildren,
3131
useAddComponentsToContainer,
32+
useUpdateContainerChildren,
3233
} from './apiHooks';
3334

3435
let axiosMock;
@@ -273,4 +274,16 @@ describe('library api hooks', () => {
273274

274275
expect(axiosMock.history.post[0].url).toEqual(url);
275276
});
277+
278+
it('should update container children', async () => {
279+
const containerId = 'lct:org:lib1';
280+
const url = getLibraryContainerChildrenApiUrl(containerId);
281+
282+
axiosMock.onPatch(url).reply(200);
283+
const { result } = renderHook(() => useUpdateContainerChildren(containerId), { wrapper });
284+
await result.current.mutateAsync([]);
285+
await waitFor(() => {
286+
expect(axiosMock.history.patch[0].url).toEqual(url);
287+
});
288+
});
276289
});

src/library-authoring/units/LibraryUnitBlocks.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => {
126126

127127
/** Render drag overlay component */
128128
const renderOverlay = (activeId: string | null) => {
129-
setHidePreviewFor(activeId);
130129
if (!activeId) {
131130
return null;
132131
}
@@ -186,6 +185,8 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => {
186185
setState={setOrderedBlocks}
187186
updateOrder={handleReorder}
188187
renderOverlay={renderOverlay}
188+
activeId={hidePreviewFor}
189+
setActiveId={setHidePreviewFor}
189190
>
190191
{renderedBlocks}
191192
</DraggableList>

src/library-authoring/units/LibraryUnitPage.test.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import userEvent from '@testing-library/user-event';
22
import {
3+
fireEvent,
34
initializeMocks,
45
render,
56
screen,
@@ -16,9 +17,16 @@ import {
1617
import { mockContentSearchConfig, mockGetBlockTypes } from '../../search-manager/data/api.mock';
1718
import { mockClipboardEmpty } from '../../generic/data/api.mock';
1819
import LibraryLayout from '../LibraryLayout';
20+
import { getLibraryContainerChildrenApiUrl } from '../data/api';
21+
import { RequestStatus } from '../../data/constants';
22+
import { closestCorners } from '@dnd-kit/core';
23+
import { ToastActionData } from '../../generic/toast-context';
24+
import { act } from 'react';
1925

2026
const path = '/library/:libraryId/*';
2127
const libraryTitle = mockContentLibrary.libraryData.title;
28+
let axiosMock: import("axios-mock-adapter/types");
29+
let mockShowToast: (message: string, action?: ToastActionData | undefined) => void;
2230

2331
mockClipboardEmpty.applyMock();
2432
mockGetContainerMetadata.applyMock();
@@ -29,9 +37,22 @@ mockContentLibrary.applyMock();
2937
mockXBlockFields.applyMock();
3038
mockLibraryBlockMetadata.applyMock();
3139

40+
jest.mock('@dnd-kit/core', () => ({
41+
...jest.requireActual('@dnd-kit/core'),
42+
// Since jsdom (used by jest) does not support getBoundingClientRect function
43+
// which is required for drag-n-drop calculations, we mock closestCorners fn
44+
// from dnd-kit to return collided elements as per the test. This allows us to
45+
// test all drag-n-drop handlers.
46+
closestCorners: jest.fn(),
47+
}));
48+
// eslint-disable-next-line no-promise-executor-return
49+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
50+
3251
describe('<LibraryUnitPage />', () => {
3352
beforeEach(() => {
34-
initializeMocks();
53+
const mocks = initializeMocks();
54+
axiosMock = mocks.axiosMock;
55+
mockShowToast = mocks.mockShowToast;
3556
});
3657

3758
const renderLibraryUnitPage = (unitId?: string, libraryId?: string) => {
@@ -111,4 +132,19 @@ describe('<LibraryUnitPage />', () => {
111132
userEvent.click(closeButton);
112133
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
113134
});
135+
136+
it('should call update order api on dragging component', async () => {
137+
renderLibraryUnitPage();
138+
const firstDragHandle = (await screen.findAllByRole('button', { name: 'Drag to reorder' }))[0];
139+
axiosMock
140+
.onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId))
141+
.reply(200);
142+
closestCorners.mockReturnValue([{ id: "lb:org1:Demo_course:html:text-1" }]);
143+
await act(async () => {
144+
fireEvent.keyDown(firstDragHandle, { code: 'Space' });
145+
await sleep(1);
146+
fireEvent.keyUp(firstDragHandle, { code: 'Space' });
147+
})
148+
await waitFor(() => expect(mockShowToast).toHaveBeenLastCalledWith('test'));
149+
});
114150
});

0 commit comments

Comments
 (0)