diff --git a/packages/@react-aria/dnd/src/DragManager.ts b/packages/@react-aria/dnd/src/DragManager.ts
index 2095044e315..392595ead37 100644
--- a/packages/@react-aria/dnd/src/DragManager.ts
+++ b/packages/@react-aria/dnd/src/DragManager.ts
@@ -13,6 +13,7 @@
import {announce} from '@react-aria/live-announcer';
import {ariaHideOutside} from '@react-aria/overlays';
import {DragEndEvent, DragItem, DropActivateEvent, DropEnterEvent, DropEvent, DropExitEvent, DropItem, DropOperation, DropTarget as DroppableCollectionTarget, FocusableElement} from '@react-types/shared';
+import {flushSync} from 'react-dom';
import {getDragModality, getTypes} from './utils';
import {getInteractionModality} from '@react-aria/interactions';
import type {LocalizedStringFormatter} from '@internationalized/string';
@@ -494,7 +495,16 @@ class DragSession {
// Blur and re-focus the drop target so that the focus ring appears.
if (this.currentDropTarget) {
- this.currentDropTarget.element.blur();
+ // Since we cancel all focus events in drag sessions, refire blur to make sure state gets updated so drag target doesn't think it's still focused
+ // i.e. When you from one list to another during a drag session, we need the blur to fire on the first list after the drag.
+ if (!this.dragTarget.element.contains(this.currentDropTarget.element)) {
+ this.dragTarget.element.dispatchEvent(new FocusEvent('blur'));
+ this.dragTarget.element.dispatchEvent(new FocusEvent('focusout', {bubbles: true}));
+ }
+ // Re-focus the focusedKey upon reorder. This requires a React rerender between blurring and focusing.
+ flushSync(() => {
+ this.currentDropTarget.element.blur();
+ });
this.currentDropTarget.element.focus();
}
diff --git a/packages/@react-spectrum/list/stories/ListView.stories.tsx b/packages/@react-spectrum/list/stories/ListView.stories.tsx
index 9308ec9a170..c0dc7c8b174 100644
--- a/packages/@react-spectrum/list/stories/ListView.stories.tsx
+++ b/packages/@react-spectrum/list/stories/ListView.stories.tsx
@@ -852,7 +852,10 @@ export function DragIntoItemExample(props) {
}
export function DragBetweenListsExample(props) {
- let onDropAction = action('onDrop');
+ let {onDragStart, onDragEnd, onDrop} = props;
+ onDrop = chain(action('onDrop'), onDrop);
+ onDragStart = chain(action('dragStart'), onDragStart);
+ onDragEnd = chain(action('dragEnd'), onDragEnd);
let list1 = useListData({
initialItems: props.items1 || itemList1
@@ -898,8 +901,8 @@ export function DragBetweenListsExample(props) {
getAllowedDropOperationsAction();
return ['move', 'cancel'];
},
- onDragStart: action('dragStart'),
- onDragEnd: action('dragEnd')
+ onDragStart,
+ onDragEnd
});
// Use a random drag type so the items can only be reordered within the two lists and not dragged elsewhere.
@@ -923,7 +926,7 @@ export function DragBetweenListsExample(props) {
}
}
}
- onDropAction(e);
+ onDrop(e);
onMove(keys, e.target);
}
},
diff --git a/packages/@react-spectrum/list/test/ListViewDnd.test.js b/packages/@react-spectrum/list/test/ListViewDnd.test.js
index 788eef78a98..0a114debb08 100644
--- a/packages/@react-spectrum/list/test/ListViewDnd.test.js
+++ b/packages/@react-spectrum/list/test/ListViewDnd.test.js
@@ -14,13 +14,14 @@ jest.mock('@react-aria/live-announcer');
import {act, fireEvent, installPointerEvent, render as renderComponent, waitFor, within} from '@react-spectrum/test-utils';
import {CUSTOM_DRAG_TYPE} from '@react-aria/dnd/src/constants';
import {DataTransfer, DataTransferItem, DragEvent} from '@react-aria/dnd/test/mocks';
-import {DragBetweenListsRootOnlyExample, DragExample, DragIntoItemExample, ReorderExample} from '../stories/ListView.stories';
+import {DragBetweenListsExample, DragBetweenListsRootOnlyExample, DragExample, DragIntoItemExample, ReorderExample} from '../stories/ListView.stories';
import {Droppable} from '@react-aria/dnd/test/examples';
import {Provider} from '@react-spectrum/provider';
import React from 'react';
import {theme} from '@react-spectrum/theme-default';
import userEvent from '@testing-library/user-event';
+let isReact18 = parseInt(React.version, 10) >= 18;
describe('ListView', function () {
let offsetWidth, offsetHeight, scrollHeight;
@@ -84,9 +85,16 @@ describe('ListView', function () {
function Reorderable(props) {
return (
-
+
);
}
+
+ function DragBetweenLists(props) {
+ return (
+
+ );
+ }
+
beforeEach(() => {
jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(() => ({
left: 0,
@@ -335,6 +343,219 @@ describe('ListView', function () {
expect(event.defaultPrevented).toBe(true);
expect(dataTransfer.items._items).toHaveLength(0);
});
+
+ it('should allow moving one item within a list', async function () {
+ let {getAllByRole, getByRole} = render();
+
+ let grid = getByRole('grid');
+ let rows = getAllByRole('row');
+ let cell = within(rows[1]).getByRole('gridcell');
+ expect(within(rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(rows[1]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(rows[2]).getByRole('gridcell')).toHaveTextContent('Item Three');
+
+ let dataTransfer = new DataTransfer();
+ fireEvent.pointerDown(cell, {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 0, clientY: 0});
+ fireEvent(cell, new DragEvent('dragstart', {dataTransfer, clientX: 0, clientY: 0}));
+ expect(onDragStart).toHaveBeenCalledTimes(1);
+
+ fireEvent.pointerMove(cell, {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 1, clientY: 110});
+ fireEvent(cell, new DragEvent('drag', {dataTransfer, clientX: 1, clientY: 110}));
+ fireEvent(grid, new DragEvent('dragover', {dataTransfer, clientX: 1, clientY: 110}));
+ fireEvent.pointerUp(cell, {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 1, clientY: 110});
+
+ fireEvent(grid, new DragEvent('drop', {dataTransfer, clientX: 1, clientY: 110}));
+ act(() => jest.runAllTimers());
+ await act(async () => Promise.resolve());
+ expect(onDrop).toHaveBeenCalledTimes(1);
+
+ fireEvent(cell, new DragEvent('dragend', {dataTransfer, clientX: 1, clientY: 110}));
+ expect(onDragEnd).toHaveBeenCalledTimes(1);
+
+ act(() => jest.runAllTimers());
+
+ rows = getAllByRole('row');
+ expect(within(rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(rows[1]).getByRole('gridcell')).toHaveTextContent('Item Three');
+ expect(within(rows[2]).getByRole('gridcell')).toHaveTextContent('Item Two');
+
+ expect(document.activeElement).toBe(rows[2]);
+ });
+
+ it('should allow moving multiple items within a list', async function () {
+ let {getAllByRole, getByRole} = render();
+
+ let grid = getByRole('grid');
+ let rows = getAllByRole('row');
+ let cell = within(rows[1]).getByRole('gridcell');
+ expect(within(rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(rows[1]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(rows[2]).getByRole('gridcell')).toHaveTextContent('Item Three');
+ expect(within(rows[3]).getByRole('gridcell')).toHaveTextContent('Item Four');
+
+ act(() => userEvent.click(within(rows[1]).getByRole('checkbox')));
+ act(() => userEvent.click(within(rows[2]).getByRole('checkbox')));
+
+ let dataTransfer = new DataTransfer();
+ fireEvent.pointerDown(cell, {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 0, clientY: 0});
+ fireEvent(cell, new DragEvent('dragstart', {dataTransfer, clientX: 0, clientY: 0}));
+ expect(onDragStart).toHaveBeenCalledTimes(1);
+
+ fireEvent.pointerMove(cell, {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 1, clientY: 110});
+ fireEvent(cell, new DragEvent('drag', {dataTransfer, clientX: 1, clientY: 110}));
+ fireEvent(grid, new DragEvent('dragover', {dataTransfer, clientX: 1, clientY: 110}));
+ fireEvent.pointerUp(cell, {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 1, clientY: 110});
+
+ fireEvent(grid, new DragEvent('drop', {dataTransfer, clientX: 1, clientY: 110}));
+ act(() => jest.runAllTimers());
+ await act(async () => Promise.resolve());
+ expect(onDrop).toHaveBeenCalledTimes(1);
+
+ fireEvent(cell, new DragEvent('dragend', {dataTransfer, clientX: 1, clientY: 110}));
+ expect(onDragEnd).toHaveBeenCalledTimes(1);
+
+ act(() => jest.runAllTimers());
+
+ rows = getAllByRole('row');
+ expect(within(rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(rows[1]).getByRole('gridcell')).toHaveTextContent('Item Four');
+ expect(within(rows[2]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(rows[3]).getByRole('gridcell')).toHaveTextContent('Item Three');
+
+ expect(document.activeElement).toBe(rows[2]);
+ });
+
+ it('should allow moving one item into another list', async function () {
+ let {getAllByRole} = render();
+
+ let grid1 = getAllByRole('grid')[0];
+ let grid2 = getAllByRole('grid')[1];
+ let list1rows = within(grid1).getAllByRole('row');
+ let list2rows = within(grid2).getAllByRole('row');
+
+ expect(within(list1rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(list1rows[1]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(list1rows[2]).getByRole('gridcell')).toHaveTextContent('Item Three');
+
+ expect(within(list2rows[0]).getByRole('gridcell')).toHaveTextContent('Item Seven');
+ expect(within(list2rows[1]).getByRole('gridcell')).toHaveTextContent('Item Eight');
+ expect(within(list2rows[2]).getByRole('gridcell')).toHaveTextContent('Item Nine');
+
+ let dataTransfer = new DataTransfer();
+ fireEvent.pointerDown(list1rows[0], {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 0, clientY: 0});
+ fireEvent(list1rows[0], new DragEvent('dragstart', {dataTransfer, clientX: 0, clientY: 0}));
+ act(() => {jest.runAllTimers();});
+ fireEvent(list1rows[0], new DragEvent('drag', {dataTransfer, clientX: 0, clientY: 0}));
+ fireEvent(list1rows[0], new DragEvent('dragenter', {dataTransfer, clientX: 0, clientY: 0}));
+ fireEvent(grid2, new DragEvent('dragover', {dataTransfer, clientX: 0, clientY: 0}));
+ expect(onDragStart).toHaveBeenCalledTimes(1);
+
+ fireEvent.pointerMove(list1rows[0], {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 200, clientY: -1});
+ fireEvent(list1rows[0], new DragEvent('drag', {dataTransfer, clientX: 200, clientY: -1}));
+ fireEvent(list1rows[0], new DragEvent('dragenter', {dataTransfer, clientX: 200, clientY: -1}));
+ fireEvent(grid1, new DragEvent('dragleave', {dataTransfer, clientX: 200, clientY: -1}));
+ fireEvent(grid2, new DragEvent('dragover', {dataTransfer, clientX: 200, clientY: -1}));
+ fireEvent.pointerUp(list1rows[0], {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 200, clientY: -1});
+
+ fireEvent(grid2, new DragEvent('drop', {dataTransfer, clientX: 200, clientY: -1}));
+ // purposefully run these two together inside one act so that the events line up as they do in the dom
+ // if they are in separate ones, then we create a render cycle in between
+ // if they are two separate acts
+ act(() => {
+ jest.advanceTimersToNextTimer();
+ fireEvent(list1rows[0], new DragEvent('dragend', {dataTransfer, clientX: 200, clientY: -1}));
+ });
+ // resolve async drop
+ await act(async () => Promise.resolve());
+ // resolve focus movement
+ act(() => {jest.runAllTimers();});
+
+ expect(onDragEnd).toHaveBeenCalledTimes(1);
+ expect(onDrop).toHaveBeenCalledTimes(1);
+
+ grid1 = getAllByRole('grid')[0];
+ grid2 = getAllByRole('grid')[1];
+ list1rows = within(grid1).getAllByRole('row');
+ list2rows = within(grid2).getAllByRole('row');
+
+ expect(within(list1rows[0]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(list1rows[1]).getByRole('gridcell')).toHaveTextContent('Item Three');
+ expect(within(list1rows[2]).getByRole('gridcell')).toHaveTextContent('Item Four');
+
+ expect(within(list2rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(list2rows[1]).getByRole('gridcell')).toHaveTextContent('Item Seven');
+ expect(within(list2rows[2]).getByRole('gridcell')).toHaveTextContent('Item Eight');
+
+ expect(document.activeElement).toBe(list2rows[0]);
+ });
+
+ it('should allow moving multiple items into another list', async function () {
+ let {getAllByRole} = render();
+
+ let grid1 = getAllByRole('grid')[0];
+ let grid2 = getAllByRole('grid')[1];
+ let list1rows = within(grid1).getAllByRole('row');
+ let list2rows = within(grid2).getAllByRole('row');
+
+ expect(within(list1rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(list1rows[1]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(list1rows[2]).getByRole('gridcell')).toHaveTextContent('Item Three');
+
+ expect(within(list2rows[0]).getByRole('gridcell')).toHaveTextContent('Item Seven');
+ expect(within(list2rows[1]).getByRole('gridcell')).toHaveTextContent('Item Eight');
+ expect(within(list2rows[2]).getByRole('gridcell')).toHaveTextContent('Item Nine');
+
+ act(() => userEvent.click(within(list1rows[0]).getByRole('checkbox')));
+ act(() => userEvent.click(within(list1rows[2]).getByRole('checkbox')));
+
+ let dataTransfer = new DataTransfer();
+ fireEvent.pointerDown(list1rows[0], {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 0, clientY: 0});
+ fireEvent(list1rows[0], new DragEvent('dragstart', {dataTransfer, clientX: 0, clientY: 0}));
+ act(() => {jest.runAllTimers();});
+ fireEvent(list1rows[0], new DragEvent('drag', {dataTransfer, clientX: 0, clientY: 0}));
+ fireEvent(list1rows[0], new DragEvent('dragenter', {dataTransfer, clientX: 0, clientY: 0}));
+ fireEvent(grid2, new DragEvent('dragover', {dataTransfer, clientX: 0, clientY: 0}));
+ expect(onDragStart).toHaveBeenCalledTimes(1);
+
+ fireEvent.pointerMove(list1rows[0], {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 200, clientY: -1});
+ fireEvent(list1rows[0], new DragEvent('drag', {dataTransfer, clientX: 200, clientY: -1}));
+ fireEvent(list1rows[0], new DragEvent('dragenter', {dataTransfer, clientX: 200, clientY: -1}));
+ fireEvent(grid1, new DragEvent('dragleave', {dataTransfer, clientX: 200, clientY: -1}));
+ fireEvent(grid2, new DragEvent('dragover', {dataTransfer, clientX: 200, clientY: -1}));
+ fireEvent.pointerUp(list1rows[0], {pointerType: 'mouse', button: 0, pointerId: 1, clientX: 200, clientY: -1});
+
+ fireEvent(grid2, new DragEvent('drop', {dataTransfer, clientX: 200, clientY: -1}));
+ // purposefully run these two together inside one act so that the events line up as they do in the dom
+ // if they are in separate ones, then we create an render cycle in between
+ // if they are two separate acts
+ act(() => {
+ jest.advanceTimersToNextTimer();
+ fireEvent(list1rows[0], new DragEvent('dragend', {dataTransfer, clientX: 200, clientY: -1}));
+ });
+ if (!isReact18) {
+ // not sure why but in React 17 this blur happens in the browser but not in the test
+ act(() => list1rows[0].blur());
+ }
+ // resolve async drop
+ await act(async () => Promise.resolve());
+ // resolve focus movement
+ act(() => {jest.runAllTimers();});
+
+ grid1 = getAllByRole('grid')[0];
+ grid2 = getAllByRole('grid')[1];
+ list1rows = within(grid1).getAllByRole('row');
+ list2rows = within(grid2).getAllByRole('row');
+
+ expect(within(list1rows[0]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(list1rows[1]).getByRole('gridcell')).toHaveTextContent('Item Four');
+ expect(within(list1rows[2]).getByRole('gridcell')).toHaveTextContent('Item Five');
+
+ expect(within(list2rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(list2rows[1]).getByRole('gridcell')).toHaveTextContent('Item Three');
+ expect(within(list2rows[2]).getByRole('gridcell')).toHaveTextContent('Item Seven');
+
+ expect(document.activeElement).toBe(list2rows[0]);
+ });
});
describe('via keyboard', function () {
@@ -449,6 +670,226 @@ describe('ListView', function () {
dropOperation: 'move'
});
});
+
+ it('should allow moving one item within a list', async function () {
+ let {getAllByRole} = render();
+
+ let rows = getAllByRole('row');
+ expect(within(rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(rows[1]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(rows[2]).getByRole('gridcell')).toHaveTextContent('Item Three');
+
+ userEvent.tab();
+ let draghandle = within(getAllByRole('row')[1]).getAllByRole('button')[0];
+ expect(draghandle).toBeTruthy();
+
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowRight'});
+ fireEvent.keyUp(document.activeElement, {key: 'ArrowRight'});
+
+ expect(document.activeElement).toBe(draghandle);
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ act(() => jest.runAllTimers());
+
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowDown'});
+ fireEvent.keyUp(document.activeElement, {key: 'ArrowDown'});
+
+ expect(document.activeElement).toHaveAttribute('aria-label', 'Insert between Item Three and Item Four');
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ await act(async () => Promise.resolve());
+ expect(onDrop).toHaveBeenCalledTimes(1);
+
+ rows = getAllByRole('row');
+ expect(within(rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(rows[1]).getByRole('gridcell')).toHaveTextContent('Item Three');
+ expect(within(rows[2]).getByRole('gridcell')).toHaveTextContent('Item Two');
+
+ expect(document.activeElement).toBe(rows[2]);
+ });
+
+ it('should allow moving multiple items within a list', async function () {
+ let {getAllByRole} = render();
+
+ let rows = getAllByRole('row');
+ expect(within(rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(rows[1]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(rows[2]).getByRole('gridcell')).toHaveTextContent('Item Three');
+ expect(within(rows[3]).getByRole('gridcell')).toHaveTextContent('Item Four');
+
+ userEvent.tab();
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowDown'});
+ fireEvent.keyUp(document.activeElement, {key: 'ArrowDown'});
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ let draghandle = within(getAllByRole('row')[2]).getAllByRole('button')[0];
+ expect(draghandle).toBeTruthy();
+
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowRight'});
+ fireEvent.keyUp(document.activeElement, {key: 'ArrowRight'});
+
+ expect(document.activeElement).toBe(draghandle);
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ act(() => jest.runAllTimers());
+
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowDown'});
+ fireEvent.keyUp(document.activeElement, {key: 'ArrowDown'});
+
+ expect(document.activeElement).toHaveAttribute('aria-label', 'Insert between Item Four and Item Five');
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ await act(async () => Promise.resolve());
+ expect(onDrop).toHaveBeenCalledTimes(1);
+
+ rows = getAllByRole('row');
+ expect(within(rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(rows[1]).getByRole('gridcell')).toHaveTextContent('Item Four');
+ expect(within(rows[2]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(rows[3]).getByRole('gridcell')).toHaveTextContent('Item Three');
+
+ expect(document.activeElement).toBe(rows[3]);
+ });
+
+ it('should allow moving one item into another list', async function () {
+ let {getAllByRole} = render();
+
+ let list1 = getAllByRole('grid')[0];
+ let list2 = getAllByRole('grid')[1];
+
+ let list1rows = within(list1).getAllByRole('row');
+ let list2rows = within(list2).getAllByRole('row');
+ expect(within(list1rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(list1rows[1]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(list1rows[2]).getByRole('gridcell')).toHaveTextContent('Item Three');
+
+ expect(within(list2rows[0]).getByRole('gridcell')).toHaveTextContent('Item Seven');
+ expect(within(list2rows[1]).getByRole('gridcell')).toHaveTextContent('Item Eight');
+ expect(within(list2rows[2]).getByRole('gridcell')).toHaveTextContent('Item Nine');
+
+ userEvent.tab();
+
+ let draghandle = within(getAllByRole('row')[0]).getAllByRole('button')[0];
+ expect(draghandle).toBeTruthy();
+
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowRight'});
+ fireEvent.keyUp(document.activeElement, {key: 'ArrowRight'});
+
+ expect(document.activeElement).toBe(draghandle);
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ act(() => jest.runAllTimers());
+
+ userEvent.tab();
+
+ expect(document.activeElement).toHaveAttribute('aria-label', 'Insert before Item Seven');
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ await act(async () => Promise.resolve());
+ expect(onDrop).toHaveBeenCalledTimes(1);
+
+ act(() => jest.runAllTimers());
+
+ list1 = getAllByRole('grid')[0];
+ list2 = getAllByRole('grid')[1];
+ list1rows = within(list1).getAllByRole('row');
+ list2rows = within(list2).getAllByRole('row');
+
+ expect(within(list1rows[0]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(list1rows[1]).getByRole('gridcell')).toHaveTextContent('Item Three');
+ expect(within(list1rows[2]).getByRole('gridcell')).toHaveTextContent('Item Four');
+
+ expect(within(list2rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(list2rows[1]).getByRole('gridcell')).toHaveTextContent('Item Seven');
+ expect(within(list2rows[2]).getByRole('gridcell')).toHaveTextContent('Item Eight');
+
+ expect(document.activeElement).toBe(list2rows[0]);
+ });
+
+ it('should allow moving multiple items into another list', async function () {
+ let {getAllByRole} = render();
+
+ let list1 = getAllByRole('grid')[0];
+ let list2 = getAllByRole('grid')[1];
+
+ let list1rows = within(list1).getAllByRole('row');
+ let list2rows = within(list2).getAllByRole('row');
+
+ expect(within(list1rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(list1rows[1]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(list1rows[2]).getByRole('gridcell')).toHaveTextContent('Item Three');
+
+ expect(within(list2rows[0]).getByRole('gridcell')).toHaveTextContent('Item Seven');
+ expect(within(list2rows[1]).getByRole('gridcell')).toHaveTextContent('Item Eight');
+ expect(within(list2rows[2]).getByRole('gridcell')).toHaveTextContent('Item Nine');
+
+ userEvent.tab();
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowDown'});
+ fireEvent.keyUp(document.activeElement, {key: 'ArrowDown'});
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ let draghandle = within(list1rows[0]).getAllByRole('button')[0];
+ expect(draghandle).toBeTruthy();
+
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowRight'});
+ fireEvent.keyUp(document.activeElement, {key: 'ArrowRight'});
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ act(() => jest.runAllTimers());
+
+ userEvent.tab();
+
+ expect(document.activeElement).toHaveAttribute('aria-label', 'Insert before Item Seven');
+
+ fireEvent.keyDown(document.activeElement, {key: 'Enter'});
+ fireEvent.keyUp(document.activeElement, {key: 'Enter'});
+
+ await act(async () => Promise.resolve());
+ expect(onDrop).toHaveBeenCalledTimes(1);
+
+ act(() => jest.runAllTimers());
+
+ list1 = getAllByRole('grid')[0];
+ list2 = getAllByRole('grid')[1];
+ list1rows = within(list1).getAllByRole('row');
+ list2rows = within(list2).getAllByRole('row');
+
+ expect(within(list1rows[0]).getByRole('gridcell')).toHaveTextContent('Item Two');
+ expect(within(list1rows[1]).getByRole('gridcell')).toHaveTextContent('Item Four');
+ expect(within(list1rows[2]).getByRole('gridcell')).toHaveTextContent('Item Five');
+
+ expect(within(list2rows[0]).getByRole('gridcell')).toHaveTextContent('Item One');
+ expect(within(list2rows[1]).getByRole('gridcell')).toHaveTextContent('Item Three');
+ expect(within(list2rows[2]).getByRole('gridcell')).toHaveTextContent('Item Seven');
+
+ expect(document.activeElement).toBe(list2rows[0]);
+ });
});
it('should make row selection happen on pressUp if list is draggable', function () {