Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
125 changes: 125 additions & 0 deletions packages/react-aria-components/test/Tree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2045,6 +2045,131 @@ 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');
fireEvent.keyDown(document.activeElement, {key: 'Enter'});
fireEvent.keyUp(document.activeElement, {key: 'Enter'});
// run onInsert promise
await act(async () => {});
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 () => {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two tests actually always pass without the changes in useDroppableCollection, not quite sure why it differs from the observed browser behavior. Also interesting to note is that having Strict Mode on in the storybook locally also will cause the issue to not appear...

Still digging as to why this would be the case

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');
fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'});
fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'});
// run onInsert promise
await act(async () => {});
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 user.keyboard('{Enter}');
await act(async () => {});
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');
fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'});
fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'});
// run onInsert promise
await act(async () => {});
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');
fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'});
fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'});
await act(async () => {});
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)) {
Comment on lines -256 to +259
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Root issue was that using the collection iterator for TableCollection returns just the column header and the body, not the actual rows. Walking the items should be more accurate I believe

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