2019-09-17 20:57:53 +02:00
|
|
|
/*
|
|
|
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
|
|
* or more contributor license agreements. Licensed under the Elastic License;
|
|
|
|
* you may not use this file except in compliance with the Elastic License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import React from 'react';
|
2020-05-29 09:34:55 +02:00
|
|
|
import { render, mount } from 'enzyme';
|
2020-11-03 21:18:44 +01:00
|
|
|
import { DragDrop, ReorderableDragDrop, DropToHandler, DropHandler } from './drag_drop';
|
|
|
|
import { ChildDragDropProvider, ReorderProvider } from './providers';
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
jest.useFakeTimers();
|
|
|
|
|
|
|
|
describe('DragDrop', () => {
|
2020-11-03 21:18:44 +01:00
|
|
|
const value = { id: '1', label: 'hello' };
|
2019-09-17 20:57:53 +02:00
|
|
|
test('renders if nothing is being dragged', () => {
|
|
|
|
const component = render(
|
2020-11-03 21:18:44 +01:00
|
|
|
<DragDrop value={value} draggable label="dragging">
|
2020-10-01 17:58:23 +02:00
|
|
|
<button>Hello!</button>
|
2019-09-17 20:57:53 +02:00
|
|
|
</DragDrop>
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(component).toMatchSnapshot();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('dragover calls preventDefault if droppable is true', () => {
|
|
|
|
const preventDefault = jest.fn();
|
2020-10-01 17:58:23 +02:00
|
|
|
const component = mount(
|
|
|
|
<DragDrop droppable>
|
|
|
|
<button>Hello!</button>
|
|
|
|
</DragDrop>
|
|
|
|
);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
component.find('[data-test-subj="lnsDragDrop"]').simulate('dragover', { preventDefault });
|
|
|
|
|
|
|
|
expect(preventDefault).toBeCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('dragover does not call preventDefault if droppable is false', () => {
|
|
|
|
const preventDefault = jest.fn();
|
2020-10-01 17:58:23 +02:00
|
|
|
const component = mount(
|
|
|
|
<DragDrop>
|
|
|
|
<button>Hello!</button>
|
|
|
|
</DragDrop>
|
|
|
|
);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
component.find('[data-test-subj="lnsDragDrop"]').simulate('dragover', { preventDefault });
|
|
|
|
|
|
|
|
expect(preventDefault).not.toBeCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('dragstart sets dragging in the context', async () => {
|
|
|
|
const setDragging = jest.fn();
|
|
|
|
const dataTransfer = {
|
|
|
|
setData: jest.fn(),
|
|
|
|
getData: jest.fn(),
|
|
|
|
};
|
|
|
|
|
|
|
|
const component = mount(
|
2020-09-04 16:11:41 +02:00
|
|
|
<ChildDragDropProvider dragging={value} setDragging={setDragging}>
|
2020-03-03 17:48:54 +01:00
|
|
|
<DragDrop value={value} draggable={true} label="drag label">
|
2020-10-01 17:58:23 +02:00
|
|
|
<button>Hello!</button>
|
2020-03-03 17:48:54 +01:00
|
|
|
</DragDrop>
|
2019-09-17 20:57:53 +02:00
|
|
|
</ChildDragDropProvider>
|
|
|
|
);
|
|
|
|
|
|
|
|
component.find('[data-test-subj="lnsDragDrop"]').simulate('dragstart', { dataTransfer });
|
|
|
|
|
|
|
|
jest.runAllTimers();
|
|
|
|
|
2020-03-03 17:48:54 +01:00
|
|
|
expect(dataTransfer.setData).toBeCalledWith('text', 'drag label');
|
2019-09-17 20:57:53 +02:00
|
|
|
expect(setDragging).toBeCalledWith(value);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('drop resets all the things', async () => {
|
|
|
|
const preventDefault = jest.fn();
|
|
|
|
const stopPropagation = jest.fn();
|
|
|
|
const setDragging = jest.fn();
|
|
|
|
const onDrop = jest.fn();
|
|
|
|
|
|
|
|
const component = mount(
|
2020-11-03 21:18:44 +01:00
|
|
|
<ChildDragDropProvider dragging={{ id: '2', label: 'hi' }} setDragging={setDragging}>
|
2019-10-16 05:01:43 +02:00
|
|
|
<DragDrop onDrop={onDrop} droppable={true} value={value}>
|
2020-10-01 17:58:23 +02:00
|
|
|
<button>Hello!</button>
|
2019-09-17 20:57:53 +02:00
|
|
|
</DragDrop>
|
|
|
|
</ChildDragDropProvider>
|
|
|
|
);
|
|
|
|
|
|
|
|
component
|
|
|
|
.find('[data-test-subj="lnsDragDrop"]')
|
|
|
|
.simulate('drop', { preventDefault, stopPropagation });
|
|
|
|
|
|
|
|
expect(preventDefault).toBeCalled();
|
|
|
|
expect(stopPropagation).toBeCalled();
|
|
|
|
expect(setDragging).toBeCalledWith(undefined);
|
2020-11-03 21:18:44 +01:00
|
|
|
expect(onDrop).toBeCalledWith({ id: '2', label: 'hi' });
|
2019-09-17 20:57:53 +02:00
|
|
|
});
|
|
|
|
|
2019-10-16 05:01:43 +02:00
|
|
|
test('drop function is not called on droppable=false', async () => {
|
|
|
|
const preventDefault = jest.fn();
|
|
|
|
const stopPropagation = jest.fn();
|
|
|
|
const setDragging = jest.fn();
|
|
|
|
const onDrop = jest.fn();
|
|
|
|
|
|
|
|
const component = mount(
|
2020-11-03 21:18:44 +01:00
|
|
|
<ChildDragDropProvider dragging={{ id: 'hi' }} setDragging={setDragging}>
|
|
|
|
<DragDrop onDrop={onDrop} droppable={false} value={value}>
|
2020-10-01 17:58:23 +02:00
|
|
|
<button>Hello!</button>
|
2019-10-16 05:01:43 +02:00
|
|
|
</DragDrop>
|
|
|
|
</ChildDragDropProvider>
|
|
|
|
);
|
|
|
|
|
|
|
|
component
|
|
|
|
.find('[data-test-subj="lnsDragDrop"]')
|
|
|
|
.simulate('drop', { preventDefault, stopPropagation });
|
|
|
|
|
|
|
|
expect(preventDefault).toBeCalled();
|
|
|
|
expect(stopPropagation).toBeCalled();
|
|
|
|
expect(setDragging).toBeCalledWith(undefined);
|
|
|
|
expect(onDrop).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
2019-09-17 20:57:53 +02:00
|
|
|
test('droppable is reflected in the className', () => {
|
|
|
|
const component = render(
|
|
|
|
<DragDrop
|
|
|
|
onDrop={(x: unknown) => {
|
|
|
|
throw x;
|
|
|
|
}}
|
|
|
|
droppable
|
|
|
|
>
|
2020-10-01 17:58:23 +02:00
|
|
|
<button>Hello!</button>
|
2019-09-17 20:57:53 +02:00
|
|
|
</DragDrop>
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(component).toMatchSnapshot();
|
|
|
|
});
|
2020-09-04 16:11:41 +02:00
|
|
|
|
|
|
|
test('items that have droppable=false get special styling when another item is dragged', () => {
|
|
|
|
const component = mount(
|
2020-11-03 21:18:44 +01:00
|
|
|
<ChildDragDropProvider dragging={value} setDragging={() => {}}>
|
|
|
|
<DragDrop value={value} draggable={true} label="a">
|
2020-10-01 17:58:23 +02:00
|
|
|
<button>Hello!</button>
|
2020-09-04 16:11:41 +02:00
|
|
|
</DragDrop>
|
|
|
|
<DragDrop onDrop={(x: unknown) => {}} droppable={false}>
|
2020-10-01 17:58:23 +02:00
|
|
|
<button>Hello!</button>
|
2020-09-04 16:11:41 +02:00
|
|
|
</DragDrop>
|
|
|
|
</ChildDragDropProvider>
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(component.find('[data-test-subj="lnsDragDrop"]').at(1)).toMatchSnapshot();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('additional styles are reflected in the className until drop', () => {
|
2020-11-03 21:18:44 +01:00
|
|
|
let dragging: { id: '1' } | undefined;
|
2020-09-04 16:11:41 +02:00
|
|
|
const getAdditionalClasses = jest.fn().mockReturnValue('additional');
|
|
|
|
const component = mount(
|
|
|
|
<ChildDragDropProvider
|
|
|
|
dragging={dragging}
|
|
|
|
setDragging={() => {
|
2020-11-03 21:18:44 +01:00
|
|
|
dragging = { id: '1' };
|
2020-09-04 16:11:41 +02:00
|
|
|
}}
|
|
|
|
>
|
2020-11-03 21:18:44 +01:00
|
|
|
<DragDrop value={{ label: 'ignored', id: '3' }} draggable={true} label="a">
|
2020-10-01 17:58:23 +02:00
|
|
|
<button>Hello!</button>
|
2020-09-04 16:11:41 +02:00
|
|
|
</DragDrop>
|
|
|
|
<DragDrop
|
|
|
|
onDrop={(x: unknown) => {}}
|
|
|
|
droppable
|
|
|
|
getAdditionalClassesOnEnter={getAdditionalClasses}
|
|
|
|
>
|
2020-10-01 17:58:23 +02:00
|
|
|
<button>Hello!</button>
|
2020-09-04 16:11:41 +02:00
|
|
|
</DragDrop>
|
|
|
|
</ChildDragDropProvider>
|
|
|
|
);
|
|
|
|
|
|
|
|
const dataTransfer = {
|
|
|
|
setData: jest.fn(),
|
|
|
|
getData: jest.fn(),
|
|
|
|
};
|
|
|
|
component
|
|
|
|
.find('[data-test-subj="lnsDragDrop"]')
|
|
|
|
.first()
|
|
|
|
.simulate('dragstart', { dataTransfer });
|
|
|
|
jest.runAllTimers();
|
|
|
|
|
|
|
|
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragover');
|
|
|
|
expect(component.find('.additional')).toHaveLength(1);
|
|
|
|
|
|
|
|
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragleave');
|
|
|
|
expect(component.find('.additional')).toHaveLength(0);
|
|
|
|
|
|
|
|
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragover');
|
|
|
|
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('drop');
|
|
|
|
expect(component.find('.additional')).toHaveLength(0);
|
|
|
|
});
|
2020-11-03 21:18:44 +01:00
|
|
|
|
|
|
|
describe('reordering', () => {
|
|
|
|
const mountComponent = (
|
|
|
|
dragging: { id: '1' } | undefined,
|
|
|
|
onDrop: DropHandler = jest.fn(),
|
|
|
|
dropTo: DropToHandler = jest.fn()
|
|
|
|
) =>
|
|
|
|
mount(
|
|
|
|
<ChildDragDropProvider
|
|
|
|
dragging={{ id: '1' }}
|
|
|
|
setDragging={() => {
|
|
|
|
dragging = { id: '1' };
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<ReorderProvider id="groupId">
|
|
|
|
<DragDrop
|
|
|
|
label="1"
|
|
|
|
draggable
|
|
|
|
droppable
|
|
|
|
dragType="reorder"
|
|
|
|
dropType="reorder"
|
|
|
|
itemsInGroup={['1', '2', '3']}
|
|
|
|
value={{ id: '1' }}
|
|
|
|
onDrop={onDrop}
|
|
|
|
dropTo={dropTo}
|
|
|
|
>
|
|
|
|
<span>1</span>
|
|
|
|
</DragDrop>
|
|
|
|
<DragDrop
|
|
|
|
label="2"
|
|
|
|
draggable
|
|
|
|
droppable
|
|
|
|
dragType="reorder"
|
|
|
|
dropType="reorder"
|
|
|
|
itemsInGroup={['1', '2', '3']}
|
|
|
|
value={{
|
|
|
|
id: '2',
|
|
|
|
}}
|
|
|
|
onDrop={onDrop}
|
|
|
|
dropTo={dropTo}
|
|
|
|
>
|
|
|
|
<span>2</span>
|
|
|
|
</DragDrop>
|
|
|
|
<DragDrop
|
|
|
|
label="3"
|
|
|
|
draggable
|
|
|
|
droppable
|
|
|
|
dragType="reorder"
|
|
|
|
dropType="reorder"
|
|
|
|
itemsInGroup={['1', '2', '3']}
|
|
|
|
value={{
|
|
|
|
id: '3',
|
|
|
|
}}
|
|
|
|
onDrop={onDrop}
|
|
|
|
dropTo={dropTo}
|
|
|
|
>
|
|
|
|
<span>3</span>
|
|
|
|
</DragDrop>
|
|
|
|
</ReorderProvider>
|
|
|
|
</ChildDragDropProvider>
|
|
|
|
);
|
|
|
|
test(`ReorderableDragDrop component doesn't appear for groups of 1 or less`, () => {
|
|
|
|
let dragging;
|
|
|
|
const component = mount(
|
|
|
|
<ChildDragDropProvider
|
|
|
|
dragging={dragging}
|
|
|
|
setDragging={() => {
|
|
|
|
dragging = { id: '1' };
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<ReorderProvider id="groupId">
|
|
|
|
<DragDrop
|
|
|
|
label="1"
|
|
|
|
draggable
|
|
|
|
droppable
|
|
|
|
dragType="reorder"
|
|
|
|
dropType="reorder"
|
|
|
|
itemsInGroup={['1']}
|
|
|
|
value={{ id: '1' }}
|
|
|
|
onDrop={jest.fn()}
|
|
|
|
dropTo={jest.fn()}
|
|
|
|
>
|
|
|
|
<div />
|
|
|
|
</DragDrop>
|
|
|
|
</ReorderProvider>
|
|
|
|
</ChildDragDropProvider>
|
|
|
|
);
|
|
|
|
expect(component.find(ReorderableDragDrop)).toHaveLength(0);
|
|
|
|
});
|
|
|
|
test(`Reorderable component renders properly`, () => {
|
|
|
|
const component = mountComponent(undefined, jest.fn());
|
|
|
|
expect(component.find(ReorderableDragDrop)).toHaveLength(3);
|
|
|
|
});
|
|
|
|
test(`Elements between dragged and drop get extra class to show the reorder effect when dragging`, () => {
|
|
|
|
const component = mountComponent({ id: '1' }, jest.fn());
|
|
|
|
const dataTransfer = {
|
|
|
|
setData: jest.fn(),
|
|
|
|
getData: jest.fn(),
|
|
|
|
};
|
|
|
|
component
|
|
|
|
.find(ReorderableDragDrop)
|
|
|
|
.first()
|
|
|
|
.find('[data-test-subj="lnsDragDrop"]')
|
|
|
|
.simulate('dragstart', { dataTransfer });
|
|
|
|
jest.runAllTimers();
|
|
|
|
|
|
|
|
component.find('[data-test-subj="lnsDragDrop-reorderableDrop"]').at(2).simulate('dragover');
|
2020-11-11 12:36:04 +01:00
|
|
|
expect(
|
|
|
|
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(0).prop('style')
|
|
|
|
).toEqual({});
|
|
|
|
expect(
|
|
|
|
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(1).prop('style')
|
|
|
|
).toEqual({
|
|
|
|
transform: 'translateY(-40px)',
|
2020-11-03 21:18:44 +01:00
|
|
|
});
|
2020-11-11 12:36:04 +01:00
|
|
|
expect(
|
|
|
|
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(2).prop('style')
|
|
|
|
).toEqual({
|
|
|
|
transform: 'translateY(-40px)',
|
2020-11-03 21:18:44 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
component.find('[data-test-subj="lnsDragDrop-reorderableDrop"]').at(2).simulate('dragleave');
|
2020-11-11 12:36:04 +01:00
|
|
|
expect(
|
|
|
|
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(1).prop('style')
|
|
|
|
).toEqual({});
|
|
|
|
expect(
|
|
|
|
component.find('[data-test-subj="lnsDragDrop-reorderableDrag"]').at(2).prop('style')
|
|
|
|
).toEqual({});
|
2020-11-03 21:18:44 +01:00
|
|
|
});
|
|
|
|
test(`Dropping an item runs onDrop function`, () => {
|
|
|
|
const preventDefault = jest.fn();
|
|
|
|
const stopPropagation = jest.fn();
|
|
|
|
const onDrop = jest.fn();
|
|
|
|
|
|
|
|
const component = mountComponent({ id: '1' }, onDrop);
|
|
|
|
|
|
|
|
component
|
|
|
|
.find('[data-test-subj="lnsDragDrop-reorderableDrop"]')
|
|
|
|
.at(1)
|
|
|
|
.simulate('drop', { preventDefault, stopPropagation });
|
|
|
|
expect(preventDefault).toBeCalled();
|
|
|
|
expect(stopPropagation).toBeCalled();
|
|
|
|
expect(onDrop).toBeCalledWith({ id: '1' });
|
|
|
|
});
|
|
|
|
test(`Keyboard navigation: user can reorder an element`, () => {
|
|
|
|
const onDrop = jest.fn();
|
|
|
|
const dropTo = jest.fn();
|
|
|
|
const component = mountComponent({ id: '1' }, onDrop, dropTo);
|
|
|
|
const keyboardHandler = component
|
|
|
|
.find(ReorderableDragDrop)
|
|
|
|
.at(1)
|
|
|
|
.find('[data-test-subj="lnsDragDrop-keyboardHandler"]');
|
|
|
|
|
|
|
|
keyboardHandler.simulate('keydown', { key: 'Space' });
|
|
|
|
keyboardHandler.simulate('keydown', { key: 'ArrowDown' });
|
|
|
|
expect(dropTo).toBeCalledWith('3');
|
|
|
|
|
|
|
|
keyboardHandler.simulate('keydown', { key: 'ArrowUp' });
|
|
|
|
expect(dropTo).toBeCalledWith('1');
|
|
|
|
});
|
|
|
|
test(`Keyboard Navigation: User cannot move an element outside of the group`, () => {
|
|
|
|
const onDrop = jest.fn();
|
|
|
|
const dropTo = jest.fn();
|
|
|
|
const component = mountComponent({ id: '1' }, onDrop, dropTo);
|
|
|
|
const keyboardHandler = component
|
|
|
|
.find(ReorderableDragDrop)
|
|
|
|
.first()
|
|
|
|
.find('[data-test-subj="lnsDragDrop-keyboardHandler"]');
|
|
|
|
|
|
|
|
keyboardHandler.simulate('keydown', { key: 'Space' });
|
|
|
|
keyboardHandler.simulate('keydown', { key: 'ArrowUp' });
|
|
|
|
expect(dropTo).not.toHaveBeenCalled();
|
|
|
|
|
|
|
|
keyboardHandler.simulate('keydown', { key: 'ArrowDown' });
|
|
|
|
expect(dropTo).toBeCalledWith('2');
|
|
|
|
});
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
});
|