Skip to content

Commit 5314341

Browse files
committed
Fix expanding nodes with more than 32768 children
1 parent 14ade87 commit 5314341

File tree

3 files changed

+163
-5
lines changed

3 files changed

+163
-5
lines changed

__tests__/FixedSizeTree.spec.tsx

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import {
1111
TreeWalker,
1212
TreeWalkerValue,
1313
} from '../src';
14-
import {NodeComponentProps} from '../src/Tree';
14+
import {NodeComponentProps, NodePublicState} from '../src/Tree';
1515
import {
1616
defaultTree,
17+
treeWithLargeNode,
1718
extractReceivedRecords,
1819
mockRequestIdleCallback,
1920
sleep,
@@ -49,15 +50,15 @@ describe('FixedSizeTree', () => {
4950
>;
5051
let tree: TreeNode;
5152
let treeWalkerSpy: jest.Mock;
52-
let isOpenByDefault: boolean;
53+
let isOpenByDefault: (node: TreeNode) => boolean;
5354

5455
const getNodeData = (
5556
node: TreeNode,
5657
nestingLevel: number,
5758
): TreeWalkerValue<ExtendedData, NodeMeta> => ({
5859
data: {
5960
id: node.id.toString(),
60-
isOpenByDefault,
61+
isOpenByDefault: isOpenByDefault(node),
6162
name: node.name,
6263
nestingLevel,
6364
},
@@ -103,7 +104,7 @@ describe('FixedSizeTree', () => {
103104
beforeEach(() => {
104105
tree = defaultTree;
105106

106-
isOpenByDefault = true;
107+
isOpenByDefault = () => true;
107108

108109
treeWalkerSpy = jest.fn(treeWalker);
109110

@@ -596,5 +597,115 @@ describe('FixedSizeTree', () => {
596597
list = component.find(FixedSizeList);
597598
expect(list.prop('itemCount')).toBe(7);
598599
});
600+
601+
it('correctly collapses node with 100.000 children', async () => {
602+
tree = treeWithLargeNode;
603+
component = mountComponent();
604+
605+
const records = extractReceivedRecords(component.find(FixedSizeList));
606+
const {setOpen} = records.find(
607+
(record) => record.data.id === 'largeNode-1',
608+
) as NodePublicState<ExtendedData>;
609+
610+
await setOpen(false);
611+
component.update(); // Update the wrapper to get the latest changes
612+
613+
const updatedRecords = extractReceivedRecords(
614+
component.find(FixedSizeList),
615+
);
616+
expect(updatedRecords).toEqual([
617+
expect.objectContaining({
618+
data: expect.objectContaining({
619+
id: 'root-1',
620+
}),
621+
}),
622+
expect.objectContaining({
623+
data: expect.objectContaining({
624+
id: 'smallNode-1',
625+
}),
626+
}),
627+
expect.objectContaining({
628+
data: expect.objectContaining({
629+
id: 'smallNodeChild-1',
630+
}),
631+
}),
632+
expect.objectContaining({
633+
data: expect.objectContaining({
634+
id: 'smallNodeChild-2',
635+
}),
636+
}),
637+
expect.objectContaining({
638+
data: expect.objectContaining({
639+
id: 'largeNode-1',
640+
}),
641+
}),
642+
expect.objectContaining({
643+
data: expect.objectContaining({
644+
id: 'smallNode-2',
645+
}),
646+
}),
647+
expect.objectContaining({
648+
data: expect.objectContaining({
649+
id: 'smallNodeChild-3',
650+
}),
651+
}),
652+
expect.objectContaining({
653+
data: expect.objectContaining({
654+
id: 'smallNodeChild-4',
655+
}),
656+
}),
657+
]);
658+
});
659+
660+
it('correctly expands node with 100.000 children', async () => {
661+
tree = treeWithLargeNode;
662+
isOpenByDefault = (node) => {
663+
if (node.id === 'largeNode-1') {
664+
return false;
665+
}
666+
return true;
667+
};
668+
component = mountComponent();
669+
670+
const records = extractReceivedRecords(component.find(FixedSizeList));
671+
const {setOpen} = records.find(
672+
(record) => record.data.id === 'largeNode-1',
673+
) as NodePublicState<ExtendedData>;
674+
675+
await setOpen(true);
676+
component.update(); // Update the wrapper to get the latest changes
677+
678+
const updatedRecords = extractReceivedRecords(
679+
component.find(FixedSizeList),
680+
);
681+
682+
expect(updatedRecords.slice(-5)).toEqual([
683+
expect.objectContaining({
684+
data: expect.objectContaining({
685+
id: 'largeNodeChild-99999',
686+
}),
687+
}),
688+
expect.objectContaining({
689+
data: expect.objectContaining({
690+
id: 'largeNodeChild-100000',
691+
}),
692+
}),
693+
expect.objectContaining({
694+
data: expect.objectContaining({
695+
id: 'smallNode-2',
696+
}),
697+
}),
698+
expect.objectContaining({
699+
data: expect.objectContaining({
700+
id: 'smallNodeChild-3',
701+
}),
702+
}),
703+
expect.objectContaining({
704+
data: expect.objectContaining({
705+
id: 'smallNodeChild-4',
706+
}),
707+
}),
708+
]);
709+
});
599710
});
600711
});

__tests__/utils/misc.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,44 @@ export const defaultTree = {
7878
id: 'foo-1',
7979
name: 'Foo #1',
8080
};
81+
82+
const getLargeSetOfChildren = () => {
83+
const children = [];
84+
85+
for (let i = 0; i < 100000; i++) {
86+
children.push({
87+
id: `largeNodeChild-${i + 1}`,
88+
name: `Large Node Child #${i + 1}`,
89+
});
90+
}
91+
92+
return children;
93+
};
94+
95+
export const treeWithLargeNode = {
96+
children: [
97+
{
98+
children: [
99+
{id: 'smallNodeChild-1', name: 'Small Node Child #1'},
100+
{id: 'smallNodeChild-2', name: 'Small Node Child #2'},
101+
],
102+
id: 'smallNode-1',
103+
name: 'Small Node #1',
104+
},
105+
{
106+
children: getLargeSetOfChildren(),
107+
id: 'largeNode-1',
108+
name: 'Large Node #1',
109+
},
110+
{
111+
children: [
112+
{id: 'smallNodeChild-3', name: 'Small Node Child #3'},
113+
{id: 'smallNodeChild-4', name: 'Small Node Child #4'},
114+
],
115+
id: 'smallNode-2',
116+
name: 'Small Node #2',
117+
},
118+
],
119+
id: 'root-1',
120+
name: 'Root #1',
121+
};

src/Tree.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ const generateNewTree = <
386386
};
387387

388388
const MAX_FUNCTION_ARGUMENTS = 32768;
389+
const SPLICE_DEFAULT_ARGUMENTS_NUMBER = 2;
389390

390391
// If we need to perform only the update, treeWalker won't be used. Update will
391392
// work internally, traversing only the subtree of elements that require
@@ -484,8 +485,13 @@ const updateExistingTree = <
484485
orderParts[orderPartsCursor].length === MAX_FUNCTION_ARGUMENTS
485486
) {
486487
orderPartsCursor += 1;
488+
// Every chunk contains 2 arguments (start and delete) that are not records
489+
// we have to account for them when setting the start point of a new chunk.
487490
orderParts.push([
488-
index + 1 + orderPartsCursor * MAX_FUNCTION_ARGUMENTS,
491+
index +
492+
1 +
493+
orderPartsCursor * MAX_FUNCTION_ARGUMENTS -
494+
orderPartsCursor * SPLICE_DEFAULT_ARGUMENTS_NUMBER,
489495
0,
490496
]);
491497
}

0 commit comments

Comments
 (0)