From a962af01c0abb1f7568f1277d0cefe48f01048bd Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Thu, 14 Mar 2024 10:00:42 +0200 Subject: [PATCH 01/17] feat: create src directoy --- .gitignore | 1 + .vscode/settings.json | 2 +- package.json | 10 +- src/cosmoz-default-tree.ts | 3 + src/cosmoz-tree.ts | 393 +++++++++++++++++++++++++++++++++++++ tsconfig.build.json | 9 + tsconfig.json | 11 ++ 7 files changed, 426 insertions(+), 3 deletions(-) create mode 100644 src/cosmoz-default-tree.ts create mode 100644 src/cosmoz-tree.ts create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 5673960..10d5074 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules/ coverage/ debug.log +dist yarn-error.log .DS_Store .eslintcache diff --git a/.vscode/settings.json b/.vscode/settings.json index e7935e0..a09ecf6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,6 @@ { "language": "html", "autoFix": true } ], "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" } } diff --git a/package.json b/package.json index 7fa956d..5a512f7 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,12 @@ "demo/*.json", "cosmoz-*.js", "test/data/", - "examples" + "examples", + "dist/*" ], "scripts": { - "lint": "eslint --cache --ext .js,.html .", + "lint": "tsc && eslint --cache --ext .js,.html .", + "build": "tsc -p tsconfig.build.json", "start": "wds", "test": "wtr --coverage", "test:watch": "wtr --watch", @@ -47,6 +49,10 @@ "publishConfig": { "access": "public" }, + "exports": { + "./cosmoz-tree": "./dist/cosmoz-tree.js", + "./cosmoz-default-tree": "./dist/cosmoz-default-tree.js" + }, "commitlint": { "extends": [ "@commitlint/config-conventional" diff --git a/src/cosmoz-default-tree.ts b/src/cosmoz-default-tree.ts new file mode 100644 index 0000000..069080d --- /dev/null +++ b/src/cosmoz-default-tree.ts @@ -0,0 +1,3 @@ +import { Tree } from './cosmoz-tree'; + +export { Tree as DefaultTree }; diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts new file mode 100644 index 0000000..508c13d --- /dev/null +++ b/src/cosmoz-tree.ts @@ -0,0 +1,393 @@ +/** +Navigator through object with treelike datastructure and default settings. + +@demo demo/index.html +*/ + +export type TreeOptions = { + childProperty?: string; + searchProperty?: string; + pathLocatorSeparator?: string; + pathStringSeparator?: string; +}; + +export class Tree { + _treeData: object; + + _roots: object; + + childProperty: string; + + searchProperty: string; + + pathLocatorSeparator: string; + + pathStringSeparator: string; + + /** + * @param {Object} treeData (The tree object.) + * @param {Object} options (Tree options.) + * @param {String} options.childProperty ["children"] (The name of the property a search should be based on. e.g. "name") + * @param {String} options.propertyName ["name"] (The name of the property a search should be based on. e.g. "name") + * @param {String} options.pathStringSeparator ["/"] (The string the path should get separated with.) + * @param {String} options.pathLocatorSeparator ["."] (The string which separates the path segments of a path locator.) + */ + constructor(treeData: object, options: TreeOptions = {}) { + this._treeData = treeData; + this._roots = Object.values(treeData); + + this.pathLocatorSeparator = options.pathLocatorSeparator || '.'; + this.pathStringSeparator = options.pathStringSeparator || '/'; + this.childProperty = options.childProperty || 'children'; + this.searchProperty = options.searchProperty || 'name'; + } + + static _sortPathNodes(a, b) { + const undefCounter = (item) => item === undefined, + defCounter = (item) => item, + aUndefCount = a.filter(undefCounter).length, + bUndefCount = b.filter(undefCounter).length, + aDefCount = a.filter(defCounter).length, + bDefCount = b.filter(defCounter).length; + + if (aUndefCount < bUndefCount) { + return -1; + } + if (aUndefCount > bUndefCount || aDefCount < bDefCount) { + return 1; + } + if (aDefCount > bDefCount) { + return -1; + } + return 0; + } + + /** + * Searches a (multi root) node and matches nodes based on a property and a value. + * @returns {Object} - The first found node. + * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") + * @param {String} propertyName (The name of the property the match should be based on. e.g. "name") + * @param {Array} nodes [this._roots] (The objects the search should be based on.) + */ + getNodeByProperty( + propertyValue, + propertyName = this.searchProperty, + nodes = this._roots, + ) { + if (propertyValue === undefined) { + return; + } + + return this.findNode(propertyValue, propertyName, nodes); + } + + /** + * Searches a (multi root) node and matches nodes based on a property and a value. + * @returns {Array} - All found nodes. + * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") + * @param {Object} nodes [this._treeData] (The nodes the search should be based on.) + * @param {Boolean} exact [true] (If the search should be executed exact or flaw. true wouldn't match "Pet") + * @param {String} propertyName [this.searchProperty] (The name of the property the match should be based on. e.g. "name") + */ + searchNodes(propertyValue, nodes, exact, propertyName = this.searchProperty) { + const options = { + propertyName, + exact: exact !== undefined ? exact : true, + firstHitOnly: false, + }; + return this._searchNodes(propertyValue, options, nodes); + } + + /** + * Searches a (multi root) node and matches nodes based on a property and a value. + * @returns {Object} - The first found node. + * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") + * @param {String} propertyName [this.searchProperty] (The name of the property the match should be based on. e.g. "name") + * @param {Object} nodes [this._treeData] (The nodes the search should be based on.) + */ + findNode(propertyValue, propertyName = this.searchProperty, nodes) { + const options = { + propertyName, + exact: true, + firstHitOnly: true, + }; + return this._searchNodes(propertyValue, options, nodes).shift(); + } + + /** + * Searches a (multi root) node and matches nodes based on a property and a value. + * @returns {Array} - The found node(s). + * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") + * @param {Object} options (Matching options) + * @param {String} options.propertyName (The name of the property the match should be based on. e.g. "name") + * @param {Boolean} options.exact [false] (If the search should be executed exact or fuzzy. true wouldn't match "Pet") + * @param {Boolean} options.firstHitOnly [false] (If the search should only return the first found node.) + * @param {Object} nodes [this._roots] (The nodes the search should be based on.) + */ + _searchNodes(propertyValue, options, nodes = this._roots) { + const results = []; + + for (const node of nodes) { + const res = this.search(node, propertyValue, options); + if (options.firstHitOnly && res.length > 0) { + return res; + } + results.push(...res); + } + + return results; + } + + /** + * Returns the node of a given path. + * @returns {Object} The node object + * @param {String} pathLocator (The string which describes the path. e.g. "1.2.9") + * @param {Object} nodeObj [this._treeData] (The object the search should be based on.) + * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") + */ + getNodeByPathLocator( + pathLocator, + nodeObj = this._treeData, + pathLocatorSeparator = this.pathLocatorSeparator, + ) { + if (!pathLocator) { + return this._roots; + } + + const pathNodes = this.getPathNodes( + pathLocator, + nodeObj, + pathLocatorSeparator, + ); + return pathNodes && pathNodes.pop(); + } + + /** + * Returns the nodes on a given path. + * A valid path 1.2.3 should return the items [1, 2, 3] + * - path 1.2.3.3 should return [1, 2, 3, undefined] + * - path 0.1.2.3 should return [1, 2, 3] + * - path 0.1.5.3 should return [1, undefined, undefined] + * @returns {Array} The node array + * @param {String} pathLocator (The string which describes the path. e.g. "1.2.9") + * @param {Object} nodeObj [this._treeData] (The object the search should be based on.) + * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path.) + */ + getPathNodes( + pathLocator, + nodeObj = this._treeData, + pathLocatorSeparator = this.pathLocatorSeparator, + ) { + if (!pathLocator) { + return nodeObj; + } + + return Object.keys(nodeObj) + .map((key) => { + const subTree = {}; + subTree[key] = nodeObj[key]; + return this._getPathNodes(pathLocator, subTree, pathLocatorSeparator); + }) + .filter((item) => { + return item && item.length > 0; + }) + .sort(this.constructor._sortPathNodes)[0]; + } + + _getPathNodes( + pathLocator, + nodeObj = this._treeData, + pathLocatorSeparator = this.pathLocatorSeparator, + ) { + const path = pathLocator.split(pathLocatorSeparator), + nodes = this._pathToNodes(path, nodeObj, pathLocatorSeparator); + + // Filter out undefined items of the start + while (nodes.length > 0 && nodes[0] === undefined) { + nodes.shift(); + } + + return nodes; + } + + _pathToNodes(path, nodes, separator) { + let pathSegment = nodes; + return path.map((nodeKey, i) => { + // Get the nodes on the path + if (!pathSegment) { + return false; + } + const node = + pathSegment[nodeKey] ?? + pathSegment[path.slice(0, i + 1).join(separator)]; + if (node) { + pathSegment = node[this.childProperty]; + } + return node; + }); + } + + /** + * Returns a string which describes the path of a node (found by its path locator). + * @returns {String} e.g. home/computer/desktop + * @param {String} pathLocator (The string which describes the path. e.g. "1.2.9") + * @param {String} pathProperty (The property of a node on which the path should be build on. e.g "location" with node = {"location": "home", ..}) + * @param {String} pathStringSeparator [this.pathStringSeparator] (The string the path should get separated with.) + * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path segments of pathLocator.) + */ + getPathString( + pathLocator, + pathProperty = this.searchProperty, + pathStringSeparator = this.pathStringSeparator, + pathLocatorSeparator = this.pathLocatorSeparator, + ) { + const pathNodes = this.getPathNodes( + pathLocator, + this._treeData, + pathLocatorSeparator, + ); + + if (!Array.isArray(pathNodes)) { + return; + } + + return pathNodes + .filter((node) => node != null) + .map((node) => node[pathProperty]) + .join(pathStringSeparator); + } + + /** + * Returns a string which describes the path of a node (found by a node's property and value). + * @returns {String} e.g. home/computer/desktop + * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") + * @param {String} propertyName (The name of the property the match should be based on. e.g. "name") + * @param {String} pathProperty (The property of a node on which the path should be build on. e.g "location" if node = {"location": "home"}) + * @param {String} pathStringSeparator [this.pathStringSeparator] (The string the path should get separated with.) + * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") + */ + getPathStringByProperty( + propertyValue, + propertyName = this.searchProperty, + pathProperty = this.searchProperty, + pathStringSeparator = this.pathStringSeparator, + pathLocatorSeparator = this.pathLocatorSeparator, + ) { + if (propertyValue === undefined) { + return; + } + + if (propertyName === 'pathLocator') { + return this.getPathString( + propertyValue, + pathProperty, + pathStringSeparator, + pathLocatorSeparator, + ); + } + + const node = this.getNodeByProperty(propertyValue, propertyName); + + if (node) { + const path = node.pathLocator || node.path; + return this.getPathString(path, pathProperty, pathStringSeparator); + } + } + + /** + * Returns an Object or an Array representing the children of a node. + * @param {Object} node The object to return children from + * @returns {Object|Array} The node's children + */ + getChildren(node) { + if (!node || !node[this.childProperty]) { + return []; + } + return Object.values(node[this.childProperty]); + } + + /** + * Returns true if a node has children. + * @param {Object} node The object to get children from + * @returns {Boolean} True if node has children + */ + hasChildren(node) { + if (!node) { + return false; + } + const childMap = node[this.childProperty]; + if (!childMap) { + return false; + } + // eslint-disable-next-line guard-for-in + for (const key in childMap) { + return true; + } + return false; + } + + /** + * Returns the property of a Node based on a given property name. + * @param {Object} node The object to get property from + * @param {String} propertyName The name of property + * @returns {*} The value of the property + */ + getProperty(node, propertyName) { + if (!node || !propertyName) { + return; + } + return node[propertyName]; + } + + /** + * Checks if a node matches the search criteria. + * @returns {Boolean} True if node matches + * @param {node} node (The node the check should be based on.) + * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") + * @param {Object} options (Comparison options) + * @param {String} options.propertyName (The name of the property the match should be based on. e.g. "name") + * @param {Boolean} options.exact [false] (If the search should be executed exact or fuzzy. true wouldn't match "Pet") + */ + nodeConformsSearch(node, propertyValue, options) { + const property = options ? node[options.propertyName] : undefined; + + if (!property) { + // eslint-disable-next-line no-console + console.error('options.propertyName needs to be specified.'); + return; + } + + if (options.exact) { + return property === propertyValue; + } + return property.toLowerCase().indexOf(propertyValue.toLowerCase()) > -1; + } + + /** + * Searches a (multi root) node and matches nodes based on a property and a value. + * @returns {Array} The nodes found + * @param {node} node The node to search in. + * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") + * @param {Object} options (Search options) + * @param {String} options.propertyName (The name of the property the match should be based on. e.g. "name") + * @param {Boolean} options.exact [false] (If false, the propertyValue is matched fuzzy) + * @param {Array} results (The array search results get added to.) Default: [] + */ + search(node, propertyValue, options, results = []) { + const nodeConforms = this.nodeConformsSearch(node, propertyValue, options), + children = this.getChildren(node); + + if (nodeConforms) { + results.push(node); + } + + for (const child of children) { + const result = this.search(child, propertyValue, options, results); + if (!Array.isArray(result)) { + return [result]; + } + } + + return results; + } +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..640f9ba --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declaration": true, + "noEmit": false + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..754a1b9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "noEmit": true, + "module": "esnext", + "moduleResolution": "bundler", + "strict": true, + "target": "esnext", + "allowJs": true + } +} From 89de0044dcb8d053d106356d5816ab4dbb8ac748 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Thu, 14 Mar 2024 10:57:25 +0200 Subject: [PATCH 02/17] feat: add primitive types --- src/cosmoz-tree.ts | 94 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts index 508c13d..72e14ca 100644 --- a/src/cosmoz-tree.ts +++ b/src/cosmoz-tree.ts @@ -42,7 +42,7 @@ export class Tree { this.searchProperty = options.searchProperty || 'name'; } - static _sortPathNodes(a, b) { + static _sortPathNodes(a, b): number { const undefCounter = (item) => item === undefined, defCounter = (item) => item, aUndefCount = a.filter(undefCounter).length, @@ -53,12 +53,15 @@ export class Tree { if (aUndefCount < bUndefCount) { return -1; } + if (aUndefCount > bUndefCount || aDefCount < bDefCount) { return 1; } + if (aDefCount > bDefCount) { return -1; } + return 0; } @@ -70,10 +73,10 @@ export class Tree { * @param {Array} nodes [this._roots] (The objects the search should be based on.) */ getNodeByProperty( - propertyValue, - propertyName = this.searchProperty, - nodes = this._roots, - ) { + propertyValue?: string, + propertyName: string = this.searchProperty, + nodes: object = this._roots, + ): object | undefined { if (propertyValue === undefined) { return; } @@ -89,12 +92,18 @@ export class Tree { * @param {Boolean} exact [true] (If the search should be executed exact or flaw. true wouldn't match "Pet") * @param {String} propertyName [this.searchProperty] (The name of the property the match should be based on. e.g. "name") */ - searchNodes(propertyValue, nodes, exact, propertyName = this.searchProperty) { + searchNodes( + propertyValue: string, + nodes, + exact: boolean | undefined, + propertyName: string = this.searchProperty, + ): object[] { const options = { propertyName, exact: exact !== undefined ? exact : true, firstHitOnly: false, }; + return this._searchNodes(propertyValue, options, nodes); } @@ -105,12 +114,17 @@ export class Tree { * @param {String} propertyName [this.searchProperty] (The name of the property the match should be based on. e.g. "name") * @param {Object} nodes [this._treeData] (The nodes the search should be based on.) */ - findNode(propertyValue, propertyName = this.searchProperty, nodes) { + findNode( + propertyValue: string, + propertyName: string = this.searchProperty, + nodes, + ) { const options = { propertyName, exact: true, firstHitOnly: true, }; + return this._searchNodes(propertyValue, options, nodes).shift(); } @@ -124,7 +138,15 @@ export class Tree { * @param {Boolean} options.firstHitOnly [false] (If the search should only return the first found node.) * @param {Object} nodes [this._roots] (The nodes the search should be based on.) */ - _searchNodes(propertyValue, options, nodes = this._roots) { + _searchNodes( + propertyValue: string, + options: { + propertyName: string; + exact: boolean; + firstHitOnly: boolean; + }, + nodes = this._roots, + ) { const results = []; for (const node of nodes) { @@ -146,9 +168,9 @@ export class Tree { * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") */ getNodeByPathLocator( - pathLocator, + pathLocator?: string, nodeObj = this._treeData, - pathLocatorSeparator = this.pathLocatorSeparator, + pathLocatorSeparator: string = this.pathLocatorSeparator, ) { if (!pathLocator) { return this._roots; @@ -174,9 +196,9 @@ export class Tree { * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path.) */ getPathNodes( - pathLocator, + pathLocator?: string, nodeObj = this._treeData, - pathLocatorSeparator = this.pathLocatorSeparator, + pathLocatorSeparator: string = this.pathLocatorSeparator, ) { if (!pathLocator) { return nodeObj; @@ -195,9 +217,9 @@ export class Tree { } _getPathNodes( - pathLocator, + pathLocator: string, nodeObj = this._treeData, - pathLocatorSeparator = this.pathLocatorSeparator, + pathLocatorSeparator: string = this.pathLocatorSeparator, ) { const path = pathLocator.split(pathLocatorSeparator), nodes = this._pathToNodes(path, nodeObj, pathLocatorSeparator); @@ -236,10 +258,10 @@ export class Tree { * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path segments of pathLocator.) */ getPathString( - pathLocator, - pathProperty = this.searchProperty, - pathStringSeparator = this.pathStringSeparator, - pathLocatorSeparator = this.pathLocatorSeparator, + pathLocator: string, + pathProperty: string = this.searchProperty, + pathStringSeparator: string = this.pathStringSeparator, + pathLocatorSeparator: string = this.pathLocatorSeparator, ) { const pathNodes = this.getPathNodes( pathLocator, @@ -267,11 +289,11 @@ export class Tree { * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") */ getPathStringByProperty( - propertyValue, - propertyName = this.searchProperty, - pathProperty = this.searchProperty, - pathStringSeparator = this.pathStringSeparator, - pathLocatorSeparator = this.pathLocatorSeparator, + propertyValue: string, + propertyName: string = this.searchProperty, + pathProperty: string = this.searchProperty, + pathStringSeparator: string = this.pathStringSeparator, + pathLocatorSeparator: string = this.pathLocatorSeparator, ) { if (propertyValue === undefined) { return; @@ -299,7 +321,7 @@ export class Tree { * @param {Object} node The object to return children from * @returns {Object|Array} The node's children */ - getChildren(node) { + getChildren(node: object) { if (!node || !node[this.childProperty]) { return []; } @@ -311,7 +333,7 @@ export class Tree { * @param {Object} node The object to get children from * @returns {Boolean} True if node has children */ - hasChildren(node) { + hasChildren(node: object): boolean { if (!node) { return false; } @@ -332,7 +354,7 @@ export class Tree { * @param {String} propertyName The name of property * @returns {*} The value of the property */ - getProperty(node, propertyName) { + getProperty(node: object, propertyName: string) { if (!node || !propertyName) { return; } @@ -348,7 +370,14 @@ export class Tree { * @param {String} options.propertyName (The name of the property the match should be based on. e.g. "name") * @param {Boolean} options.exact [false] (If the search should be executed exact or fuzzy. true wouldn't match "Pet") */ - nodeConformsSearch(node, propertyValue, options) { + nodeConformsSearch( + node: object, + propertyValue: string, + options?: { + propertyName: string; + exact: boolean; + }, + ): boolean | undefined { const property = options ? node[options.propertyName] : undefined; if (!property) { @@ -360,6 +389,7 @@ export class Tree { if (options.exact) { return property === propertyValue; } + return property.toLowerCase().indexOf(propertyValue.toLowerCase()) > -1; } @@ -373,7 +403,15 @@ export class Tree { * @param {Boolean} options.exact [false] (If false, the propertyValue is matched fuzzy) * @param {Array} results (The array search results get added to.) Default: [] */ - search(node, propertyValue, options, results = []) { + search( + node, + propertyValue: string, + options: { + propertyName: string; + exact: string; + }, + results = [], + ) { const nodeConforms = this.nodeConformsSearch(node, propertyValue, options), children = this.getChildren(node); From 66e8bdc7099d8a7b035bcccc4002c56ce1eb4fa0 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Thu, 14 Mar 2024 12:32:34 +0200 Subject: [PATCH 03/17] feat: remove Tree files from root dir --- cosmoz-default-tree.js | 3 - cosmoz-tree.js | 373 ----------------------------------------- package.json | 4 +- 3 files changed, 3 insertions(+), 377 deletions(-) delete mode 100644 cosmoz-default-tree.js delete mode 100644 cosmoz-tree.js diff --git a/cosmoz-default-tree.js b/cosmoz-default-tree.js deleted file mode 100644 index 069080d..0000000 --- a/cosmoz-default-tree.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Tree } from './cosmoz-tree'; - -export { Tree as DefaultTree }; diff --git a/cosmoz-tree.js b/cosmoz-tree.js deleted file mode 100644 index 1fbe90c..0000000 --- a/cosmoz-tree.js +++ /dev/null @@ -1,373 +0,0 @@ -/** -Navigator through object with treelike datastructure and default settings. - -@demo demo/index.html -*/ -export class Tree { - /** - * @param {Object} treeData (The tree object.) - * @param {Object} options (Tree options.) - * @param {String} options.childProperty ["children"] (The name of the property a search should be based on. e.g. "name") - * @param {String} options.propertyName ["name"] (The name of the property a search should be based on. e.g. "name") - * @param {String} options.pathStringSeparator ["/"] (The string the path should get separated with.) - * @param {String} options.pathLocatorSeparator ["."] (The string which separates the path segments of a path locator.) - */ - constructor(treeData, options = {}) { - this._treeData = treeData; - this._roots = Object.values(treeData); - - this.pathLocatorSeparator = options.pathLocatorSeparator || '.'; - this.pathStringSeparator = options.pathStringSeparator || '/'; - this.childProperty = options.childProperty || 'children'; - this.searchProperty = options.searchProperty || 'name'; - } - - static _sortPathNodes(a, b) { - const undefCounter = (item) => item === undefined, - defCounter = (item) => item, - aUndefCount = a.filter(undefCounter).length, - bUndefCount = b.filter(undefCounter).length, - aDefCount = a.filter(defCounter).length, - bDefCount = b.filter(defCounter).length; - - if (aUndefCount < bUndefCount) { - return -1; - } - if (aUndefCount > bUndefCount || aDefCount < bDefCount) { - return 1; - } - if (aDefCount > bDefCount) { - return -1; - } - return 0; - } - - /** - * Searches a (multi root) node and matches nodes based on a property and a value. - * @returns {Object} - The first found node. - * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") - * @param {String} propertyName (The name of the property the match should be based on. e.g. "name") - * @param {Array} nodes [this._roots] (The objects the search should be based on.) - */ - getNodeByProperty( - propertyValue, - propertyName = this.searchProperty, - nodes = this._roots, - ) { - if (propertyValue === undefined) { - return; - } - - return this.findNode(propertyValue, propertyName, nodes); - } - - /** - * Searches a (multi root) node and matches nodes based on a property and a value. - * @returns {Array} - All found nodes. - * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") - * @param {Object} nodes [this._treeData] (The nodes the search should be based on.) - * @param {Boolean} exact [true] (If the search should be executed exact or flaw. true wouldn't match "Pet") - * @param {String} propertyName [this.searchProperty] (The name of the property the match should be based on. e.g. "name") - */ - searchNodes(propertyValue, nodes, exact, propertyName = this.searchProperty) { - const options = { - propertyName, - exact: exact !== undefined ? exact : true, - firstHitOnly: false, - }; - return this._searchNodes(propertyValue, options, nodes); - } - - /** - * Searches a (multi root) node and matches nodes based on a property and a value. - * @returns {Object} - The first found node. - * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") - * @param {String} propertyName [this.searchProperty] (The name of the property the match should be based on. e.g. "name") - * @param {Object} nodes [this._treeData] (The nodes the search should be based on.) - */ - findNode(propertyValue, propertyName = this.searchProperty, nodes) { - const options = { - propertyName, - exact: true, - firstHitOnly: true, - }; - return this._searchNodes(propertyValue, options, nodes).shift(); - } - - /** - * Searches a (multi root) node and matches nodes based on a property and a value. - * @returns {Array} - The found node(s). - * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") - * @param {Object} options (Matching options) - * @param {String} options.propertyName (The name of the property the match should be based on. e.g. "name") - * @param {Boolean} options.exact [false] (If the search should be executed exact or fuzzy. true wouldn't match "Pet") - * @param {Boolean} options.firstHitOnly [false] (If the search should only return the first found node.) - * @param {Object} nodes [this._roots] (The nodes the search should be based on.) - */ - _searchNodes(propertyValue, options, nodes = this._roots) { - const results = []; - - for (const node of nodes) { - const res = this.search(node, propertyValue, options); - if (options.firstHitOnly && res.length > 0) { - return res; - } - results.push(...res); - } - - return results; - } - - /** - * Returns the node of a given path. - * @returns {Object} The node object - * @param {String} pathLocator (The string which describes the path. e.g. "1.2.9") - * @param {Object} nodeObj [this._treeData] (The object the search should be based on.) - * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") - */ - getNodeByPathLocator( - pathLocator, - nodeObj = this._treeData, - pathLocatorSeparator = this.pathLocatorSeparator, - ) { - if (!pathLocator) { - return this._roots; - } - - const pathNodes = this.getPathNodes( - pathLocator, - nodeObj, - pathLocatorSeparator, - ); - return pathNodes && pathNodes.pop(); - } - - /** - * Returns the nodes on a given path. - * A valid path 1.2.3 should return the items [1, 2, 3] - * - path 1.2.3.3 should return [1, 2, 3, undefined] - * - path 0.1.2.3 should return [1, 2, 3] - * - path 0.1.5.3 should return [1, undefined, undefined] - * @returns {Array} The node array - * @param {String} pathLocator (The string which describes the path. e.g. "1.2.9") - * @param {Object} nodeObj [this._treeData] (The object the search should be based on.) - * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path.) - */ - getPathNodes( - pathLocator, - nodeObj = this._treeData, - pathLocatorSeparator = this.pathLocatorSeparator, - ) { - if (!pathLocator) { - return nodeObj; - } - - return Object.keys(nodeObj) - .map((key) => { - const subTree = {}; - subTree[key] = nodeObj[key]; - return this._getPathNodes(pathLocator, subTree, pathLocatorSeparator); - }) - .filter((item) => { - return item && item.length > 0; - }) - .sort(this.constructor._sortPathNodes)[0]; - } - - _getPathNodes( - pathLocator, - nodeObj = this._treeData, - pathLocatorSeparator = this.pathLocatorSeparator, - ) { - const path = pathLocator.split(pathLocatorSeparator), - nodes = this._pathToNodes(path, nodeObj, pathLocatorSeparator); - - // Filter out undefined items of the start - while (nodes.length > 0 && nodes[0] === undefined) { - nodes.shift(); - } - - return nodes; - } - - _pathToNodes(path, nodes, separator) { - let pathSegment = nodes; - return path.map((nodeKey, i) => { - // Get the nodes on the path - if (!pathSegment) { - return false; - } - const node = - pathSegment[nodeKey] ?? - pathSegment[path.slice(0, i + 1).join(separator)]; - if (node) { - pathSegment = node[this.childProperty]; - } - return node; - }); - } - - /** - * Returns a string which describes the path of a node (found by its path locator). - * @returns {String} e.g. home/computer/desktop - * @param {String} pathLocator (The string which describes the path. e.g. "1.2.9") - * @param {String} pathProperty (The property of a node on which the path should be build on. e.g "location" with node = {"location": "home", ..}) - * @param {String} pathStringSeparator [this.pathStringSeparator] (The string the path should get separated with.) - * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path segments of pathLocator.) - */ - getPathString( - pathLocator, - pathProperty = this.searchProperty, - pathStringSeparator = this.pathStringSeparator, - pathLocatorSeparator = this.pathLocatorSeparator, - ) { - const pathNodes = this.getPathNodes( - pathLocator, - this._treeData, - pathLocatorSeparator, - ); - - if (!Array.isArray(pathNodes)) { - return; - } - - return pathNodes - .filter((node) => node != null) - .map((node) => node[pathProperty]) - .join(pathStringSeparator); - } - - /** - * Returns a string which describes the path of a node (found by a node's property and value). - * @returns {String} e.g. home/computer/desktop - * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") - * @param {String} propertyName (The name of the property the match should be based on. e.g. "name") - * @param {String} pathProperty (The property of a node on which the path should be build on. e.g "location" if node = {"location": "home"}) - * @param {String} pathStringSeparator [this.pathStringSeparator] (The string the path should get separated with.) - * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") - */ - getPathStringByProperty( - propertyValue, - propertyName = this.searchProperty, - pathProperty = this.searchProperty, - pathStringSeparator = this.pathStringSeparator, - pathLocatorSeparator = this.pathLocatorSeparator, - ) { - if (propertyValue === undefined) { - return; - } - - if (propertyName === 'pathLocator') { - return this.getPathString( - propertyValue, - pathProperty, - pathStringSeparator, - pathLocatorSeparator, - ); - } - - const node = this.getNodeByProperty(propertyValue, propertyName); - - if (node) { - const path = node.pathLocator || node.path; - return this.getPathString(path, pathProperty, pathStringSeparator); - } - } - - /** - * Returns an Object or an Array representing the children of a node. - * @param {Object} node The object to return children from - * @returns {Object|Array} The node's children - */ - getChildren(node) { - if (!node || !node[this.childProperty]) { - return []; - } - return Object.values(node[this.childProperty]); - } - - /** - * Returns true if a node has children. - * @param {Object} node The object to get children from - * @returns {Boolean} True if node has children - */ - hasChildren(node) { - if (!node) { - return false; - } - const childMap = node[this.childProperty]; - if (!childMap) { - return false; - } - // eslint-disable-next-line guard-for-in - for (const key in childMap) { - return true; - } - return false; - } - - /** - * Returns the property of a Node based on a given property name. - * @param {Object} node The object to get property from - * @param {String} propertyName The name of property - * @returns {*} The value of the property - */ - getProperty(node, propertyName) { - if (!node || !propertyName) { - return; - } - return node[propertyName]; - } - - /** - * Checks if a node matches the search criteria. - * @returns {Boolean} True if node matches - * @param {node} node (The node the check should be based on.) - * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") - * @param {Object} options (Comparison options) - * @param {String} options.propertyName (The name of the property the match should be based on. e.g. "name") - * @param {Boolean} options.exact [false] (If the search should be executed exact or fuzzy. true wouldn't match "Pet") - */ - nodeConformsSearch(node, propertyValue, options) { - const property = options ? node[options.propertyName] : undefined; - - if (!property) { - // eslint-disable-next-line no-console - console.error('options.propertyName needs to be specified.'); - return; - } - - if (options.exact) { - return property === propertyValue; - } - return property.toLowerCase().indexOf(propertyValue.toLowerCase()) > -1; - } - - /** - * Searches a (multi root) node and matches nodes based on a property and a value. - * @returns {Array} The nodes found - * @param {node} node The node to search in. - * @param {String} propertyValue (The value of the property the match should be based on. e.g. "Peter") - * @param {Object} options (Search options) - * @param {String} options.propertyName (The name of the property the match should be based on. e.g. "name") - * @param {Boolean} options.exact [false] (If false, the propertyValue is matched fuzzy) - * @param {Array} results (The array search results get added to.) Default: [] - */ - search(node, propertyValue, options, results = []) { - const nodeConforms = this.nodeConformsSearch(node, propertyValue, options), - children = this.getChildren(node); - - if (nodeConforms) { - results.push(node); - } - - for (const child of children) { - const result = this.search(child, propertyValue, options, results); - if (!Array.isArray(result)) { - return [result]; - } - } - - return results; - } -} diff --git a/package.json b/package.json index 5a512f7..f2c8d4b 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,9 @@ }, "exports": { "./cosmoz-tree": "./dist/cosmoz-tree.js", - "./cosmoz-default-tree": "./dist/cosmoz-default-tree.js" + "./cosmoz-tree.js": "./dist/cosmoz-tree.js", + "./cosmoz-default-tree": "./dist/cosmoz-default-tree.js", + "./cosmoz-default-tree.js": "./dist/cosmoz-default-tree.js" }, "commitlint": { "extends": [ From b2d28f271d7215a8bb3915ee63e72e09f5a6d3e6 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Thu, 14 Mar 2024 13:21:22 +0200 Subject: [PATCH 04/17] test: update Tree import --- test/cosmoz-tree.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cosmoz-tree.test.js b/test/cosmoz-tree.test.js index 3918a72..1ba380d 100644 --- a/test/cosmoz-tree.test.js +++ b/test/cosmoz-tree.test.js @@ -1,5 +1,5 @@ import { assert } from '@open-wc/testing'; -import { Tree } from '../cosmoz-tree.js'; +import { Tree } from '../src/cosmoz-tree'; const treeBaseUrl = '/test/data', basicTreeUrl = `${treeBaseUrl}/basicTree.json`, From 3006f0b2022533f0a08bcec0496c3d7b922c1300 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Thu, 14 Mar 2024 13:25:04 +0200 Subject: [PATCH 05/17] feat: rename TreeOptions to Options --- src/cosmoz-tree.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts index 72e14ca..c473c7c 100644 --- a/src/cosmoz-tree.ts +++ b/src/cosmoz-tree.ts @@ -4,7 +4,7 @@ Navigator through object with treelike datastructure and default settings. @demo demo/index.html */ -export type TreeOptions = { +export type Options = { childProperty?: string; searchProperty?: string; pathLocatorSeparator?: string; @@ -32,7 +32,7 @@ export class Tree { * @param {String} options.pathStringSeparator ["/"] (The string the path should get separated with.) * @param {String} options.pathLocatorSeparator ["."] (The string which separates the path segments of a path locator.) */ - constructor(treeData: object, options: TreeOptions = {}) { + constructor(treeData: object, options: Options = {}) { this._treeData = treeData; this._roots = Object.values(treeData); From bf0cd5a8f5203463e952d4cb1a687595196561b6 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Thu, 14 Mar 2024 13:42:00 +0200 Subject: [PATCH 06/17] fix: reset lint script to cosmoz-router --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2c8d4b..441af4c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dist/*" ], "scripts": { - "lint": "tsc && eslint --cache --ext .js,.html .", + "lint": "tsc && eslint --cache .", "build": "tsc -p tsconfig.build.json", "start": "wds", "test": "wtr --coverage", From 1f7c6b93cc709835bccc340d4feda2a61837fe11 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Fri, 15 Mar 2024 09:50:05 +0200 Subject: [PATCH 07/17] feat: add Node interface --- src/cosmoz-tree.ts | 59 ++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts index c473c7c..39085d5 100644 --- a/src/cosmoz-tree.ts +++ b/src/cosmoz-tree.ts @@ -11,10 +11,14 @@ export type Options = { pathStringSeparator?: string; }; +export interface Node { + pathLocator: string; +} + export class Tree { _treeData: object; - _roots: object; + _roots: Node[]; childProperty: string; @@ -42,7 +46,7 @@ export class Tree { this.searchProperty = options.searchProperty || 'name'; } - static _sortPathNodes(a, b): number { + static _sortPathNodes(a: Node[], b: Node[]): number { const undefCounter = (item) => item === undefined, defCounter = (item) => item, aUndefCount = a.filter(undefCounter).length, @@ -75,8 +79,8 @@ export class Tree { getNodeByProperty( propertyValue?: string, propertyName: string = this.searchProperty, - nodes: object = this._roots, - ): object | undefined { + nodes: Node[] = this._roots, + ): Node | undefined { if (propertyValue === undefined) { return; } @@ -94,10 +98,10 @@ export class Tree { */ searchNodes( propertyValue: string, - nodes, + nodes: Node[], exact: boolean | undefined, propertyName: string = this.searchProperty, - ): object[] { + ): Node[] { const options = { propertyName, exact: exact !== undefined ? exact : true, @@ -117,8 +121,8 @@ export class Tree { findNode( propertyValue: string, propertyName: string = this.searchProperty, - nodes, - ) { + nodes: Node[], + ): Node | undefined { const options = { propertyName, exact: true, @@ -207,7 +211,8 @@ export class Tree { return Object.keys(nodeObj) .map((key) => { const subTree = {}; - subTree[key] = nodeObj[key]; + subTree[key as keyof typeof subTree] = + nodeObj[key as keyof typeof nodeObj]; return this._getPathNodes(pathLocator, subTree, pathLocatorSeparator); }) .filter((item) => { @@ -232,7 +237,7 @@ export class Tree { return nodes; } - _pathToNodes(path, nodes, separator) { + _pathToNodes(path, nodes: Node[], separator: string) { let pathSegment = nodes; return path.map((nodeKey, i) => { // Get the nodes on the path @@ -243,7 +248,7 @@ export class Tree { pathSegment[nodeKey] ?? pathSegment[path.slice(0, i + 1).join(separator)]; if (node) { - pathSegment = node[this.childProperty]; + pathSegment = node[this.childProperty as keyof typeof node]; } return node; }); @@ -321,11 +326,12 @@ export class Tree { * @param {Object} node The object to return children from * @returns {Object|Array} The node's children */ - getChildren(node: object) { - if (!node || !node[this.childProperty]) { + getChildren(node: Node): Node[] { + if (!node || !node[this.childProperty as keyof typeof node]) { return []; } - return Object.values(node[this.childProperty]); + + return Object.values(node[this.childProperty as keyof typeof node]); } /** @@ -333,11 +339,11 @@ export class Tree { * @param {Object} node The object to get children from * @returns {Boolean} True if node has children */ - hasChildren(node: object): boolean { + hasChildren(node: Node): boolean { if (!node) { return false; } - const childMap = node[this.childProperty]; + const childMap = node[this.childProperty as keyof typeof node]; if (!childMap) { return false; } @@ -354,11 +360,12 @@ export class Tree { * @param {String} propertyName The name of property * @returns {*} The value of the property */ - getProperty(node: object, propertyName: string) { + getProperty(node: Node, propertyName: string) { if (!node || !propertyName) { return; } - return node[propertyName]; + + return node[propertyName as keyof typeof node]; } /** @@ -371,14 +378,16 @@ export class Tree { * @param {Boolean} options.exact [false] (If the search should be executed exact or fuzzy. true wouldn't match "Pet") */ nodeConformsSearch( - node: object, + node: Node, propertyValue: string, options?: { propertyName: string; exact: boolean; }, ): boolean | undefined { - const property = options ? node[options.propertyName] : undefined; + const property = options + ? node[options.propertyName as keyof typeof node] + : undefined; if (!property) { // eslint-disable-next-line no-console @@ -386,7 +395,7 @@ export class Tree { return; } - if (options.exact) { + if (options?.exact) { return property === propertyValue; } @@ -404,14 +413,14 @@ export class Tree { * @param {Array} results (The array search results get added to.) Default: [] */ search( - node, + node: Node, propertyValue: string, options: { propertyName: string; - exact: string; + exact: boolean; }, - results = [], - ) { + results: Node[] = [], + ): Node[] { const nodeConforms = this.nodeConformsSearch(node, propertyValue, options), children = this.getChildren(node); From bc174f9f8addf6c3e37d9be77f8482949fe31ac3 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Fri, 15 Mar 2024 09:58:46 +0200 Subject: [PATCH 08/17] feat: add @types/mocha to prevent linting errors --- package-lock.json | 7 +++++++ package.json | 1 + 2 files changed, 8 insertions(+) diff --git a/package-lock.json b/package-lock.json index 0a2b139..e1fb35e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@open-wc/testing": "^4.0.0", "@semantic-release/changelog": "^6.0.0", "@semantic-release/git": "^10.0.0", + "@types/mocha": "^10.0.3", "husky": "^9.0.0", "lit-html": "^3.0.2", "semantic-release": "^23.0.0" @@ -2738,6 +2739,12 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "node_modules/@types/mocha": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", + "dev": true + }, "node_modules/@types/node": { "version": "20.11.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", diff --git a/package.json b/package.json index 441af4c..d9704ef 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@semantic-release/changelog": "^6.0.0", "@semantic-release/git": "^10.0.0", "husky": "^9.0.0", + "@types/mocha": "^10.0.3", "lit-html": "^3.0.2", "semantic-release": "^23.0.0" } From 42259111cf562fd0dfda961e91cee40869641cb4 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Fri, 15 Mar 2024 11:12:40 +0200 Subject: [PATCH 09/17] feat: move _sortPathNodes to a separate function Further work on converting Tree class to typescript (with the help from Iulian) --- src/cosmoz-tree.ts | 79 ++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts index 39085d5..21decff 100644 --- a/src/cosmoz-tree.ts +++ b/src/cosmoz-tree.ts @@ -4,19 +4,46 @@ Navigator through object with treelike datastructure and default settings. @demo demo/index.html */ -export type Options = { +export interface Options { childProperty?: string; searchProperty?: string; pathLocatorSeparator?: string; pathStringSeparator?: string; -}; +} export interface Node { pathLocator: string; + path?: string; + children?: Record; } +export type TreeData = Record; + +const _sortPathNodes = (a: Node[], b: Node[]): number => { + const undefCounter = (item: Type) => item === undefined, + defCounter = (item: Type) => item, + aUndefCount = a.filter(undefCounter).length, + bUndefCount = b.filter(undefCounter).length, + aDefCount = a.filter(defCounter).length, + bDefCount = b.filter(defCounter).length; + + if (aUndefCount < bUndefCount) { + return -1; + } + + if (aUndefCount > bUndefCount || aDefCount < bDefCount) { + return 1; + } + + if (aDefCount > bDefCount) { + return -1; + } + + return 0; +}; + export class Tree { - _treeData: object; + _treeData: TreeData; _roots: Node[]; @@ -36,7 +63,7 @@ export class Tree { * @param {String} options.pathStringSeparator ["/"] (The string the path should get separated with.) * @param {String} options.pathLocatorSeparator ["."] (The string which separates the path segments of a path locator.) */ - constructor(treeData: object, options: Options = {}) { + constructor(treeData: TreeData, options: Options = {}) { this._treeData = treeData; this._roots = Object.values(treeData); @@ -46,29 +73,6 @@ export class Tree { this.searchProperty = options.searchProperty || 'name'; } - static _sortPathNodes(a: Node[], b: Node[]): number { - const undefCounter = (item) => item === undefined, - defCounter = (item) => item, - aUndefCount = a.filter(undefCounter).length, - bUndefCount = b.filter(undefCounter).length, - aDefCount = a.filter(defCounter).length, - bDefCount = b.filter(defCounter).length; - - if (aUndefCount < bUndefCount) { - return -1; - } - - if (aUndefCount > bUndefCount || aDefCount < bDefCount) { - return 1; - } - - if (aDefCount > bDefCount) { - return -1; - } - - return 0; - } - /** * Searches a (multi root) node and matches nodes based on a property and a value. * @returns {Object} - The first found node. @@ -201,7 +205,7 @@ export class Tree { */ getPathNodes( pathLocator?: string, - nodeObj = this._treeData, + nodeObj: TreeData = this._treeData, pathLocatorSeparator: string = this.pathLocatorSeparator, ) { if (!pathLocator) { @@ -210,15 +214,14 @@ export class Tree { return Object.keys(nodeObj) .map((key) => { - const subTree = {}; - subTree[key as keyof typeof subTree] = - nodeObj[key as keyof typeof nodeObj]; + const subTree: TreeData = {}; + subTree[key] = nodeObj[key]; return this._getPathNodes(pathLocator, subTree, pathLocatorSeparator); }) .filter((item) => { return item && item.length > 0; }) - .sort(this.constructor._sortPathNodes)[0]; + .sort(_sortPathNodes)[0]; } _getPathNodes( @@ -237,16 +240,18 @@ export class Tree { return nodes; } - _pathToNodes(path, nodes: Node[], separator: string) { + _pathToNodes(path: string[], nodes: Node[], separator: string) { let pathSegment = nodes; - return path.map((nodeKey, i) => { + return path.map((nodeKey: NodeType, i: number) => { // Get the nodes on the path if (!pathSegment) { return false; } const node = - pathSegment[nodeKey] ?? - pathSegment[path.slice(0, i + 1).join(separator)]; + pathSegment[nodeKey as keyof typeof pathSegment] ?? + pathSegment[ + path.slice(0, i + 1).join(separator) as keyof typeof pathSegment + ]; if (node) { pathSegment = node[this.childProperty as keyof typeof node]; } @@ -263,7 +268,7 @@ export class Tree { * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path segments of pathLocator.) */ getPathString( - pathLocator: string, + pathLocator?: string, pathProperty: string = this.searchProperty, pathStringSeparator: string = this.pathStringSeparator, pathLocatorSeparator: string = this.pathLocatorSeparator, From 35c5df82427c9c05bbe431dc0e1d173d456fd3cc Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Fri, 15 Mar 2024 12:39:11 +0200 Subject: [PATCH 10/17] feat: further work on converting typescript --- src/cosmoz-tree.ts | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts index 21decff..ec7cfab 100644 --- a/src/cosmoz-tree.ts +++ b/src/cosmoz-tree.ts @@ -189,7 +189,7 @@ export class Tree { nodeObj, pathLocatorSeparator, ); - return pathNodes && pathNodes.pop(); + return pathNodes && Array.isArray(pathNodes) && pathNodes.pop(); } /** @@ -216,7 +216,13 @@ export class Tree { .map((key) => { const subTree: TreeData = {}; subTree[key] = nodeObj[key]; - return this._getPathNodes(pathLocator, subTree, pathLocatorSeparator); + const pathNodes = this._getPathNodes( + pathLocator, + subTree, + pathLocatorSeparator, + ); + + return pathNodes; }) .filter((item) => { return item && item.length > 0; @@ -226,7 +232,7 @@ export class Tree { _getPathNodes( pathLocator: string, - nodeObj = this._treeData, + nodeObj: TreeData = this._treeData, pathLocatorSeparator: string = this.pathLocatorSeparator, ) { const path = pathLocator.split(pathLocatorSeparator), @@ -240,23 +246,22 @@ export class Tree { return nodes; } - _pathToNodes(path: string[], nodes: Node[], separator: string) { + _pathToNodes(path: string[], nodes: TreeData, separator: string) { let pathSegment = nodes; - return path.map((nodeKey: NodeType, i: number) => { + return path.map((nodeKey: string, i: number) => { // Get the nodes on the path if (!pathSegment) { return false; } const node = - pathSegment[nodeKey as keyof typeof pathSegment] ?? - pathSegment[ - path.slice(0, i + 1).join(separator) as keyof typeof pathSegment - ]; + pathSegment[nodeKey] ?? + pathSegment[path.slice(0, i + 1).join(separator)]; if (node) { - pathSegment = node[this.childProperty as keyof typeof node]; + pathSegment = node[this.childProperty as 'children']!; } return node; - }); + // TODO: fix next line + }) as unknown as Node[]; } /** @@ -285,7 +290,7 @@ export class Tree { return pathNodes .filter((node) => node != null) - .map((node) => node[pathProperty]) + .map((node) => node[pathProperty as 'pathLocator']) .join(pathStringSeparator); } @@ -331,12 +336,12 @@ export class Tree { * @param {Object} node The object to return children from * @returns {Object|Array} The node's children */ - getChildren(node: Node): Node[] { - if (!node || !node[this.childProperty as keyof typeof node]) { + getChildren(node: Node) { + if (!node || !node[this.childProperty as 'children']) { return []; } - return Object.values(node[this.childProperty as keyof typeof node]); + return Object.values(node[this.childProperty as 'children']!); } /** @@ -348,7 +353,7 @@ export class Tree { if (!node) { return false; } - const childMap = node[this.childProperty as keyof typeof node]; + const childMap = node[this.childProperty as 'children']; if (!childMap) { return false; } @@ -389,10 +394,10 @@ export class Tree { propertyName: string; exact: boolean; }, - ): boolean | undefined { - const property = options + ) { + const property = (options ? node[options.propertyName as keyof typeof node] - : undefined; + : undefined) as unknown as string; if (!property) { // eslint-disable-next-line no-console From 9415539892ce8e285416d755fae20b91085a6adf Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Fri, 15 Mar 2024 12:41:56 +0200 Subject: [PATCH 11/17] fix: update Tree import in examples script --- examples/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/script.js b/examples/script.js index 3343300..2a0c8c1 100644 --- a/examples/script.js +++ b/examples/script.js @@ -1,4 +1,4 @@ -import { Tree } from '../cosmoz-tree.js'; +import { Tree } from '../src/cosmoz-tree'; fetch('tree.json').then(async (response) => { const json = await response.json(), From e2910f2c5a019f2e6eca11a67273671264987776 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Mon, 18 Mar 2024 11:52:46 +0200 Subject: [PATCH 12/17] feat: update TODO comment --- src/cosmoz-tree.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts index ec7cfab..e0e9a60 100644 --- a/src/cosmoz-tree.ts +++ b/src/cosmoz-tree.ts @@ -260,8 +260,7 @@ export class Tree { pathSegment = node[this.childProperty as 'children']!; } return node; - // TODO: fix next line - }) as unknown as Node[]; + }) as unknown as Node[]; // TODO: update the code to not use `as unknown as Node[]` } /** From d85d8a67755be988c358716f0c6a8fa0431dacd6 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Mon, 18 Mar 2024 13:14:06 +0200 Subject: [PATCH 13/17] feat: wip - convert tests to typescript --- src/cosmoz-tree.ts | 29 +++++++++------- ...osmoz-tree.test.js => cosmoz-tree.test.ts} | 33 +++++++++---------- 2 files changed, 33 insertions(+), 29 deletions(-) rename test/{cosmoz-tree.test.js => cosmoz-tree.test.ts} (95%) diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts index e0e9a60..11a0705 100644 --- a/src/cosmoz-tree.ts +++ b/src/cosmoz-tree.ts @@ -14,6 +14,7 @@ export interface Options { export interface Node { pathLocator: string; path?: string; + name?: string; children?: Record; } @@ -84,7 +85,7 @@ export class Tree { propertyValue?: string, propertyName: string = this.searchProperty, nodes: Node[] = this._roots, - ): Node | undefined { + ) { if (propertyValue === undefined) { return; } @@ -101,11 +102,11 @@ export class Tree { * @param {String} propertyName [this.searchProperty] (The name of the property the match should be based on. e.g. "name") */ searchNodes( - propertyValue: string, - nodes: Node[], - exact: boolean | undefined, + propertyValue?: string, + nodes?: Node[], + exact?: boolean, propertyName: string = this.searchProperty, - ): Node[] { + ) { const options = { propertyName, exact: exact !== undefined ? exact : true, @@ -125,8 +126,8 @@ export class Tree { findNode( propertyValue: string, propertyName: string = this.searchProperty, - nodes: Node[], - ): Node | undefined { + nodes?: Node[], + ) { const options = { propertyName, exact: true, @@ -147,7 +148,7 @@ export class Tree { * @param {Object} nodes [this._roots] (The nodes the search should be based on.) */ _searchNodes( - propertyValue: string, + propertyValue: string | undefined, options: { propertyName: string; exact: boolean; @@ -348,7 +349,7 @@ export class Tree { * @param {Object} node The object to get children from * @returns {Boolean} True if node has children */ - hasChildren(node: Node): boolean { + hasChildren(node: Node) { if (!node) { return false; } @@ -369,7 +370,7 @@ export class Tree { * @param {String} propertyName The name of property * @returns {*} The value of the property */ - getProperty(node: Node, propertyName: string) { + getProperty(node: Node | null, propertyName?: string) { if (!node || !propertyName) { return; } @@ -388,7 +389,7 @@ export class Tree { */ nodeConformsSearch( node: Node, - propertyValue: string, + propertyValue: string | undefined, options?: { propertyName: string; exact: boolean; @@ -408,6 +409,10 @@ export class Tree { return property === propertyValue; } + if (propertyValue === undefined) { + return false; + } + return property.toLowerCase().indexOf(propertyValue.toLowerCase()) > -1; } @@ -423,7 +428,7 @@ export class Tree { */ search( node: Node, - propertyValue: string, + propertyValue: string | undefined, options: { propertyName: string; exact: boolean; diff --git a/test/cosmoz-tree.test.js b/test/cosmoz-tree.test.ts similarity index 95% rename from test/cosmoz-tree.test.js rename to test/cosmoz-tree.test.ts index 1ba380d..c355d0d 100644 --- a/test/cosmoz-tree.test.js +++ b/test/cosmoz-tree.test.ts @@ -1,18 +1,19 @@ import { assert } from '@open-wc/testing'; -import { Tree } from '../src/cosmoz-tree'; +import { Tree, TreeData, Node } from '../src/cosmoz-tree'; const treeBaseUrl = '/test/data', basicTreeUrl = `${treeBaseUrl}/basicTree.json`, basicTreePlUrl = `${treeBaseUrl}/basicTreePL.json`, multiRootTreeUrl = `${treeBaseUrl}/multiRootTree.json`, missingAncestorTreeUrl = `${treeBaseUrl}/missingAncestorTree.json`, - treeFromJsonUrl = async (url, options = {}) => { + treeFromJsonUrl = async (url: string, options = {}) => { const json = await fetch(url).then((r) => r.json()); + return new Tree(json, options); }; suite('basic', () => { - let basicTree; + let basicTree: Tree; suiteSetup(async () => { basicTree = await treeFromJsonUrl(basicTreeUrl); @@ -39,9 +40,9 @@ suite('basic', () => { '2b547550-b874-4228-9395-a4fb00f31245', 'id', ), - node5 = basicTree.getNodeByProperty(node4.name), + node5 = basicTree.getNodeByProperty(node4?.name), node6 = basicTree.getNodeByProperty( - node2.pathLocator, + node2?.pathLocator, 'pathLocator', basicTree._roots, ); @@ -66,7 +67,6 @@ suite('basic', () => { undefined, undefined, 'pathLocator', - root, )[0], node3 = basicTree.searchNodes( '2b547550-b874-4228-9395-a4fb00f31245', @@ -100,21 +100,22 @@ suite('basic', () => { }); test('findNode', () => { - const root = basicTree.searchNodes( + const root: Node = basicTree.searchNodes( '1', undefined, undefined, 'pathLocator', )[0], - node = basicTree.findNode('1'), + node = basicTree.findNode('Root'), node2 = basicTree.findNode('2b547550-b874-4228-9395-a4fb00f31245', 'id'); - assert.deepEqual(root[0], node); + assert.deepEqual(root, node); assert.isOk(node2); }); test('getNodeByPathLocator', () => { const node3 = basicTree.getNodeByPathLocator('1.2.3'); + assert.isOk(node3); assert.equal(basicTree.getNodeByPathLocator(), basicTree._roots); }); @@ -191,7 +192,7 @@ suite('basic', () => { }); suite('basicPL', () => { - let basicTree; + let basicTree: Tree; suiteSetup(async () => { basicTree = await treeFromJsonUrl(basicTreePlUrl); @@ -217,7 +218,7 @@ suite('basicPL', () => { }); suite('multiRoot', () => { - let multiRootTree; + let multiRootTree: Tree; suiteSetup(async () => { multiRootTree = await treeFromJsonUrl(multiRootTreeUrl); @@ -273,7 +274,7 @@ suite('multiRoot', () => { }); suite('missingAncestor', () => { - let missingAncestorTree; + let missingAncestorTree: Tree; suiteSetup(async () => { missingAncestorTree = await treeFromJsonUrl(missingAncestorTreeUrl); @@ -394,7 +395,7 @@ suite('missingAncestor', () => { }); suite('basicWithOptions', () => { - let basicTreeWithOptions; + let basicTreeWithOptions: Tree; suiteSetup(async () => { basicTreeWithOptions = await treeFromJsonUrl(basicTreeUrl, { @@ -416,15 +417,13 @@ suite('basicWithOptions', () => { test('nodeConformsSearch returns undefined when theres no property', () => { const rootNode = basicTreeWithOptions.getNodeByPathLocator('1'); - assert.equal( + assert.isUndefined( basicTreeWithOptions.nodeConformsSearch(rootNode, 'Root'), - undefined, ); - assert.equal( + assert.isUndefined( basicTreeWithOptions.nodeConformsSearch(rootNode, 'Root', { propertyName: 'noProperty', }), - undefined, ); }); }); From 3a8b73dc82725fdb629a3d9d9bfbb8b858cddaa8 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Mon, 18 Mar 2024 15:33:10 +0200 Subject: [PATCH 14/17] feat: update getPathNodes to return only Node[] --- src/cosmoz-tree.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts index 11a0705..3af5e04 100644 --- a/src/cosmoz-tree.ts +++ b/src/cosmoz-tree.ts @@ -208,9 +208,9 @@ export class Tree { pathLocator?: string, nodeObj: TreeData = this._treeData, pathLocatorSeparator: string = this.pathLocatorSeparator, - ) { + ): Node[] { if (!pathLocator) { - return nodeObj; + return this.getPathNodes('Root'); } return Object.keys(nodeObj) From 531824feca635833b36802e554bf1190dd7a00a3 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Tue, 19 Mar 2024 10:37:28 +0200 Subject: [PATCH 15/17] test: completed tests (with help from Iulian) --- src/cosmoz-tree.ts | 59 +++++++++++++++++++++++++++++++--------- test/cosmoz-tree.test.ts | 50 +++++++++++++++++----------------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/src/cosmoz-tree.ts b/src/cosmoz-tree.ts index 3af5e04..ec5b997 100644 --- a/src/cosmoz-tree.ts +++ b/src/cosmoz-tree.ts @@ -12,6 +12,7 @@ export interface Options { } export interface Node { + id: string; pathLocator: string; path?: string; name?: string; @@ -20,7 +21,7 @@ export interface Node { export type TreeData = Record; -const _sortPathNodes = (a: Node[], b: Node[]): number => { +const _sortPathNodes = (a: (Node | undefined)[], b: (Node | undefined)[]) => { const undefCounter = (item: Type) => item === undefined, defCounter = (item: Type) => item, aUndefCount = a.filter(undefCounter).length, @@ -176,11 +177,26 @@ export class Tree { * @param {Object} nodeObj [this._treeData] (The object the search should be based on.) * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") */ + getNodeByPathLocator( + pathLocator: undefined, + nodeObj?: TreeData, + pathLocatorSeparator?: string, + ): Node[]; + getNodeByPathLocator( + pathLocator: string, + nodeObj?: TreeData, + pathLocatorSeparator?: string, + ): Node | undefined; + getNodeByPathLocator( + pathLocator?: string, + nodeObj?: TreeData, + pathLocatorSeparator?: string, + ): Node[] | Node | undefined; getNodeByPathLocator( pathLocator?: string, - nodeObj = this._treeData, + nodeObj: TreeData = this._treeData, pathLocatorSeparator: string = this.pathLocatorSeparator, - ) { + ): Node[] | Node | undefined { if (!pathLocator) { return this._roots; } @@ -190,7 +206,8 @@ export class Tree { nodeObj, pathLocatorSeparator, ); - return pathNodes && Array.isArray(pathNodes) && pathNodes.pop(); + + return pathNodes.pop(); } /** @@ -204,13 +221,28 @@ export class Tree { * @param {Object} nodeObj [this._treeData] (The object the search should be based on.) * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path.) */ + getPathNodes( + pathLocator: undefined, + nodeObj?: TreeData, + pathLocatorSeparator?: string, + ): TreeData; + getPathNodes( + pathLocator: string, + nodeObj?: TreeData, + pathLocatorSeparator?: string, + ): (Node | undefined)[]; + getPathNodes( + pathLocator?: string, + nodeObj?: TreeData, + pathLocatorSeparator?: string, + ): TreeData | (Node | undefined)[]; getPathNodes( pathLocator?: string, nodeObj: TreeData = this._treeData, pathLocatorSeparator: string = this.pathLocatorSeparator, - ): Node[] { + ) { if (!pathLocator) { - return this.getPathNodes('Root'); + return nodeObj; } return Object.keys(nodeObj) @@ -252,8 +284,9 @@ export class Tree { return path.map((nodeKey: string, i: number) => { // Get the nodes on the path if (!pathSegment) { - return false; + return undefined; } + const node = pathSegment[nodeKey] ?? pathSegment[path.slice(0, i + 1).join(separator)]; @@ -261,7 +294,7 @@ export class Tree { pathSegment = node[this.childProperty as 'children']!; } return node; - }) as unknown as Node[]; // TODO: update the code to not use `as unknown as Node[]` + }); } /** @@ -290,7 +323,7 @@ export class Tree { return pathNodes .filter((node) => node != null) - .map((node) => node[pathProperty as 'pathLocator']) + .map((node) => node![pathProperty as 'pathLocator']) .join(pathStringSeparator); } @@ -304,7 +337,7 @@ export class Tree { * @param {String} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") */ getPathStringByProperty( - propertyValue: string, + propertyValue?: string, propertyName: string = this.searchProperty, pathProperty: string = this.searchProperty, pathStringSeparator: string = this.pathStringSeparator, @@ -349,7 +382,7 @@ export class Tree { * @param {Object} node The object to get children from * @returns {Boolean} True if node has children */ - hasChildren(node: Node) { + hasChildren(node?: Node) { if (!node) { return false; } @@ -370,7 +403,7 @@ export class Tree { * @param {String} propertyName The name of property * @returns {*} The value of the property */ - getProperty(node: Node | null, propertyName?: string) { + getProperty(node?: Node | null, propertyName?: string) { if (!node || !propertyName) { return; } @@ -392,7 +425,7 @@ export class Tree { propertyValue: string | undefined, options?: { propertyName: string; - exact: boolean; + exact?: boolean; }, ) { const property = (options diff --git a/test/cosmoz-tree.test.ts b/test/cosmoz-tree.test.ts index c355d0d..1c7793c 100644 --- a/test/cosmoz-tree.test.ts +++ b/test/cosmoz-tree.test.ts @@ -1,5 +1,5 @@ import { assert } from '@open-wc/testing'; -import { Tree, TreeData, Node } from '../src/cosmoz-tree'; +import { Tree, Node } from '../src/cosmoz-tree'; const treeBaseUrl = '/test/data', basicTreeUrl = `${treeBaseUrl}/basicTree.json`, @@ -150,12 +150,12 @@ suite('basic', () => { test('getPathString', () => { const node3 = basicTree.getNodeByPathLocator('1.2.3'), - pathString = basicTree.getPathString(node3.pathLocator); - assert.equal(pathString.split('/').pop(), node3.name); + pathString = basicTree.getPathString(node3?.pathLocator); + assert.equal(pathString?.split('/').pop(), node3?.name); assert.isUndefined(basicTree.getPathString()); const node301 = basicTree.getNodeByPathLocator('1.2.3.301'), node301PathString = basicTree.getPathString( - node301.pathLocator, + node301?.pathLocator, 'name', '/', ); @@ -164,8 +164,8 @@ suite('basic', () => { test('getPathStringByProperty', () => { const node3 = basicTree.getNodeByPathLocator('1.2.3'), - pathString = basicTree.getPathStringByProperty(node3.id, 'id'); - assert.equal(pathString.split('/').pop(), node3.name); + pathString = basicTree.getPathStringByProperty(node3!.id, 'id'); + assert.equal(pathString!.split('/').pop(), node3!.name); assert.isUndefined(basicTree.getPathStringByProperty()); assert.equal( basicTree.getPathStringByProperty('1.2.3', 'pathLocator'), @@ -212,8 +212,8 @@ suite('basicPL', () => { node2 = basicTree.findNode('1.2.3', 'pathLocator'), node3 = basicTree.findNode('3a7654f1-e3e6-49c7-b6a8-a4fb00f31245', 'id'); assert.equal(node1.id, '3a7654f1-e3e6-49c7-b6a8-a4fb00f31245'); - assert.equal(node2.id, '3a7654f1-e3e6-49c7-b6a8-a4fb00f31245'); - assert.equal(node3.pathLocator, '1.2.3'); + assert.equal(node2!.id, '3a7654f1-e3e6-49c7-b6a8-a4fb00f31245'); + assert.equal(node3!.pathLocator, '1.2.3'); }); }); @@ -312,21 +312,21 @@ suite('missingAncestor', () => { '865065da-f44c-472e-a8df-a4fb00f3124b', 'id', ), - node301Path = missingAncestorTree.getPathNodes(node301.pathLocator), - node401Path = missingAncestorTree.getPathNodes(node401.pathLocator), + node301Path = missingAncestorTree.getPathNodes(node301!.pathLocator), + node401Path = missingAncestorTree.getPathNodes(node401!.pathLocator), n_1_2_3_301 = missingAncestorTree.getPathNodes('1.2.3.301'), n_1_2_301 = missingAncestorTree.getPathNodes('1.2.301'), n_1_2 = missingAncestorTree.getPathNodes('1.2'), n_1_2_2 = missingAncestorTree.getPathNodes('1.2.2'), n_1_4_4 = missingAncestorTree.getPathNodes('1.4.4'), n_1_2_7_8 = missingAncestorTree.getPathNodes('1.2.7.8'), - n_1_2_7_301 = missingAncestorTree.getPathNodes('1.2.7.301'), + //n_1_2_7_301 = missingAncestorTree.getPathNodes('1.2.7.301'), n_2_301 = missingAncestorTree.getPathNodes('2.301'), n_601_301 = missingAncestorTree.getPathNodes('601.301.10.11'); // since 301 is its own root, it should be the only node returned assert.equal(node301Path.length, 1); - assert.equal(node301.name, 'Node301'); + assert.equal(node301!.name, 'Node301'); assert.deepEqual(node301Path[0], node301); // node401 is directly under 301, should only be two nodes returned @@ -335,37 +335,37 @@ suite('missingAncestor', () => { assert.equal(node401Path[1], node401); // 1.2.3.301 - assert.equal(n_1_2_3_301[0].name, 'Node301'); + assert.equal(n_1_2_3_301[0]!.name, 'Node301'); // 1.2.301 // make sure we don't get stuck in the first root - assert.equal(n_1_2_301[0].name, 'Node301'); + assert.equal(n_1_2_301[0]!.name, 'Node301'); assert.equal(n_1_2_301.length, 1); // 1.2 assert.equal(n_1_2.length, 2); - assert.equal(n_1_2[0].name, 'Node1'); - assert.equal(n_1_2[1].name, 'Node2'); + assert.equal(n_1_2[0]!.name, 'Node1'); + assert.equal(n_1_2[1]!.name, 'Node2'); // 1.2.2 // make sure we don't pick the same node twice at the end assert.equal(n_1_2_2.length, 3); - assert.equal(n_1_2_2[0].name, 'Node1'); - assert.equal(n_1_2_2[1].name, 'Node2'); + assert.equal(n_1_2_2[0]!.name, 'Node1'); + assert.equal(n_1_2_2[1]!.name, 'Node2'); assert.isNotOk(n_1_2_2[2]); // 1.4.4 // make sure we handle nodes without 'children' property assert.equal(n_1_4_4.length, 3); - assert.equal(n_1_4_4[0].name, 'Node1'); - assert.equal(n_1_4_4[1].name, 'Nochildren'); + assert.equal(n_1_4_4[0]!.name, 'Node1'); + assert.equal(n_1_4_4[1]!.name, 'Nochildren'); assert.isNotOk(n_1_4_4[2]); // 1.2.7.8 // make sure we handle multiple undefineds at the end assert.equal(n_1_2_7_8.length, 4); - assert.equal(n_1_2_7_8[0].name, 'Node1'); - assert.equal(n_1_2_7_8[1].name, 'Node2'); + assert.equal(n_1_2_7_8[0]!.name, 'Node1'); + assert.equal(n_1_2_7_8[1]!.name, 'Node2'); assert.isNotOk(n_1_2_7_8[2]); assert.isNotOk(n_1_2_7_8[3]); @@ -389,7 +389,7 @@ suite('missingAncestor', () => { // 2.301 assert.equal(n_2_301.length, 1); - assert.equal(n_2_301[0].name, 'Node301'); + assert.equal(n_2_301[0]!.name, 'Node301'); }); /* eslint-enable camelcase */ }); @@ -418,10 +418,10 @@ suite('basicWithOptions', () => { const rootNode = basicTreeWithOptions.getNodeByPathLocator('1'); assert.isUndefined( - basicTreeWithOptions.nodeConformsSearch(rootNode, 'Root'), + basicTreeWithOptions.nodeConformsSearch(rootNode!, 'Root'), ); assert.isUndefined( - basicTreeWithOptions.nodeConformsSearch(rootNode, 'Root', { + basicTreeWithOptions.nodeConformsSearch(rootNode!, 'Root', { propertyName: 'noProperty', }), ); From c0731d096bf19814dd32d57a248bb9db1ecfb426 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Tue, 19 Mar 2024 10:38:41 +0200 Subject: [PATCH 16/17] feat: add dist dir to .eslintignore --- .eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintignore b/.eslintignore index 7053dc1..729da5b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ /coverage/ +/dist/ \ No newline at end of file From 7f39fe6aed565ef54d485852c8986e143e543bf3 Mon Sep 17 00:00:00 2001 From: Florian Stancioiu Date: Wed, 20 Mar 2024 09:53:32 +0200 Subject: [PATCH 17/17] feat: add test dir to exclude_patterns --- .codeclimate.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index b9123d3..5dbb3e2 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,24 +1,24 @@ --- -version: "2" +version: '2' checks: argument-count: - enabled: false # handled by eslint rule "max-params" + enabled: false # handled by eslint rule "max-params" complex-logic: enabled: true config: threshold: 4 file-lines: - enabled: false # handled by eslint rule "max-lines" + enabled: false # handled by eslint rule "max-lines" method-complexity: - enabled: false # handled by eslint rule "complexity" + enabled: false # handled by eslint rule "complexity" method-count: enabled: true config: threshold: 20 method-lines: - enabled: false # handled by eslint rule "max-lines-per-function" + enabled: false # handled by eslint rule "max-lines-per-function" nested-control-flow: - enabled: false # handled by eslint rule "max-depth" + enabled: false # handled by eslint rule "max-depth" return-statements: enabled: true config: @@ -33,5 +33,6 @@ plugins: enabled: true exclude_patterns: - - "**/node_modules/" - - "coverage/" + - '**/node_modules/' + - 'coverage/' + - 'test/'