Skip to content

Commit

Permalink
feat(design): add immutable tree transform (#2776)
Browse files Browse the repository at this point in the history
  • Loading branch information
damienwebdev committed Apr 29, 2024
1 parent 2d8ec58 commit d2ce4fd
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 0 deletions.
1 change: 1 addition & 0 deletions libs/design/tree/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export { DaffTreeItemDirective } from './tree-item/tree-item.directive';
export { DaffTreeData } from './interfaces/tree-data';
export { DaffTreeUi } from './interfaces/tree-ui';
export { daffTransformTreeInPlace } from './utils/transform-in-place';
export { daffTransformTree } from './utils/transform';
export { DaffTreeRenderMode } from './interfaces/tree-render-mode';
223 changes: 223 additions & 0 deletions libs/design/tree/src/utils/transform.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import { daffTransformTree } from './transform';
import { DaffTreeData } from '../interfaces/tree-data';

describe('daffTransformTree', () => {
it('should transform a tree like structure into a DaffTreeData', () => {
const myTree = {
name: 'Test',
link: '/test.html',
carbonScore: 1,
children: [{
name: 'Test 2',
link: '/test2.html',
carbonScore: 2,
children: [],
}],
};
const transformer = (node: typeof myTree): DaffTreeData<{ carbonScore: number}> => ({
title: node.name,
url: node.link,
id: node.name,
items: [],
data: {
carbonScore: node.carbonScore,
},
});

expect(daffTransformTree(myTree, transformer, 'children')).toEqual({
title: 'Test',
url: '/test.html',
data: {
carbonScore: 1,
},
items: [
{
title: 'Test 2',
url: '/test2.html',
data: {
carbonScore: 2,
},
items: [],
id: 'Test 2',
},
],
id: 'Test',
});
});

it('should transform a more complex tree structure into a DaffTreeData', () => {
const myTree = {
name: 'Test',
link: '/test.html',
carbonScore: 1,
children: [
{
name: 'Test 2',
link: '/test2.html',
carbonScore: 2,
children: [],
},
{
name: 'Test 3',
link: '/test3.html',
carbonScore: 3,
children: [
{
name: 'Test 4',
link: '/test4.html',
carbonScore: 4,
children: [],
},
{
name: 'Test 5',
link: '/test5.html',
carbonScore: 5,
children: [],
},
],
},
],
};
const transformer = (node: typeof myTree): DaffTreeData<{ carbonScore: number}> => ({
title: node.name,
url: node.link,
id: node.name,
items: [],
data: {
carbonScore: node.carbonScore,
},
});

expect(daffTransformTree(myTree, transformer, 'children')).toEqual({
title: 'Test',
url: '/test.html',
data: {
carbonScore: 1,
},
items: [
{
title: 'Test 2',
url: '/test2.html',
data: {
carbonScore: 2,
},
items: [],
id: 'Test 2',
},
{
title: 'Test 3',
url: '/test3.html',
data: {
carbonScore: 3,
},
items: [
{
title: 'Test 4',
url: '/test4.html',
data: {
carbonScore: 4,
},
items: [],
id: 'Test 4',
},
{
title: 'Test 5',
url: '/test5.html',
data: {
carbonScore: 5,
},
items: [],
id: 'Test 5',
},
],
id: 'Test 3',
},
],
id: 'Test',
});
});

it('should not modify the original tree structure', () => {
const myTree = {
name: 'test',
tacos: 1,
items: [],
};
Object.freeze(myTree);
const transform = (t: typeof myTree): DaffTreeData<{ tacos: number}> => ({
title: t.name,
url: '',
id: '',
items: [],
data: {
tacos: t.tacos,
},
});
expect(() => daffTransformTree(myTree, transform, 'items')).not.toThrowError();
});

it('should allow you to add more items during transform', () => {
const myTree = {
name: 'test',
tacos: 1,
items: [
{
name: 'test',
tacos: 2,
items: [],
},
],
};
const transform = (t: typeof myTree): DaffTreeData<{ tacos: number}> => {
const items = t.tacos === 1 ? [
{
title: 'test',
url: '',
id: '',
items: [],
data: {
tacos: 3,
},
},
] : [];
return {
title: t.name,
url: '',
id: '',
items,
data: {
tacos: t.tacos,
},
};
};

expect(daffTransformTree(myTree, transform, 'items')).toEqual({
title: 'test',
url: '',
id: '',
items: [
{
title: 'test',
url: '',
id: '',
items: [],
data: {
tacos: 3,
},
},
{
title: 'test',
url: '',
id: '',
items: [],
data: {
tacos: 2,
},
},
],
data: {
tacos: 1,
},
});
});
});
40 changes: 40 additions & 0 deletions libs/design/tree/src/utils/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { RecursiveTreeKeyOfType } from '../interfaces/recursive-key';
import { DaffTreeData } from '../interfaces/tree-data';

/**
* Transform a tree-like structure into a {@link DaffTreeData}.
*
* @param tree - The data structure representing tree-like data.
* @param transformFn - A user-supplied function that will transform the user
* type into a {@link DaffTreeData}
* @param key - The property of the your tree that indicates which
* key contains the "children" of your tree structure.
*
*/
export const daffTransformTree = <
// eslint-disable-next-line @typescript-eslint/ban-types
T extends Record<any,any>,
V
>(
tree: T,
transformFn: (type: T) => DaffTreeData<V>,
key: RecursiveTreeKeyOfType<T>,
): DaffTreeData<V> => {

const transformedTree: DaffTreeData<V> = transformFn(tree);

const queue: { node: T; parent: DaffTreeData<V> }[] = [{ node: tree, parent: transformedTree }];

while (queue.length > 0) {
const { node, parent } = queue.shift();

const childItems = node[key];
for (const child of <T[]>childItems) {
const transformedChild: DaffTreeData<V> = transformFn(child);
parent.items.push(transformedChild);
queue.push({ node: child, parent: transformedChild });
}
}

return transformedTree;
};

0 comments on commit d2ce4fd

Please sign in to comment.