Skip to content

Commit 42c54f7

Browse files
committed
add tests and fix broken ones
1 parent 7ff84d5 commit 42c54f7

File tree

2 files changed

+195
-14
lines changed

2 files changed

+195
-14
lines changed

packages/@react-stately/data/src/useTreeData.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
160160
items: initialItems.map(item => {
161161
let node: TreeNode<T> = {
162162
key: getKey(item),
163-
parentKey: parentKey,
163+
parentKey: parentKey ?? null,
164164
value: item,
165165
children: null
166166
};
@@ -173,9 +173,9 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
173173
};
174174
}
175175

176-
function updateTree(items: TreeNode<T>[], key: Key, update: (node: TreeNode<T>) => TreeNode<T> | null, originalMap: Map<Key, TreeNode<T>>) {
177-
let node = originalMap.get(key);
178-
if (!node) {
176+
function updateTree(items: TreeNode<T>[], key: Key | null, update: (node: TreeNode<T>) => TreeNode<T> | null, originalMap: Map<Key, TreeNode<T>>) {
177+
let node = key == null ? null : originalMap.get(key);
178+
if (node == null) {
179179
return {items, nodeMap: originalMap};
180180
}
181181
let map = new Map<Key, TreeNode<T>>(originalMap);
@@ -442,7 +442,18 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
442442
};
443443
}
444444

445-
function moveItems<T extends object>(state: TreeDataState<T>, keys: Iterable<Key>, toParent: TreeNode<T> | null, toIndex: number, updateTree: (items: TreeNode<T>[], key: Key, update: (node: TreeNode<T>) => TreeNode<T> | null, originalMap: Map<Key, TreeNode<T>>) => TreeDataState<T>): TreeDataState<T> {
445+
function moveItems<T extends object>(
446+
state: TreeDataState<T>,
447+
keys: Iterable<Key>,
448+
toParent: TreeNode<T> | null,
449+
toIndex: number,
450+
updateTree: (
451+
items: TreeNode<T>[],
452+
key: Key,
453+
update: (node: TreeNode<T>) => TreeNode<T> | null,
454+
originalMap: Map<Key, TreeNode<T>>
455+
) => TreeDataState<T>
456+
): TreeDataState<T> {
446457
let {items, nodeMap} = state;
447458

448459
let parent = toParent;
@@ -464,23 +475,31 @@ function moveItems<T extends object>(state: TreeDataState<T>, keys: Iterable<Key
464475
let newMap = nodeMap;
465476
let i = 0;
466477

467-
function traversal(node, preorder, postorder) {
478+
function traversal(node, {inorder, preorder, postorder}) {
479+
inorder?.(node);
468480
if (node != null) {
469481
for (let child of node.children ?? []) {
470-
preorder(child);
471-
traversal(child, preorder, postorder);
472-
postorder(child);
482+
// preorder(child);
483+
traversal(child, {inorder, preorder, postorder});
484+
postorder?.(child);
473485
}
474486
}
475487
}
476488

477-
function preorder(child) {
478-
// pre-order so we add items as we encounter them in the tree, then we can insert them in expected order later
489+
function inorder(child) {
490+
// in-order so we add items as we encounter them in the tree, then we can insert them in expected order later
479491
if (keyArray.includes(child.key)) {
480492
inOrderKeys.set(child.key, i++);
481493
}
482494
}
483495

496+
function preorder(child) {
497+
// // add items as we encounter them in the tree, then we can insert them in expected order later
498+
// if (keyArray.includes(child.key)) {
499+
// inOrderKeys.set(child.key, i++);
500+
// }
501+
}
502+
484503
function postorder(child) {
485504
// remove items and update the tree from the leaves and work upwards toward the root, this way
486505
// we don't copy child node references from parents inadvertently
@@ -491,17 +510,19 @@ function moveItems<T extends object>(state: TreeDataState<T>, keys: Iterable<Key
491510
newMap = nextMap;
492511
}
493512
// decrement the index if the child being removed is in the target parent and before the target index
494-
if (child.parentKey === (toParent?.key ?? null)
513+
if (child.parentKey === toParent?.key
514+
&& keyArray.includes(child.key)
495515
&& (toParent?.children ? toParent.children.indexOf(child) : items.indexOf(child)) < originalToIndex) {
496516
toIndex--;
497517
}
498518
}
499519

500-
traversal({children: items}, preorder, postorder);
520+
traversal({children: items}, {inorder, preorder, postorder});
501521

502522
let inOrderItems = removedItems.sort((a, b) => inOrderKeys.get(a.key)! > inOrderKeys.get(b.key)! ? 1 : -1);
503523
// If parentKey is null, insert into the root.
504524
if (!toParent || toParent.key == null) {
525+
newMap = new Map(nodeMap);
505526
inOrderItems.forEach(movedNode => newMap.set(movedNode.key, movedNode));
506527
return {items: [
507528
...newItems.slice(0, toIndex),
@@ -510,6 +531,7 @@ function moveItems<T extends object>(state: TreeDataState<T>, keys: Iterable<Key
510531
], nodeMap: newMap};
511532
}
512533

534+
console.log('newItems', newItems, toIndex, inOrderItems)
513535
// Otherwise, update the parent node and its ancestors.
514536
return updateTree(newItems, toParent.key, parentNode => ({
515537
key: parentNode.key,

packages/@react-stately/data/test/useTreeData.test.js

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ describe('useTreeData', function () {
490490
expect(result.current.items).not.toBe(initialResult.items);
491491
expect(result.current.items).toHaveLength(1);
492492
expect(result.current.items[0].value).toEqual({expanded: true, name: 'Danny'});
493-
expect(result.current.items[0].parentKey).toBeUndefined();
493+
expect(result.current.items[0].parentKey).toBeNull();
494494
});
495495

496496
it('should update an item', function () {
@@ -869,6 +869,165 @@ describe('useTreeData', function () {
869869
expect(result.current.items.length).toEqual(2);
870870
});
871871

872+
it('can move an item multiple times', function () {
873+
const initialItems = [...initial, {name: 'Eli'}];
874+
875+
let {result} = renderHook(() =>
876+
useTreeData({initialItems, getChildren, getKey})
877+
);
878+
act(() => {
879+
result.current.moveAfter('Eli', ['David']);
880+
});
881+
expect(result.current.items[0].key).toEqual('Eli');
882+
expect(result.current.items[1].key).toEqual('David');
883+
act(() => {
884+
result.current.moveAfter('David', ['Eli']);
885+
});
886+
expect(result.current.items[0].key).toEqual('David');
887+
expect(result.current.items[1].key).toEqual('Eli');
888+
act(() => {
889+
result.current.moveAfter('Eli', ['David']);
890+
});
891+
expect(result.current.items[0].key).toEqual('Eli');
892+
expect(result.current.items[1].key).toEqual('David');
893+
act(() => {
894+
result.current.moveAfter('David', ['Eli']);
895+
});
896+
expect(result.current.items[0].key).toEqual('David');
897+
expect(result.current.items[1].key).toEqual('Eli');
898+
899+
// do the same with moveBefore
900+
act(() => {
901+
result.current.moveBefore('David', ['Eli']);
902+
});
903+
expect(result.current.items[0].key).toEqual('Eli');
904+
expect(result.current.items[1].key).toEqual('David');
905+
act(() => {
906+
result.current.moveBefore('Eli', ['David']);
907+
});
908+
expect(result.current.items[0].key).toEqual('David');
909+
expect(result.current.items[1].key).toEqual('Eli');
910+
act(() => {
911+
result.current.moveBefore('David', ['Eli']);
912+
});
913+
expect(result.current.items[0].key).toEqual('Eli');
914+
expect(result.current.items[1].key).toEqual('David');
915+
act(() => {
916+
result.current.moveBefore('Eli', ['David']);
917+
});
918+
expect(result.current.items[0].key).toEqual('David');
919+
expect(result.current.items[1].key).toEqual('Eli');
920+
});
921+
922+
it('can move an item within its same level', function () {
923+
const initialItems = [
924+
{id: 'projects', name: 'Projects', childItems: [
925+
{id: 'project-1', name: 'Project 1'},
926+
{id: 'project-2', name: 'Project 2', childItems: [
927+
{id: 'project-2A', name: 'Project 2A'},
928+
{id: 'project-2B', name: 'Project 2B'},
929+
{id: 'project-2C', name: 'Project 2C'}
930+
]},
931+
{id: 'project-3', name: 'Project 3'},
932+
{id: 'project-4', name: 'Project 4'},
933+
{id: 'project-5', name: 'Project 5', childItems: [
934+
{id: 'project-5A', name: 'Project 5A'},
935+
{id: 'project-5B', name: 'Project 5B'},
936+
{id: 'project-5C', name: 'Project 5C'}
937+
]}
938+
]},
939+
{id: 'reports', name: 'Reports', childItems: [
940+
{id: 'reports-1', name: 'Reports 1', childItems: [
941+
{id: 'reports-1A', name: 'Reports 1A', childItems: [
942+
{id: 'reports-1AB', name: 'Reports 1AB', childItems: [
943+
{id: 'reports-1ABC', name: 'Reports 1ABC'}
944+
]}
945+
]},
946+
{id: 'reports-1B', name: 'Reports 1B'},
947+
{id: 'reports-1C', name: 'Reports 1C'}
948+
]},
949+
{id: 'reports-2', name: 'Reports 2'}
950+
]}
951+
];
952+
953+
let {result} = renderHook(() =>
954+
useTreeData({initialItems, getChildren: (item) => item.childItems, getKey: (item) => item.id})
955+
);
956+
act(() => {
957+
result.current.moveAfter('project-3', ['project-2']);
958+
});
959+
expect(result.current.items[0].key).toEqual('projects');
960+
expect(result.current.items[0].children[0].key).toEqual('project-1');
961+
expect(result.current.items[0].children[1].key).toEqual('project-3');
962+
expect(result.current.items[0].children[2].key).toEqual('project-2');
963+
964+
// move again to the same place
965+
act(() => {
966+
result.current.moveAfter('project-3', ['project-2']);
967+
});
968+
expect(result.current.items[0].key).toEqual('projects');
969+
expect(result.current.items[0].children[0].key).toEqual('project-1');
970+
expect(result.current.items[0].children[1].key).toEqual('project-3');
971+
expect(result.current.items[0].children[2].key).toEqual('project-2');
972+
973+
// move to a different place
974+
act(() => {
975+
result.current.moveAfter('project-4', ['project-2']);
976+
});
977+
expect(result.current.items[0].key).toEqual('projects');
978+
expect(result.current.items[0].children[0].key).toEqual('project-1');
979+
expect(result.current.items[0].children[1].key).toEqual('project-3');
980+
expect(result.current.items[0].children[2].key).toEqual('project-4');
981+
expect(result.current.items[0].children[3].key).toEqual('project-2');
982+
});
983+
984+
985+
it('can move an item to a different level', function () {
986+
const initialItems = [
987+
{id: 'projects', name: 'Projects', childItems: [
988+
{id: 'project-1', name: 'Project 1'},
989+
{id: 'project-2', name: 'Project 2', childItems: [
990+
{id: 'project-2A', name: 'Project 2A'},
991+
{id: 'project-2B', name: 'Project 2B'},
992+
{id: 'project-2C', name: 'Project 2C'}
993+
]},
994+
{id: 'project-3', name: 'Project 3'},
995+
{id: 'project-4', name: 'Project 4'},
996+
{id: 'project-5', name: 'Project 5', childItems: [
997+
{id: 'project-5A', name: 'Project 5A'},
998+
{id: 'project-5B', name: 'Project 5B'},
999+
{id: 'project-5C', name: 'Project 5C'}
1000+
]}
1001+
]},
1002+
{id: 'reports', name: 'Reports', childItems: [
1003+
{id: 'reports-1', name: 'Reports 1', childItems: [
1004+
{id: 'reports-1A', name: 'Reports 1A', childItems: [
1005+
{id: 'reports-1AB', name: 'Reports 1AB', childItems: [
1006+
{id: 'reports-1ABC', name: 'Reports 1ABC'}
1007+
]}
1008+
]},
1009+
{id: 'reports-1B', name: 'Reports 1B'},
1010+
{id: 'reports-1C', name: 'Reports 1C'}
1011+
]},
1012+
{id: 'reports-2', name: 'Reports 2'}
1013+
]}
1014+
];
1015+
1016+
let {result} = renderHook(() =>
1017+
useTreeData({initialItems, getChildren: (item) => item.childItems, getKey: (item) => item.id})
1018+
);
1019+
act(() => {
1020+
result.current.moveBefore('project-2B', ['project-3']);
1021+
});
1022+
expect(result.current.items[0].key).toEqual('projects');
1023+
expect(result.current.items[0].children[0].key).toEqual('project-1');
1024+
expect(result.current.items[0].children[1].key).toEqual('project-2');
1025+
1026+
expect(result.current.items[0].children[1].children[0].key).toEqual('project-2A');
1027+
expect(result.current.items[0].children[1].children[1].key).toEqual('project-3');
1028+
expect(result.current.items[0].children[1].children[2].key).toEqual('project-2B');
1029+
});
1030+
8721031
it('should move an item to a different level after the target', function () {
8731032
const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}];
8741033
let {result} = renderHook(() =>

0 commit comments

Comments
 (0)