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: 24 additions & 0 deletions packages/react-aria-components/test/Table.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,30 @@ describe('Table', () => {
await user.keyboard('{Escape}');
act(() => jest.runAllTimers());
});

it('should select dropped item', async () => {
const DndTableExample = stories.DndTableExample;
let {getAllByRole} = render(<DndTableExample />);
let tableTester = testUtilUser.createTester('Table', {root: getAllByRole('grid')[1]});
expect(tableTester.rows).toHaveLength(7);
expect(tableTester.selectedRows).toHaveLength(0);
await user.tab();
await user.keyboard('{ArrowRight}');
await user.keyboard('{Enter}');
act(() => jest.runAllTimers());
expect(document.activeElement).toHaveAttribute('aria-label', 'Insert between Adobe Photoshop and Adobe XD');
await user.tab();
expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on');
await user.keyboard('{ArrowDown}');
expect(document.activeElement).toHaveAttribute('aria-label', 'Insert before Pictures');
fireEvent.keyDown(document.activeElement, {key: 'Enter'});
fireEvent.keyUp(document.activeElement, {key: 'Enter'});
// run onInsert promise in DnDTableExample first, otherwise updateFocusAfterDrop doesn't run properly
await act(async () => {});
act(() => jest.runAllTimers());
expect(tableTester.rows).toHaveLength(8);
expect(tableTester.selectedRows).toHaveLength(1);
});
});

describe('column resizing', () => {
Expand Down
128 changes: 128 additions & 0 deletions packages/react-aria-components/test/Tree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2075,6 +2075,134 @@ describe('Tree', () => {
expect(getItems).toHaveBeenCalledTimes(1);
expect(getItems).toHaveBeenCalledWith(new Set(['projects', 'reports']));
});

it('should select the parent and all its children when dropped', async () => {
let {getAllByRole} = render(<TreeWithDragAndDrop selectionMode="multiple" />);
let trees = getAllByRole('treegrid');

let firstTreeTester = testUtilUser.createTester('Tree', {root: trees[0]});
let secondTreeTester = testUtilUser.createTester('Tree', {root: trees[1]});
expect(firstTreeTester.rows).toHaveLength(2);
// has the empty state row
expect(secondTreeTester.rows).toHaveLength(1);
await user.tab();
// selects and drops first row onto second tree
await user.keyboard('{ArrowRight}');
await user.keyboard('{ArrowRight}');
await user.keyboard('{ArrowRight}');
await user.keyboard('{Enter}');
act(() => jest.runAllTimers());
await user.tab();
expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on');
await act(async () => {
fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'});
fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'});
});
act(() => jest.runAllTimers());
expect(secondTreeTester.rows).toHaveLength(1);
// expands tree row children
await user.keyboard('{ArrowRight}');
await user.keyboard('{ArrowDown}');
await user.keyboard('{ArrowDown}');
await user.keyboard('{ArrowRight}');
expect(secondTreeTester.selectedRows).toHaveLength(9);
});

it('should focus the parent row when dropped on if it isnt expanded', async () => {
let {getAllByRole} = render(<TreeWithDragAndDrop />);
let trees = getAllByRole('treegrid');

let firstTreeTester = testUtilUser.createTester('Tree', {root: trees[0]});
let secondTreeTester = testUtilUser.createTester('Tree', {root: trees[1]});
expect(firstTreeTester.rows).toHaveLength(2);
// has the empty state row
expect(secondTreeTester.rows).toHaveLength(1);
await user.tab();
// selects and drops first row onto second tree
await user.keyboard('{ArrowRight}');
await user.keyboard('{ArrowRight}');
await user.keyboard('{Enter}');
act(() => jest.runAllTimers());
await user.tab();
expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on');
await act(async () => {
fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'});
fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'});
});
act(() => jest.runAllTimers());
expect(secondTreeTester.rows).toHaveLength(1);
await user.keyboard('{ArrowRight}');
expect(secondTreeTester.rows).toHaveLength(6);
// tab back to the first tree and drop a new row onto one of the 2nd tree's child rows as it is expanded
await user.tab({shift: true});
expect(document.activeElement).toBe(firstTreeTester.rows[0]);
await user.keyboard('{ArrowRight}');
await user.keyboard('{Enter}');
act(() => jest.runAllTimers());
await user.tab();
for (let i = 0; i < 7; i++) {
await user.keyboard('{ArrowDown}');
}
expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on Project 2');
await act(async () => {
fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'});
fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'});
});
act(() => jest.runAllTimers());
expect(document.activeElement).toBe(secondTreeTester.rows[2]);
});

it('should focus the dropped row when dropped on a parent that is expanded', async () => {
let {getAllByRole} = render(<TreeWithDragAndDrop />);
let trees = getAllByRole('treegrid');

let firstTreeTester = testUtilUser.createTester('Tree', {root: trees[0]});
let secondTreeTester = testUtilUser.createTester('Tree', {root: trees[1]});
expect(firstTreeTester.rows).toHaveLength(2);
// has the empty state row
expect(secondTreeTester.rows).toHaveLength(1);
await user.tab();
// selects and drops first row onto second tree
await user.keyboard('{ArrowRight}');
await user.keyboard('{ArrowRight}');
await user.keyboard('{Enter}');
act(() => jest.runAllTimers());

await user.tab();
expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on');
await act(async () => {
fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'});
fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'});
});
act(() => jest.runAllTimers());
expect(secondTreeTester.rows).toHaveLength(1);
// expands tree row children
await user.keyboard('{ArrowRight}');
await user.keyboard('{ArrowDown}');
await user.keyboard('{ArrowDown}');
await user.keyboard('{ArrowRight}');
expect(secondTreeTester.rows).toHaveLength(9);
// tab back to the first tree and drop a new row onto one of the 2nd tree's child rows as it is expanded
await user.tab({shift: true});
expect(document.activeElement).toBe(firstTreeTester.rows[0]);
await user.keyboard('{ArrowRight}');
await user.keyboard('{Enter}');

act(() => jest.runAllTimers());
await user.tab();
for (let i = 0; i < 14; i++) {
await user.keyboard('{ArrowDown}');
}
expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on Project 2');
await act(async () => {
fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'});
fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'});
});
act(() => jest.runAllTimers());
expect(document.activeElement).toHaveTextContent('Projects');
expect(document.activeElement).toBe(secondTreeTester.rows[3]);

});
});

describe('press events', () => {
Expand Down
17 changes: 13 additions & 4 deletions packages/react-aria/src/dnd/useDroppableCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,18 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
state.selectionManager.isSelectionEqual(prevSelectedKeys)
) {
let newKeys = new Set<Key>();
for (let item of state.collection) {
if (item.type === 'item' && !prevCollection.getItem(item.key)) {
let key = state.collection.getFirstKey();
while (key != null) {
let item = state.collection.getItem(key);
if (item?.type === 'item' && !prevCollection.getItem(item.key)) {
newKeys.add(item.key);
}

if (item?.hasChildNodes && state.collection.getItem(item.lastChildKey!)?.type === 'item') {
key = item.firstChildKey!;
} else {
key = state.collection.getKeyAfter(key);
}
}

state.selectionManager.setSelectedKeys(newKeys);
Expand All @@ -268,10 +276,11 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
let first: Key | null | undefined = newKeys.keys().next().value;
if (first != null) {
let item = state.collection.getItem(first);

let dropTarget = droppingState.current.target;
let isParentRowExpanded = state.collection['expandedKeys'] ? state.collection['expandedKeys'].has(item?.parentKey) : false;
// If this is a cell, focus the parent row.
// eslint-disable-next-line max-depth
if (item?.type === 'cell') {
if (item && (item?.type === 'cell' || (dropTarget.type === 'item' && dropTarget.dropPosition === 'on' && !isParentRowExpanded))) {
first = item.parentKey;
}

Expand Down
Loading