Skip to content

Commit

Permalink
Feature/filter (#21)
Browse files Browse the repository at this point in the history
* Update tsconfig.json

* init

* created sample dynamic types

* parsing with a custom children property for the model is working

* modified the build CI to only activate with pull requests

* attempted to filter the array

* working filter function, will likely need to regenerate the trees after filtering them. Suggestion: take he model, filter it, then generate the tree from there

* two filtering modes working successfully

* fixed the typing, and added a test for when the first node is removed under "remove children" mode
  • Loading branch information
riexn authored Dec 29, 2021
1 parent 7c8e769 commit 42d6fb0
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 2 deletions.
24 changes: 24 additions & 0 deletions app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import treeHandler from '../src/treeHandler';

const dataTree = {
id: '1',
tag: 'pending',
subtasks: [
{ id: '2', tag: 'pending', subtasks: [] },
{
id: '3',
tag: 'in progress',
subtasks: [
{ id: '4', tag: 'pending', subtasks: [] },
{ id: '5', tag: 'pending', subtasks: [] },
],
},
{ id: '4', tag: 'complete', subtasks: [] },
],
};

const tree = treeHandler.parse(dataTree, { childrenProperty: 'subtasks' });
const newTree = tree.filter((node) => {
return node.model.tag === 'pending';
});
console.log('new tree', newTree);
58 changes: 57 additions & 1 deletion src/TreeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from './types';
import { clamp } from './utils';
import treeHandler from './treeHandler';
import { ParseConfigProps } from '.';
import { FilterConfig, ModelPredicateFunction, ParseConfigProps } from '.';

export class TreeNode<T extends TreeModel> {
private _model: T = {} as T;
Expand Down Expand Up @@ -76,6 +76,37 @@ export class TreeNode<T extends TreeModel> {
return result;
}

public filter(
func: ModelPredicateFunction,
mode?: 'removeChildren'
): TreeNode<T> | undefined;

public filter(
func: ModelPredicateFunction,
mode?: 'mergeChildren'
): TreeNode<T>[];

public filter(
func: ModelPredicateFunction,
mode: 'mergeChildren' | 'removeChildren' = 'removeChildren'
): TreeNode<T>[] | TreeNode<T> | undefined {
if (mode === 'mergeChildren') {
const newTrees = _filter(func, this.config.childrenProperty)(this.model);
return newTrees.map((tree) => treeHandler.parse(tree, this.config));
} else {
const newTree = _filterRemoveChildren(
func,
this.config.childrenProperty,
this.model
);

if (newTree) {
return treeHandler.parse(newTree, this.config);
}
return undefined;
}
}

public moveUnderParnet({
node,
toParent,
Expand Down Expand Up @@ -218,3 +249,28 @@ export class TreeNode<T extends TreeModel> {
});
}
}

// keeps the children
const _filter =
(func: ModelPredicateFunction, childrenProperty: string) =>
(model: TreeModel): TreeModel[] => {
let filteredChidlren: any[] = model[childrenProperty].flatMap(
_filter(func, childrenProperty)
);
return !func(model)
? filteredChidlren
: [{ ...model, [childrenProperty]: filteredChidlren }];
};
const _filterRemoveChildren = (
func: ModelPredicateFunction,
childrenProperty: string,
tree: TreeModel
) => {
if (tree[childrenProperty].length > 0) {
tree[childrenProperty] = tree[childrenProperty].filter((child: TreeModel) =>
Boolean(_filterRemoveChildren(func, childrenProperty, child))
);
}

return func(tree) ? tree : undefined;
};
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export interface TreeHandlerConstructor {
model: TreeModel;
}

export interface FilterConfig {
keepChildren: boolean;
}

export interface ParseConfigProps {
childrenProperty: string;
}
Expand All @@ -22,6 +26,10 @@ export interface PredicateFunction<T extends TreeModel> {
(node: TreeNode<T>): boolean;
}

export interface ModelPredicateFunction {
(node: TreeModel): boolean;
}

export interface ForEachFunction<T extends TreeModel> {
(node: TreeNode<T>): void;
}
Expand Down
163 changes: 163 additions & 0 deletions test/filter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import treeHandler from '../src/index';

describe('Filter', () => {
test('Merge the children to their parent position', () => {
const dataTree = {
id: '1',
tag: 'pending',
subtasks: [
{ id: '2', tag: 'pending', subtasks: [] },
{
id: '3',
tag: 'in progress',
subtasks: [
{
id: '4',
tag: 'pending',
subtasks: [
{
id: '6',
tag: 'complete',
subtasks: [
{
id: '10',
tag: 'pending',
subtasks: [{ id: '10', tag: 'complete', subtasks: [] }],
},
],
},
{ id: '7', tag: 'complete', subtasks: [] },
],
},
{ id: '5', tag: 'pending', subtasks: [] },
],
},
{ id: '4', tag: 'complete', subtasks: [] },
],
};

const treeResult = {
id: '1',
tag: 'pending',
subtasks: [
{ id: '2', tag: 'pending', subtasks: [] },
{
id: '4',
tag: 'pending',
subtasks: [
{
id: '6',
tag: 'complete',
subtasks: [
{
id: '10',
tag: 'pending',
subtasks: [{ id: '10', tag: 'complete', subtasks: [] }],
},
],
},
{ id: '7', tag: 'complete', subtasks: [] },
],
},
{ id: '5', tag: 'pending', subtasks: [] },
{ id: '4', tag: 'complete', subtasks: [] },
],
};

const tree = treeHandler.parse(dataTree, { childrenProperty: 'subtasks' });
const newTree = tree.filter(
(node) => node.tag !== 'in progress',
'mergeChildren'
);
expect(treeResult).toStrictEqual(newTree[0]?.model);
});

test('Remove children (default mode)', () => {
const dataTree = {
id: '1',
tag: 'pending',
subtasks: [
{
id: '2',
tag: 'pending',
subtasks: [
{ id: '4', tag: 'complete', subtasks: [] },
{ id: '46', tag: 'in progress', subtasks: [] },
],
},
{
id: '3',
tag: 'in progress',
subtasks: [
{
id: '4',
tag: 'pending',
subtasks: [
{
id: '6',
tag: 'complete',
subtasks: [
{
id: '10',
tag: 'pending',
subtasks: [{ id: '11', tag: 'complete', subtasks: [] }],
},
],
},
{
id: '7',
tag: 'complete',
subtasks: [{ id: '74', tag: 'in progress', subtasks: [] }],
},
],
},
{ id: '5', tag: 'pending', subtasks: [] },
],
},
{ id: '4', tag: 'complete', subtasks: [] },
],
};

const treeResult = {
id: '1',
tag: 'pending',
subtasks: [
{
id: '2',
tag: 'pending',
subtasks: [{ id: '4', tag: 'complete', subtasks: [] }],
},
{ id: '4', tag: 'complete', subtasks: [] },
],
};

const tree = treeHandler.parse(dataTree, {
childrenProperty: 'subtasks',
});
const newTree = tree.filter((node) => node.tag !== 'in progress');
expect(treeResult).toStrictEqual(newTree?.model);
});

test('filter out root', () => {
const dataTree = {
id: '1',
tag: 'pending',
subtasks: [
{
id: '2',
tag: 'pending',
subtasks: [
{ id: '4', tag: 'complete', subtasks: [] },
{ id: '46', tag: 'in progress', subtasks: [] },
],
},
],
};
const tree = treeHandler.parse(dataTree, {
childrenProperty: 'subtasks',
});

const newTree = tree.filter((node) => node.tag !== 'pending');
expect(newTree).toBe(undefined);
});
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// "incremental": true, /* Enable incremental compilation */
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": ["ES2015"], /* Specify library files to be included in the compilation. */
"lib": ["ES2019"], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
Expand Down

0 comments on commit 42d6fb0

Please sign in to comment.