Skip to content

Commit

Permalink
fix(tree): unflattening tree->flat array multiple times, fixes #1657
Browse files Browse the repository at this point in the history
fixes #1657
- calling the function `unflattenParentChildArrayToTree` multiple times should always produce the same result
  • Loading branch information
ghiscoding committed Aug 30, 2024
1 parent b3a25fc commit 67edd1e
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 3 deletions.
56 changes: 56 additions & 0 deletions packages/common/src/services/__tests__/utilities.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,34 @@ describe('Service/Utilies', () => {
]);
});

it('should produce same result when calling the function twice with input that have a parent/child array and return a hierarchical array structure', () => {
const input = [
{ id: 18, size: 90, dateModified: '2015-03-03', file: 'something.txt', parentId: null, },
{ id: 11, file: 'Music', parentId: null, },
{ id: 12, file: 'mp3', parentId: 11, },
{ id: 16, file: 'rock', parentId: 12, },
{ id: 17, dateModified: '2015-05-13', file: 'soft.mp3', size: 98, parentId: 16, },
{ id: 14, file: 'pop', parentId: 12, },
{ id: 15, dateModified: '2015-03-01', file: 'theme.mp3', size: 85, parentId: 14, },
];

const output1 = unflattenParentChildArrayToTree(input, { parentPropName: 'parentId', childrenPropName: 'files' });
const output2 = unflattenParentChildArrayToTree(input, { parentPropName: 'parentId', childrenPropName: 'files' });

expect(output1).toEqual([
{
id: 11, __treeLevel: 0, __collapsed: false, parentId: null, file: 'Music', files: [{
id: 12, __treeLevel: 1, __collapsed: false, parentId: 11, file: 'mp3', files: [
{ id: 14, __treeLevel: 2, __collapsed: false, parentId: 12, file: 'pop', files: [{ id: 15, __treeLevel: 3, parentId: 14, file: 'theme.mp3', dateModified: '2015-03-01', size: 85, }] },
{ id: 16, __treeLevel: 2, __collapsed: false, parentId: 12, file: 'rock', files: [{ id: 17, __treeLevel: 3, parentId: 16, file: 'soft.mp3', dateModified: '2015-05-13', size: 98, }] },
]
}]
},
{ id: 18, __treeLevel: 0, parentId: null, file: 'something.txt', dateModified: '2015-03-03', size: 90, },
]);
expect(output1).toEqual(output2);
});

it('should take a parent/child array with aggregators and return a hierarchical array structure', () => {
const input = [
{ id: 18, size: 90, dateModified: '2015-03-03', file: 'something.txt', parentId: null, },
Expand All @@ -82,6 +110,34 @@ describe('Service/Utilies', () => {
{ id: 18, __treeLevel: 0, parentId: null, file: 'something.txt', dateModified: '2015-03-03', size: 90, },
]);
});

it('should produce same result when calling the function twice with input that have a parent/child array with aggregators and return a hierarchical array structure', () => {
const input = [
{ id: 18, size: 90, dateModified: '2015-03-03', file: 'something.txt', parentId: null, },
{ id: 11, file: 'Music', parentId: null, __treeTotals: { count: { size: 2, }, sum: { size: (98 + 85), }, }, },
{ id: 12, file: 'mp3', parentId: 11, __treeTotals: { count: { size: 2, }, sum: { size: (98 + 85), }, }, },
{ id: 16, file: 'rock', parentId: 12, __treeTotals: { count: { size: 2, }, sum: { size: 98, }, }, },
{ id: 17, dateModified: '2015-05-13', file: 'soft.mp3', size: 98, parentId: 16, },
{ id: 14, file: 'pop', parentId: 12, __treeTotals: { count: { size: 2, }, sum: { size: 85, }, }, },
{ id: 15, dateModified: '2015-03-01', file: 'theme.mp3', size: 85, parentId: 14, },
];

const output1 = unflattenParentChildArrayToTree(input, { aggregators: [new SumAggregator('size')], parentPropName: 'parentId', childrenPropName: 'files' });
const output2 = unflattenParentChildArrayToTree(input, { aggregators: [new SumAggregator('size')], parentPropName: 'parentId', childrenPropName: 'files' });

expect(output1).toEqual([
{
id: 11, __treeLevel: 0, __collapsed: false, parentId: null, file: 'Music', __treeTotals: { count: { size: 2, }, sum: { size: (98 + 85), }, }, files: [{
id: 12, __treeLevel: 1, __collapsed: false, parentId: 11, file: 'mp3', __treeTotals: { count: { size: 2, }, sum: { size: (98 + 85), }, }, files: [
{ id: 14, __treeLevel: 2, __collapsed: false, parentId: 12, file: 'pop', __treeTotals: { count: { size: 1, }, sum: { size: 85, }, }, files: [{ id: 15, __treeLevel: 3, parentId: 14, file: 'theme.mp3', dateModified: '2015-03-01', size: 85, }] },
{ id: 16, __treeLevel: 2, __collapsed: false, parentId: 12, file: 'rock', __treeTotals: { count: { size: 1, }, sum: { size: 98, }, }, files: [{ id: 17, __treeLevel: 3, parentId: 16, file: 'soft.mp3', dateModified: '2015-05-13', size: 98, }] },
]
}]
},
{ id: 18, __treeLevel: 0, parentId: null, file: 'something.txt', dateModified: '2015-03-03', size: 90, },
]);
expect(output1).toEqual(output2);
});
});

describe('cancellablePromise method', () => {
Expand Down
12 changes: 9 additions & 3 deletions packages/common/src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function flattenToParentChildArray<T>(treeArray: T[], options?: { aggrega
type FlatParentChildArray = Omit<T, keyof typeof childrenPropName>;

if (options?.shouldAddTreeLevelNumber) {
if (options?.aggregators) {
if (Array.isArray(options?.aggregators)) {
options.aggregators.forEach((aggregator) => {
addTreeLevelAndAggregatorsByMutation(treeArray, { childrenPropName, levelPropName, aggregator });
});
Expand Down Expand Up @@ -179,7 +179,13 @@ export function unflattenParentChildArrayToTree<P, T extends P & { [childrenProp
if (!(childrenPropName in p)) {
p[childrenPropName] = [];
}
p[childrenPropName].push(item);
const existIdx = p[childrenPropName]?.findIndex((x: any) => x.id === item.id);
if (existIdx < 0) {
p[childrenPropName].push(item);
} else {
// replace existing one when already exists (probably equal to the same item in the end)
p[childrenPropName][existIdx] = item;
}
if (p[collapsedPropName] === undefined) {
p[collapsedPropName] = options?.initiallyCollapsed ?? false;
}
Expand All @@ -189,7 +195,7 @@ export function unflattenParentChildArrayToTree<P, T extends P & { [childrenProp
// we need and want the Tree Level,
// we can do that after the tree is created and mutate the array by adding a __treeLevel property on each item
// perhaps there might be a way to add this while creating the tree for now that is the easiest way I found
if (options?.aggregators) {
if (Array.isArray(options?.aggregators)) {
options.aggregators.forEach((aggregator) => {
addTreeLevelAndAggregatorsByMutation(roots, { childrenPropName, levelPropName, aggregator }, 0);
});
Expand Down

0 comments on commit 67edd1e

Please sign in to comment.