├── .babelrc ├── .browserslistrc ├── .coveralls.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── js-tree-list.js └── js-tree-list.min.js ├── package.json ├── releases.js ├── rollup.config.js ├── src ├── .babelrc ├── index.js ├── list-to-tree.js ├── node.js ├── tree-to-list.js ├── tree.js └── utils.js ├── test ├── after_build │ ├── .babelrc │ ├── index.js │ └── test.js └── unit │ ├── .eslintrc │ ├── jest.conf.js │ ├── setup.js │ └── test │ ├── generate-tree-default.js │ ├── index.test.js │ ├── list-to-tree-uuid.test.js │ ├── list-to-tree.test.js │ ├── node.test.js │ ├── tree-to-list.test.js │ └── tree.test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "browsers": ["> 1%", "last 2 versions", "safari >= 7", "not ie <= 8"], 8 | "node": "6.0.0" 9 | } 10 | } 11 | ] 12 | ], 13 | "plugins": ["transform-runtime"], 14 | "env": { 15 | "test": { 16 | "presets": [ 17 | [ 18 | "env", 19 | { 20 | "targets": { 21 | "browsers": [ 22 | "> 1%", 23 | "last 2 versions", 24 | "safari >= 7", 25 | "not ie <= 8" 26 | ], 27 | "node": "6.0.0" 28 | } 29 | } 30 | ] 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | safari >= 7 4 | not ie <= 8 -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | repo_token: e0I6IvxfEZlb2pY0ab3m8p8CDgGAuBvRC -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | /lib/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true 11 | }, 12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 13 | extends: 'standard', 14 | // add your custom rules here 15 | rules: { 16 | // allow async-await 17 | 'generator-star-spacing': 'off', 18 | // allow debugger during development 19 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-debug.log 6 | test/unit/coverage 7 | test/e2e/reports 8 | selenium-debug.log 9 | .idea/ 10 | yarn-error.log 11 | coverage/ 12 | .cache 13 | 14 | .AppleDouble 15 | .LSOverride 16 | Icon 17 | 18 | # Thumbnails 19 | ._* 20 | 21 | # Files that might appear on external disk 22 | .Spotlight-V100 23 | .Trashes 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | *.iml 3 | coverage/ 4 | examples/ 5 | node_modules/ 6 | typings/ 7 | sandbox/ 8 | test/ 9 | bower.json 10 | CODE_OF_CONDUCT.md 11 | COLLABORATOR_GUIDE.md 12 | CONTRIBUTING.md 13 | COOKBOOK.md 14 | ECOSYSTEM.md 15 | Gruntfile.js 16 | karma.conf.js 17 | webpack.*.js 18 | sauce_connect.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 9.5.0 4 | script: 5 | - yarn run test 6 | - yarn run build 7 | after_success: 8 | - npm run test:after_build 9 | - yarn run coveralls 10 | - yarn run codecov 11 | deploy: 12 | # provider: npm 13 | # email: a@wyr.me 14 | # api_key: 15 | # secure: RSZ3hkk2TzVp2HyRX5PVzmmI/Sy4kDUX8YdaTwjSXlQi8nAI6iGh3hPENz78hI94nwg6QOwqrxdeGfgTINcVXXu2rySWcrAiUX4nCjpoWlMi9hNoKbzywdFs684Xho9bpd7V3Tu/YZxqNCuBpLVYGM4uIPNUb8vdVi4XkfNWQCwdtKFLzJOrwrlRAY0WB8oBgRhGlypgBXUbRq0QTUYhCk2kwaissHK+BcqFpBmoICeDiyFbiovUpLv0kazi46ZJZnG4o5goPW1x79bE6Ezr+Qymy4VRYQtdpQLFtBEqBrS1VP9Q10YdWGpi79it5/0Y1DsClfo/suqpSqAU3QgmatT5GPFXQwNXVhyuLgoQu3STiwlYfSWUhOGKCe2SSX2BDdzy2ZpuKH4BKdJJpeaQr3tAWv7JlKBOtkElZimKFfSI8BdpCRfng8mp9hwmIRVjMIbhCSj5ToVhjwGfPuI9cO/1T+/4zxaLa7HqXiQNsjkRBsFv2IUpzTTDNdWcsZxBX+0xGZTtcbaiZGjkbRb2j9wwgc03D+8Uc1S22tfrooRvR+CA7jr4q+wxEtKPsDmGwKa1YX4S68nxO6Kw29VRGq4yJ80GY2Rb2DB3ogVFDKAn5dzffsJ2eJZGQeFT7ii+4t4z0SALSS7PAxF4d0W/yiAKOaWYdARlF8z/oELOtdo= 16 | # on: 17 | # tags: true 18 | # repo: yi-ge/js-tree-list 19 | provider: releases 20 | api_key: 21 | secure: W8wkSYvZjpXi8QV/apgatLuDgeceMrlq3A79FgpOAhG27bgV7N43zkbjV9RSSjZiVWJ0oZnSgHQ0Qic1P6rN7iJPmm2AY70rA6pYoE2rdb3H2NAa1n4+STDmLIgeDLZW6dAVdbOQU48W8fKBjaVgjGbRZAhu++Llalsd30QDBW64pkiCYaatxvikhzNbr4P1nhDJyJl/gUnBbKspXRpGMb8BfY9exT2LPUXM+fQSExcEjLVhkRsTVkBh3Jk61Rj55gw9wCuQxHWjTzuss870fBNkHUYxfLaBs8NGkRpMJ1ER4NrVav872KStAjnyYZobqNqHE3P2SuwCnAhfJC0xXgQnzPIjhAnQqe6X4iSbR9RHSo7VBPdAkBH3zh79amJUi0o7gh2a/cWsGE/+8HGM/D6BsNdUJ5KjAY1cyk2iJkHTP8pJpexoQELFnw9iX+7ZwxL4x0dSa/B6IzM/90dQQIAxBKAZ8L0G469hMMH4OOQdfmWwznVICotOAkkBuYNZazbslJd4fA2BpGPmb+YIGjNkM7hdp39NUJr8Fu1fCOvxzxTw1pzZqRmX3O39b+fRYw06PIeW2j2qzJPogyGR9I3Nl2TrKqj4Irt91FtUF3vLj2LQO5cg8rftgNRgR4JcZfilBtoudBKrAoqyWju62rxf4xllPbvbljAvjsKBI+M= 22 | file: bin/js-tree-list.min.js 23 | skip_cleanup: true 24 | on: 25 | tags: true 26 | repo: yi-ge/js-tree-list -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yige 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-tree-list 2 | 3 | [![npm version](https://img.shields.io/npm/v/js-tree-list.svg?style=flat-square)](https://www.npmjs.org/package/js-tree-list) 4 | [![build status](https://img.shields.io/travis/yi-ge/js-tree-list.svg?style=flat-square)](https://travis-ci.org/yi-ge/js-tree-list) 5 | [![Codecov](https://img.shields.io/codecov/c/github/yi-ge/js-tree-list.svg?style=flat-square)](https://codecov.io/gh/yi-ge/js-tree-list) 6 | [![code coverage](https://img.shields.io/coveralls/yi-ge/js-tree-list.svg?style=flat-square)](https://coveralls.io/github/yi-ge/js-tree-list) 7 | [![npm](https://img.shields.io/npm/dt/js-tree-list.svg?style=flat-square)](http://npm-stat.com/charts.html?package=js-tree-list) 8 | [![license](https://img.shields.io/github/license/yi-ge/js-tree-list.svg?style=flat-square)](https://github.com/yi-ge/js-tree-list/blob/master/LICENSE) 9 | [![GitHub last commit](https://img.shields.io/github/last-commit/yi-ge/js-tree-list.svg?style=flat-square)](https://github.com/yi-ge/js-tree-list) 10 | [![bitHound](https://img.shields.io/bithound/dependencies/github/yi-ge/js-tree-list.svg?style=flat-square)](https://www.bithound.io/github/yi-ge/js-tree-list) 11 | 12 | [![GitHub release](https://img.shields.io/github/release/yi-ge/js-tree-list.svg?style=flat-square)](https://github.com/yi-ge/js-tree-list/releases) 13 | [![Github file size](https://img.shields.io/github/size/yi-ge/js-tree-list/bin/js-tree-list.min.js.svg?style=flat-square)](https://github.com/yi-ge/js-tree-list/blob/master/bin/js-tree-list.min.js) 14 | [![codebeat badge](https://codebeat.co/badges/1e0be277-b609-4336-a4aa-b18c2cb94951)](https://codebeat.co/projects/github-com-yi-ge-js-tree-list-master) 15 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fyi-ge%2Fjs-tree-list.svg?type=small)](https://app.fossa.io/projects/git%2Bgithub.com%2Fyi-ge%2Fjs-tree-list?ref=badge_small) 16 | 17 | [![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard) 18 | 19 | Convert list to tree, managing a tree and its nodes. 20 | 21 | Fork from: 22 | https://github.com/DenQ/iron-tree 23 | https://github.com/DenQ/list-to-tree 24 | 25 | The author of this project is [DenQ](https://github.com/DenQ). This project has only been improved a little. 26 | 27 | **There is a known bug that may be reported incorrectly when the list order is incorrect. The temporary alternative is as follows:** 28 | 29 | ```js 30 | // 获取描述对象的值 31 | export const getObjectValue = (obj, des) => { 32 | return eval('obj.' + des) // eslint-disable-line 33 | } 34 | 35 | /** 36 | * 将json按一定规律组合成父子模式 37 | * @param {[type]} data json数据 38 | * @param {Number} [minPid=0] 最大的父ID(可能不是0,最大的是几就填几,就从几开始排) 39 | * @param {String} [IDString='ID'] ID的别名 40 | * @param {String} [pidString='pid'] 父ID的别名 41 | * @param {String} [childrenString='children'] 生成的子的别名(子的数据就在这个名称下) 42 | * @return {[type]} 父子JSON 43 | */ 44 | export const toTreeData = ( 45 | data, 46 | minPid = 0, 47 | IDString = 'ID', 48 | pidString = 'pid', 49 | childrenString = 'children', 50 | sort = 'sort' 51 | ) => { 52 | let pos = {} 53 | let tree = [] 54 | let n = 0 55 | 56 | if (minPid === 'null') { 57 | minPid = null 58 | } 59 | 60 | while (data.length !== 0) { 61 | if (getObjectValue(data[n], pidString) == minPid) { // eslint-disable-line 62 | // delete getObjectValue(data[n], pidString) 63 | data[n][childrenString] = [] 64 | tree.push(data[n]) 65 | pos[getObjectValue(data[n], IDString)] = [tree.length - 1] 66 | data.splice(n, 1) 67 | n-- 68 | } else { 69 | let posArray = pos[getObjectValue(data[n], pidString)] 70 | if (posArray !== undefined) { 71 | let obj = tree[posArray[0]] 72 | for (let j = 1; j < posArray.length; j++) { 73 | obj = obj[childrenString][posArray[j]] 74 | } 75 | // delete getObjectValue(data[n], pidString) 76 | data[n][childrenString] = [] 77 | obj[childrenString].push(data[n]) 78 | pos[getObjectValue(data[n], IDString)] = posArray.concat([ 79 | obj[childrenString].length - 1 80 | ]) 81 | data.splice(n, 1) 82 | n-- 83 | } 84 | } 85 | n++ 86 | if (n > data.length - 1) { 87 | n = 0 88 | } 89 | } 90 | 91 | // sort 92 | const toSort = (tree) => { 93 | tree.sort((a, b) => { 94 | return a[sort] - b[sort] 95 | }) 96 | } 97 | 98 | const ergodicTree = (tree) => { 99 | for (const n in tree) { 100 | if (tree[n][childrenString] && tree[n][childrenString].length) { 101 | toSort(tree[n][childrenString]) 102 | ergodicTree(tree[n][childrenString]) 103 | } 104 | } 105 | } 106 | 107 | toSort(tree) 108 | ergodicTree(tree) 109 | 110 | return tree 111 | } 112 | ``` 113 | 114 | **You can use it** 115 | ```js 116 | console.time('tree算法') 117 | const girdData = toTreeData(tmp, 'null', 'dataUuid', 'parentUuid', '_children') 118 | console.timeEnd('tree算法') 119 | ``` 120 | 121 | ## Features 122 | 123 | * Convert list to tree. 124 | * Convert tree to list. 125 | * Tree sort by `last`. 126 | * UUID is support. 127 | 128 | ## Installation 129 | 130 | ``` 131 | $ npm install js-tree-list 132 | ``` 133 | 134 | ## Usage 135 | 136 | ```js 137 | // JsTreeList.ListToTree Config 138 | const defaultOptions = { 139 | key_id: 'id', 140 | key_parent: 'parent', 141 | key_child: 'child', 142 | key_last: null, 143 | uuid: false, 144 | empty_children: false 145 | } 146 | ``` 147 | 148 | ```js 149 | import JsTreeList from "js-tree-list" 150 | var list = [ 151 | { 152 | id: 1, 153 | parent: 0 154 | }, 155 | { 156 | id: 2, 157 | parent: 1 158 | }, 159 | { 160 | id: 3, 161 | parent: 1 162 | }, 163 | { 164 | id: 4, 165 | parent: 2 166 | }, 167 | { 168 | id: 5, 169 | parent: 2 170 | }, 171 | { 172 | id: 6, 173 | parent: 0 174 | }, 175 | { 176 | id: 7, 177 | parent: 0 178 | }, 179 | { 180 | id: 8, 181 | parent: 7 182 | }, 183 | { 184 | id: 9, 185 | parent: 8 186 | }, 187 | { 188 | id: 10, 189 | parent: 0 190 | } 191 | ] 192 | 193 | const tree = new JsTreeList.ListToTree(list, { 194 | key_id: "id", 195 | key_parent: "parent", 196 | key_child: "children", 197 | key_last: "last" 198 | }).GetTree() 199 | 200 | const list = new JsTreeList.TreeToList(tree, { 201 | key_child: "children", 202 | empty_children: true 203 | }).GetList() 204 | 205 | console.log(tree) 206 | console.log(list) 207 | ``` 208 | 209 | ###### Result 210 | 211 | [{ 212 | "id": 1, 213 | "parent": 0, 214 | "child": [ 215 | { 216 | "id": 2, 217 | "parent": 1, 218 | "child": [ 219 | { 220 | "id": 4, 221 | "parent": 2 222 | }, { 223 | "id": 5, 224 | "parent": 2 225 | } 226 | ] 227 | }, 228 | { 229 | "id": 3, 230 | "parent": 1 231 | } 232 | ] 233 | }, { 234 | "id": 6, 235 | "parent": 0 236 | }, { 237 | "id": 7, 238 | "parent": 0, 239 | "child": [ 240 | { 241 | "id": 8, 242 | "parent": 7, 243 | "child": [ 244 | { 245 | "id": 9, 246 | "parent": 8 247 | } 248 | ] 249 | } 250 | ] 251 | }, { 252 | "id": 10, 253 | "parent": 0 254 | }]; 255 | 256 | # Methods 257 | 258 | * **constructor(list, options)** 259 | * params: 260 | * `list` - array list with elements. Like `{ id: 5: parent: 1 }`. 261 | * `options` - optional parameter. Object for describe flags and field names for tree. 262 | * **.GetTree()** This method will be return json tree 263 | * example: 264 | ``` 265 | tree.GetTree() 266 | ``` 267 | * **.sort(callback)** The custom sort method 268 | * callback(a, b) - a and b have `Node` type and have methods: add, remove, get, set, sort, traversal, etc... 269 | * example: 270 | ```js 271 | function compareById(vector) { 272 | return (a, b) => { 273 | const aid = Number(a.get("id")) 274 | const bid = Number(b.get("id")) 275 | if (aid > bid) { 276 | return vector ? 1 : -1 277 | } else if (aid < bid) { 278 | return vector ? -1 : 1 279 | } else { 280 | return 0 281 | } 282 | } 283 | } 284 | ltt.sort(compareById(false)) 285 | ``` 286 | 287 | # The Tree and Node Base usage 288 | 289 | ```js 290 | // create tree 291 | import JsTreeList from "js-tree-list" 292 | const object = { id: 1, title: "Root" } 293 | const tree = new JsTreeList.Tree(object) 294 | 295 | // add nodes 296 | const regularObject = { id: 2, title: "Node 2" } 297 | tree.add(parentNode => { 298 | return parentNode.get("id") === 1 299 | }, regularObject) 300 | 301 | // contains node 302 | const targetNode = tree.contains(currentNode => { 303 | return currentNode.get("id") === 2 304 | }) 305 | 306 | // remove node 307 | const result = tree.remove(currentNode => { 308 | return currentNode.get("id") === 2 309 | }) 310 | 311 | // traversal 312 | const criteria = currentNode => currentNode.get("id") === 1 313 | tree.traversal(criteria, currentNode => { 314 | currentNode.set("some", true) 315 | }) 316 | ``` 317 | 318 | ```js 319 | function compareById(vector) { 320 | return (a, b) => { 321 | const aid = Number(a.get("id")) 322 | const bid = Number(b.get("id")) 323 | if (aid > bid) { 324 | return vector ? 1 : -1 325 | } else if (aid < bid) { 326 | return vector ? -1 : 1 327 | } else { 328 | return 0 329 | } 330 | } 331 | } 332 | tree.sort(compareById(false)) // desc 333 | ``` 334 | 335 | The following are the other methods available. 336 | 337 | --- 338 | 339 | # Tree 340 | 341 | This is the class of tree management 342 | 343 | ### Properties 344 | 345 | * **rootNode** Root tree node 346 | * type `Node` 347 | 348 | ### Methods 349 | 350 | * **contstructor(object)** 351 | * params 352 | * object - json `object`. Optional 353 | * return `Three` 354 | * example 355 | ```js 356 | const object = { id: 1, title: "Root" } 357 | const tree = new JsTreeList.Tree(object) 358 | ``` 359 | * **.add(criteria, object)** Adds a node to the tree if the criterion is true 360 | * params 361 | * criteria(Node) - `function` or `string`. If `string` then criteria is **"root"** 362 | * object - content for the node 363 | * return `Three` 364 | * examples 365 | ```js 366 | const object = { id: 1, title: "Root" } 367 | const tree = new JsTreeList.Tree() 368 | const resultTree = tree.add("root", object) 369 | ``` 370 | ```js 371 | const regularObject = { id: 2, title: "Node 2" } 372 | const resultTree = tree.add(parentNode => { 373 | return parentNode.get("id") === 1 374 | }, regularObject) 375 | ``` 376 | * **.remove(criteria)** Removes a node from a tree if the criterion is true 377 | * params 378 | * criteria(Node) - return `boolean` 379 | * return `boolean` 380 | * examples 381 | ```js 382 | const result = tree.remove(currentNode => { 383 | return currentNode.get("id") === 7 384 | }) 385 | ``` 386 | * **.contains(criteria)** Searches for a node in a tree according to the criterion 387 | 388 | * params 389 | * criteria(Node) - return `boolean` 390 | * return `Node` 391 | * examples 392 | 393 | ```js 394 | const targetNode = tree.contains(currentNode => { 395 | return currentNode.get("id") === 7 396 | }) 397 | ``` 398 | 399 | * **.sort(compare)** Sorts a tree 400 | * params 401 | * compare(a:Node, b:Node) - comparison function 402 | * return `null` 403 | * examples 404 | ```js 405 | function compareById(vector) { 406 | return (a, b) => { 407 | const aid = Number(a.get("id")) 408 | const bid = Number(b.get("id")) 409 | if (aid > bid) { 410 | return vector ? 1 : -1 411 | } else if (aid < bid) { 412 | return vector ? -1 : 1 413 | } else { 414 | return 0 415 | } 416 | } 417 | } 418 | tree.sort(compareById(false)) //Desc 419 | ``` 420 | * **.move(criteria, destination)** Moves the desired branch or node to the node or branch of the destination, according to the criteria 421 | * params 422 | * criteria(Node) - callback 423 | * destination(Node) - callback 424 | * return `boolean` 425 | * examples 426 | ```js 427 | const search = currentNode => currentNode.get("id") === 7 428 | const destination = currentNode => currentNode.get("id") === 3 429 | const result = tree.move(search, destination) 430 | ``` 431 | * **.traversal(criteria, callback)** Bypasses the tree and, according to the criterion, calls a function for each node 432 | * params 433 | * criteria(Node) - return `boolean` 434 | * callback(Node) 435 | * return `null` 436 | * examples 437 | ```js 438 | const criteria = currentNode => currentNode.get("id") === 7 439 | tree.traversal(criteria, currentNode => { 440 | currentNode.set("some", true) 441 | }) 442 | ``` 443 | ```js 444 | tree.traversal(null, currentNode => { 445 | if (currentNode.get("id") % 2 === 0) { 446 | currentNode.set("some", true) 447 | } 448 | }) 449 | ``` 450 | * **.toJson(options)** Represents a tree in the form of a json format 451 | * params 452 | * options - `object`. Optional 453 | * empty_children - Type `boolean`. Allow empty children. Default `true` 454 | * key_children - Type `string`. Field name for children. Default `children` 455 | * return `object` 456 | * examples 457 | ```js 458 | const json = tree.toJson() 459 | ``` 460 | 461 | --- 462 | 463 | # Node 464 | 465 | This is the node management class 466 | 467 | ### Properties 468 | 469 | * **content** Content of the node 470 | * type `object` 471 | * **children** Children of the node 472 | * type `array` 473 | * **length** Number children of the node 474 | * type `number` 475 | 476 | ### Methods 477 | 478 | * **constructor(json)** 479 | 480 | * params 481 | * json - simple `json` object 482 | * examples 483 | 484 | ```js 485 | import JsTreeList from "js-tree-list" 486 | const rootContent = { 487 | id: 1, 488 | name: "Root" 489 | } 490 | let node = new JsTreeList.Node(rootContent) 491 | ``` 492 | 493 | * **.add(child)** Adding a child to the node 494 | * return `Node` - created node 495 | * params 496 | * child - type `object`/json 497 | * examples 498 | ```js 499 | const rootContent = { 500 | id: 1, 501 | name: "Root" 502 | } 503 | let node = new JsTreeList.Node(rootContent) 504 | const childNode = node.add({ id: 2, name: "Two node" }) 505 | ``` 506 | * **.remove(criteria)** Removing a child node according to the criterion 507 | 508 | * return - removed `Node` 509 | * params 510 | * criteria - criteria function for removing nodes 511 | * examples 512 | 513 | ```js 514 | const removedNodes = node.remove(itemNode => { 515 | return itemNode.get("id") === 3 516 | }) 517 | ``` 518 | 519 | * **.get(path)** Access to node content by field name 520 | * return `mixed` 521 | * params 522 | * path - key name for object in node. For example `id` or `fullname`, etc... 523 | * examples 524 | ```js 525 | node.get("id") // 1 526 | node.get("name") // "Some name" 527 | ``` 528 | * **.set(path, value)** Setting a value or creating a new field in the contents of a node 529 | * return `boolean` 530 | * params 531 | * path - `String` field name 532 | * value - `mixed` 533 | * examples 534 | ```js 535 | node.set('id', 100)); // returned `true`. Node.content.id = 100 536 | node.get('id'); // 100 537 | ``` 538 | * **.sort(compare)** Sorting child nodes 539 | * return `null` 540 | * params 541 | * compare - custom function for sorting 542 | * examples 543 | ```js 544 | function compareById(vector) { 545 | return (a, b) => { 546 | const aid = Number(a.get("id")) 547 | const bid = Number(b.get("id")) 548 | if (aid > bid) { 549 | return vector ? 1 : -1 550 | } else if (aid < bid) { 551 | return vector ? -1 : 1 552 | } else { 553 | return 0 554 | } 555 | } 556 | } 557 | node.sort(compareById(false)) 558 | ``` 559 | * **.traversal(criteria, callback)** Bypassing child nodes according to the criterion and applying function to them 560 | * return `null` 561 | * params 562 | * criteria - `function` criteria each nodes 563 | * callback - `function` fire when criteria is true for node 564 | * examples 565 | ```js 566 | // for all nodes 567 | node.traversal(null, currentNode => { 568 | const name = currentNode.get("name") 569 | currentNode.set("name", `${name}!`) // Last symbol "!" 570 | }) 571 | ``` 572 | ```js 573 | // only for node.id == 3 574 | node.traversal( 575 | currentNode => currentNode.get("id") === 3, 576 | currentNode => { 577 | const name = currentNode.get("name") 578 | currentNode.set("name", `${name}!`) // Last symbol "!" 579 | } 580 | ) 581 | ``` 582 | 583 | --- 584 | -------------------------------------------------------------------------------- /bin/js-tree-list.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global['js-tree-list'] = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 8 | return typeof obj; 9 | } : function (obj) { 10 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 11 | }; 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | var classCallCheck = function (instance, Constructor) { 24 | if (!(instance instanceof Constructor)) { 25 | throw new TypeError("Cannot call a class as a function"); 26 | } 27 | }; 28 | 29 | var createClass = function () { 30 | function defineProperties(target, props) { 31 | for (var i = 0; i < props.length; i++) { 32 | var descriptor = props[i]; 33 | descriptor.enumerable = descriptor.enumerable || false; 34 | descriptor.configurable = true; 35 | if ("value" in descriptor) descriptor.writable = true; 36 | Object.defineProperty(target, descriptor.key, descriptor); 37 | } 38 | } 39 | 40 | return function (Constructor, protoProps, staticProps) { 41 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 42 | if (staticProps) defineProperties(Constructor, staticProps); 43 | return Constructor; 44 | }; 45 | }(); 46 | 47 | 48 | 49 | 50 | 51 | var defineProperty = function (obj, key, value) { 52 | if (key in obj) { 53 | Object.defineProperty(obj, key, { 54 | value: value, 55 | enumerable: true, 56 | configurable: true, 57 | writable: true 58 | }); 59 | } else { 60 | obj[key] = value; 61 | } 62 | 63 | return obj; 64 | }; 65 | 66 | var Node = function () { 67 | function Node(content) { 68 | classCallCheck(this, Node); 69 | 70 | this.content = content; 71 | this.children = []; 72 | this.length = 0; 73 | } 74 | 75 | createClass(Node, [{ 76 | key: 'get', 77 | value: function get$$1(fieldKey) { 78 | if (typeof this.content[fieldKey] !== 'undefined') { 79 | return this.content[fieldKey]; 80 | } 81 | } 82 | }, { 83 | key: 'set', 84 | value: function set$$1(fieldKey, value) { 85 | return !!(this.content[fieldKey] = value); 86 | } 87 | }, { 88 | key: 'add', 89 | value: function add(child) { 90 | var node = child instanceof Node ? child : new Node(child); 91 | node.parent = this; 92 | this.length++; 93 | this.children.push(node); 94 | return node; 95 | } 96 | }, { 97 | key: 'remove', 98 | value: function remove(callback) { 99 | var index = this.children.findIndex(callback); 100 | if (index > -1) { 101 | var removeItems = this.children.splice(index, 1); 102 | this.length--; 103 | return removeItems; 104 | } 105 | return []; 106 | } 107 | }, { 108 | key: 'sort', 109 | value: function sort(compare) { 110 | return this.children.sort(compare); 111 | } 112 | }, { 113 | key: 'traversal', 114 | value: function traversal(criteria, callback) { 115 | criteria = criteria || function () { 116 | return true; 117 | }; 118 | this.children.filter(criteria).forEach(callback); 119 | } 120 | }]); 121 | return Node; 122 | }(); 123 | 124 | /** 125 | * node-compare-by-id 126 | * Return callback to compare nodes by id 127 | * @param boolean vector If vector is true then sort asc else desc 128 | * @return function Compare function 129 | */ 130 | 131 | 132 | /** 133 | * remove-empty-children (for json tree) 134 | * @param {*} jTree 135 | * @param {*} node 136 | * @param {*} options 137 | */ 138 | var removeEmptyChildren = function removeEmptyChildren(jTree) { 139 | var node = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 140 | var options = arguments[2]; 141 | var key_children = options.key_children; 142 | 143 | node = node || jTree[0]; 144 | if (node[key_children].length === 0) { 145 | delete node[key_children]; 146 | } else { 147 | node[key_children].forEach(function (item) { 148 | removeEmptyChildren(jTree, item, options); 149 | }); 150 | } 151 | }; 152 | 153 | /** 154 | * search-node 155 | * @param {*} tree 156 | * @param {*} node 157 | * @param {*} criteria 158 | * @param {*} options 159 | */ 160 | var searchNode = function searchNode(tree, node, criteria, options) { 161 | var currentNode = node || tree.rootNode; 162 | if (criteria(currentNode)) { 163 | return currentNode; 164 | } 165 | var children = currentNode.children; 166 | var target = null; 167 | for (var i = 0; i < children.length; i++) { 168 | var item = children[i]; 169 | target = searchNode(tree, item, criteria); 170 | if (target) { 171 | return target; 172 | } 173 | } 174 | }; 175 | 176 | /** 177 | * showTree 178 | * @param {*} tree 179 | * @param {*} node 180 | * @param {*} level 181 | */ 182 | 183 | 184 | /** 185 | * traversal-tree 186 | * @param {*} tree 187 | * @param {*} node 188 | * @param {*} criteria 189 | * @param {*} callback 190 | */ 191 | var traversalTree = function traversalTree(tree) { 192 | var node = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 193 | var criteria = arguments[2]; 194 | var callback = arguments[3]; 195 | 196 | var currentNode = node || tree.rootNode; 197 | if (!node) { 198 | if (typeof criteria === 'function' && criteria(currentNode)) { 199 | callback(currentNode); 200 | } else if (criteria === null) { 201 | callback(currentNode); 202 | } 203 | } 204 | currentNode.traversal(criteria, callback); 205 | var children = currentNode.children; 206 | 207 | children.forEach(function (item) { 208 | traversalTree(tree, item, criteria, callback); 209 | }); 210 | }; 211 | 212 | /** 213 | * serializeTree 214 | * @param {*} tree 215 | * @param {*} node 216 | * @param {*} target 217 | * @param {*} options 218 | */ 219 | var serializeTree = function serializeTree(tree) { 220 | var node = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 221 | var target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; 222 | var options = arguments[3]; 223 | var key_children = options.key_children; 224 | 225 | node = node || tree.rootNode; 226 | if (!node) { 227 | return null; 228 | } 229 | var index = target.push(Object.assign(defineProperty({}, key_children, []), node.content)); 230 | node.children.forEach(function (item) { 231 | serializeTree(tree, item, target[index - 1][key_children], options); 232 | }); 233 | return target; 234 | }; 235 | 236 | var Tree = function () { 237 | function Tree() { 238 | var object = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; 239 | classCallCheck(this, Tree); 240 | 241 | this.rootNode = null; 242 | if (object) { 243 | this.rootNode = new Node(object); 244 | } 245 | } 246 | 247 | // only for rootNode 248 | 249 | 250 | createClass(Tree, [{ 251 | key: 'get', 252 | value: function get$$1(path) { 253 | return this.rootNode.get(path); 254 | } 255 | 256 | // only for rootNode 257 | 258 | }, { 259 | key: 'set', 260 | value: function set$$1(path, value) { 261 | this.rootNode.set(path, value); 262 | } 263 | }, { 264 | key: 'add', 265 | value: function add(callback, object) { 266 | var type = typeof callback === 'undefined' ? 'undefined' : _typeof(callback); 267 | if (type === 'string' && callback === 'root') { 268 | this.rootNode = new Node(object); 269 | return this; 270 | } else if (type === 'function') { 271 | var target = searchNode(this, null, callback); 272 | if (target && target.add(object)) { 273 | return this; 274 | } else { 275 | console.log('Warning', object); 276 | } 277 | } 278 | } 279 | }, { 280 | key: 'contains', 281 | value: function contains(criteria) { 282 | return searchNode(this, null, criteria); 283 | } 284 | }, { 285 | key: 'remove', 286 | value: function remove(criteria) { 287 | var targetNode = this.contains(criteria); 288 | if (targetNode) { 289 | return !!targetNode.parent.remove(criteria); 290 | } 291 | return false; 292 | } 293 | }, { 294 | key: 'move', 295 | value: function move(search, destination) { 296 | var targetNode = this.contains(search); 297 | if (targetNode && this.remove(search)) { 298 | var destinationNode = this.contains(destination); 299 | return !!destinationNode.add(targetNode); 300 | } 301 | return false; 302 | } 303 | }, { 304 | key: 'traversal', 305 | value: function traversal(criteria, callback) { 306 | traversalTree(this, null, criteria, callback); 307 | } 308 | }, { 309 | key: 'sort', 310 | value: function sort(compare) { 311 | this.traversal(null, function (currentNode) { 312 | currentNode.sort(compare); 313 | }); 314 | } 315 | }, { 316 | key: 'toJson', 317 | value: function toJson() { 318 | var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 319 | 320 | var optionsDefault = { 321 | key_children: 'children', 322 | empty_children: true 323 | }; 324 | options = Object.assign(optionsDefault, options); 325 | var result = serializeTree(this, null, [], options); 326 | 327 | if (!options.empty_children) { 328 | removeEmptyChildren(result, null, options); 329 | } 330 | 331 | if (result && result.length > 0) { 332 | return result[0]; 333 | } else { 334 | return []; 335 | } 336 | } 337 | }]); 338 | return Tree; 339 | }(); 340 | 341 | var defaultOptions = { 342 | key_id: 'id', 343 | key_parent: 'parent', 344 | key_child: 'child', 345 | key_last: null, 346 | uuid: false, 347 | empty_children: false 348 | }; 349 | 350 | function sortBy(collection, propertyA, propertyB) { 351 | return collection.sort(function (a, b) { 352 | if (a[propertyB] < b[propertyB]) { 353 | if (a[propertyA] > b[propertyA]) { 354 | return 1; 355 | } 356 | return -1; 357 | } else { 358 | if (a[propertyA] < b[propertyA]) { 359 | return -1; 360 | } 361 | return 1; 362 | } 363 | }); 364 | } 365 | 366 | var ListToTree = function () { 367 | function ListToTree(list) { 368 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 369 | classCallCheck(this, ListToTree); 370 | 371 | var _list = list.map(function (item) { 372 | return item; 373 | }); 374 | 375 | options = Object.assign({}, defaultOptions, options); 376 | this.options = options; 377 | var _options = options, 378 | key_id = _options.key_id, 379 | key_parent = _options.key_parent, 380 | uuid = _options.uuid; 381 | 382 | 383 | if (uuid === false) { 384 | sortBy(_list, key_parent, key_id); 385 | } 386 | 387 | var tree = new Tree(defineProperty({}, key_id, 0)); 388 | _list.forEach(function (item, index) { 389 | tree.add(function (parentNode) { 390 | return parentNode.get(key_id) === item[key_parent] || item[key_parent] === null; 391 | }, item); 392 | }); 393 | 394 | this.tree = tree; 395 | } 396 | 397 | createClass(ListToTree, [{ 398 | key: 'sort', 399 | value: function sort(criteria) { 400 | this.tree.sort(criteria); 401 | } 402 | }, { 403 | key: 'last', 404 | value: function last(val, key_id, key_last, key_child) { 405 | for (var n in val) { 406 | if (val[n][key_child] && val[n][key_child].length) { 407 | // 如果有子元素,则先对子元素进行处理 408 | this.last(val[n][key_child], key_id, key_last, key_child); 409 | } 410 | if (val[n][key_last] !== 0) { 411 | if (n - 1 >= 0 && val[n - 1][key_id] !== val[n][key_last] || n - 1 < 0) { 412 | var tmp = val.splice(n, 1); // 从该元素位置删除元素并将已删除的元素放置于新数组(tmp) 413 | val.splice(n + 1, 0, tmp[0]); // 在指定ID元素后面添加被删除的元素 414 | } 415 | } 416 | } 417 | } 418 | }, { 419 | key: 'GetTree', 420 | value: function GetTree() { 421 | var _options2 = this.options, 422 | key_id = _options2.key_id, 423 | key_child = _options2.key_child, 424 | empty_children = _options2.empty_children, 425 | key_last = _options2.key_last; 426 | 427 | 428 | var json = this.tree.toJson({ 429 | key_children: key_child, 430 | empty_children: empty_children 431 | })[key_child]; 432 | 433 | if (key_last) { 434 | this.last(json, key_id, key_last, key_child); 435 | } 436 | return json; 437 | } 438 | }]); 439 | return ListToTree; 440 | }(); 441 | 442 | var index = { 443 | ListToTree: ListToTree, 444 | Tree: Tree, 445 | Node: Node 446 | }; 447 | 448 | return index; 449 | 450 | }))); 451 | -------------------------------------------------------------------------------- /bin/js-tree-list.min.js: -------------------------------------------------------------------------------- 1 | !function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):n["js-tree-list"]=e()}(this,function(){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&"function"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?"symbol":typeof n},e=function(n,e){if(!(n instanceof e))throw new TypeError("Cannot call a class as a function")},t=function(){function n(n,e){for(var t=0;t-1){var t=this.children.splice(e,1);return this.length--,t}return[]}},{key:"sort",value:function(n){return this.children.sort(n)}},{key:"traversal",value:function(n,e){n=n||function(){return!0},this.children.filter(n).forEach(e)}}]),n}(),o=function n(e,t,r,i){var o=t||e.rootNode;if(r(o))return o;for(var u=o.children,l=null,c=0;c0&&void 0!==arguments[0]?arguments[0]:void 0;e(this,u),this.rootNode=null,n&&(this.rootNode=new i(n))}return t(u,[{key:"get",value:function(n){return this.rootNode.get(n)}},{key:"set",value:function(n,e){this.rootNode.set(n,e)}},{key:"add",value:function(e,t){var r=void 0===e?"undefined":n(e);if("string"===r&&"root"===e)return this.rootNode=new i(t),this;if("function"===r){var u=o(this,null,e);if(u&&u.add(t))return this;console.log("Warning",t)}}},{key:"contains",value:function(n){return o(this,null,n)}},{key:"remove",value:function(n){var e=this.contains(n);return!!e&&!!e.parent.remove(n)}},{key:"move",value:function(n,e){var t=this.contains(n);return!(!t||!this.remove(n))&&!!this.contains(e).add(t)}},{key:"traversal",value:function(n,e){!function n(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments[2],i=arguments[3],o=t||e.rootNode;t||("function"==typeof r&&r(o)?i(o):null===r&&i(o)),o.traversal(r,i),o.children.forEach(function(t){n(e,t,r,i)})}(this,null,n,e)}},{key:"sort",value:function(n){this.traversal(null,function(e){e.sort(n)})}},{key:"toJson",value:function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=function n(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments[3],u=o.key_children;if(!(t=t||e.rootNode))return null;var l=i.push(Object.assign(r({},u,[]),t.content));return t.children.forEach(function(t){n(e,t,i[l-1][u],o)}),i}(this,null,[],n=Object.assign({key_children:"children",empty_children:!0},n));return n.empty_children||function n(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments[2],i=r.key_children;0===(t=t||e[0])[i].length?delete t[i]:t[i].forEach(function(t){n(e,t,r)})}(e,null,n),e&&e.length>0?e[0]:[]}}]),u}(),l={key_id:"id",key_parent:"parent",key_child:"child",key_last:null,uuid:!1,empty_children:!1};return{ListToTree:function(){function n(t){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};e(this,n);var o=t.map(function(n){return n});i=Object.assign({},l,i),this.options=i;var c,a,s=i,f=s.key_id,h=s.key_parent;!1===s.uuid&&(c=h,a=f,o.sort(function(n,e){return n[a]e[c]?1:-1:n[c]=0&&n[i-1][e]!==n[i][t]||i-1<0)){var o=n.splice(i,1);n.splice(i+1,0,o[0])}}},{key:"GetTree",value:function(){var n=this.options,e=n.key_id,t=n.key_child,r=n.empty_children,i=n.key_last,o=this.tree.toJson({key_children:t,empty_children:r})[t];return i&&this.last(o,e,i,t),o}}]),n}(),Tree:u,Node:i}}); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-tree-list", 3 | "version": "1.2.2", 4 | "description": "Convert list to tree, managing a tree and its nodes.", 5 | "main": "bin/js-tree-list.min.js", 6 | "scripts": { 7 | "build:babel": "babel src -d dist", 8 | "build": "rollup --config rollup.config.js && uglifyjs --compress --output bin/js-tree-list.min.js --mangle -- bin/js-tree-list.js", 9 | "unit": "jest --config test/unit/jest.conf.js --coverage", 10 | "test": "npm run unit", 11 | "test:watch": "jest --config test/unit/jest.conf.js --watch", 12 | "test:watchAll": "jest --config test/unit/jest.conf.js --watchAll --coverage", 13 | "test:after_build": "node test/after_build/index.js", 14 | "coveralls": "cat ./test/unit/coverage/lcov.info | ./node_modules/.bin/coveralls", 15 | "codecov": "codecov -t 21b722b7-28ff-49ce-8a38-37c451027140", 16 | "releases": "node releases.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/yi-ge/js-tree-list.git" 21 | }, 22 | "keywords": [ 23 | "tree", 24 | "list to tree", 25 | "js list tree" 26 | ], 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/yi-ge/js-tree-list/issues" 30 | }, 31 | "homepage": "https://github.com/yi-ge/js-tree-list#readme", 32 | "devDependencies": { 33 | "babel-cli": "^6.26.0", 34 | "babel-eslint": "^8.2.1", 35 | "babel-jest": "^22.1.0", 36 | "babel-plugin-external-helpers": "^6.22.0", 37 | "babel-plugin-transform-runtime": "^6.23.0", 38 | "babel-preset-env": "^1.6.1", 39 | "babel-register": "^6.26.0", 40 | "codecov": "^3.0.0", 41 | "coveralls": "^3.0.0", 42 | "eslint": "^4.16.0", 43 | "eslint-config-standard": "^11.0.0-beta.0", 44 | "eslint-plugin-import": "^2.8.0", 45 | "eslint-plugin-node": "^5.2.1", 46 | "eslint-plugin-promise": "^3.6.0", 47 | "eslint-plugin-standard": "^3.0.1", 48 | "jest": "^22.1.4", 49 | "rollup": "^0.55.0", 50 | "rollup-plugin-babel": "^3.0.3", 51 | "rollup-plugin-node-resolve": "^3.0.2", 52 | "uglify-js": "^3.3.8", 53 | "uuid": "^3.2.1" 54 | }, 55 | "engines": { 56 | "node": ">= 6.0.0", 57 | "npm": ">= 3.0.0" 58 | }, 59 | "browserslist": [ 60 | "> 1%", 61 | "last 2 versions", 62 | "safari >= 7", 63 | "not ie <= 8" 64 | ], 65 | "jest": { 66 | "coverageDirectory": "./test/unit/coverage/", 67 | "collectCoverage": true 68 | } 69 | } -------------------------------------------------------------------------------- /releases.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const { exec } = require('child_process') 4 | const package = require('./package.json') 5 | 6 | let arrVersion = (package.version).split('.') 7 | 8 | if (process.argv[2]) { 9 | package.version = process.argv[2] 10 | } else { 11 | arrVersion[2] = parseInt(arrVersion[2]) + 1 12 | package.version = arrVersion.join('.') 13 | } 14 | 15 | console.log('The now version is: ' + package.version) 16 | 17 | const upPackage = JSON.stringify(package, null, 2) 18 | fs.writeFileSync(path.resolve(__dirname + '/package.json'), upPackage) 19 | 20 | exec( 21 | `git add . && git commit -m "releases v${ 22 | package.version 23 | }." && git config --local user.name "yi-ge" && git config --local user.email "a@wyr.me" && git push --all origin && git tag -a v${ 24 | package.version 25 | } -m "releases v${ 26 | package.version 27 | }." && git push origin --tags && git fetch origin`, 28 | (error, stdout, stderr) => { 29 | if (error) { 30 | console.error(`exec error: ${error}`) 31 | return 32 | } 33 | console.log(`stdout: ${stdout}`) 34 | console.log(`stderr: ${stderr}`) 35 | } 36 | ) 37 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve' 2 | import babel from 'rollup-plugin-babel' 3 | 4 | export default { 5 | input: 'src/index.js', 6 | output: { 7 | name: 'js-tree-list', 8 | file: 'bin/js-tree-list.js', 9 | format: 'umd' 10 | }, 11 | plugins: [ 12 | resolve(), 13 | babel({ 14 | exclude: 'node_modules/**', 15 | plugins: ['external-helpers'] 16 | }) 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "browsers": ["> 1%", "last 2 versions", "safari >= 7", "not ie <= 8"], 9 | "node": "6.0.0" 10 | } 11 | } 12 | ] 13 | ], 14 | "env": { 15 | "test": { 16 | "presets": [ 17 | [ 18 | "env", 19 | { 20 | "targets": { 21 | "browsers": [ 22 | "> 1%", 23 | "last 2 versions", 24 | "safari >= 7", 25 | "not ie <= 8" 26 | ], 27 | "node": "6.0.0" 28 | } 29 | } 30 | ] 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ListToTree from './list-to-tree' 2 | import Tree from './tree' 3 | import Node from './node' 4 | 5 | export default { 6 | ListToTree, 7 | Tree, 8 | Node 9 | } 10 | -------------------------------------------------------------------------------- /src/list-to-tree.js: -------------------------------------------------------------------------------- 1 | import LTT from './tree' 2 | 3 | const defaultOptions = { 4 | key_id: 'id', 5 | key_parent: 'parent', 6 | key_child: 'child', 7 | key_last: null, 8 | uuid: false, 9 | empty_children: false 10 | } 11 | 12 | function sortBy (collection, propertyA, propertyB) { 13 | return collection.sort(function (a, b) { 14 | if (a[propertyB] < b[propertyB]) { 15 | if (a[propertyA] > b[propertyA]) { 16 | return 1 17 | } 18 | return -1 19 | } else { 20 | if (a[propertyA] < b[propertyA]) { 21 | return -1 22 | } 23 | return 1 24 | } 25 | }) 26 | } 27 | 28 | export default class ListToTree { 29 | constructor(list, options = {}) { 30 | const _list = list.map(item => item) 31 | 32 | options = Object.assign({}, defaultOptions, options) 33 | this.options = options 34 | const { key_id, key_parent, uuid } = options 35 | 36 | if (uuid === false) { 37 | sortBy(_list, key_parent, key_id) 38 | } 39 | 40 | const tree = new LTT({ 41 | [key_id]: 0 42 | }) 43 | _list.forEach((item, index) => { 44 | tree.add(parentNode => { 45 | return parentNode.get(key_id) === item[key_parent] || item[key_parent] === null 46 | }, item) 47 | }) 48 | 49 | this.tree = tree 50 | } 51 | 52 | sort (criteria) { 53 | this.tree.sort(criteria) 54 | } 55 | 56 | last (val, key_id, key_last, key_child) { 57 | for (let n in val) { 58 | if (val[n][key_child] && val[n][key_child].length) { // 如果有子元素,则先对子元素进行处理 59 | this.last(val[n][key_child], key_id, key_last, key_child) 60 | } 61 | if (val[n][key_last] !== 0) { 62 | if (((n - 1) >= 0 && val[n - 1][key_id] !== val[n][key_last]) || (n - 1) < 0) { 63 | const tmp = val.splice(n, 1) // 从该元素位置删除元素并将已删除的元素放置于新数组(tmp) 64 | val.splice(n + 1, 0, tmp[0]) // 在指定ID元素后面添加被删除的元素 65 | } 66 | } 67 | } 68 | } 69 | 70 | GetTree () { 71 | const { key_id, key_child, empty_children, key_last } = this.options 72 | 73 | let json = this.tree.toJson({ 74 | key_children: key_child, 75 | empty_children: empty_children 76 | })[key_child] 77 | 78 | if (key_last) { 79 | this.last(json, key_id, key_last, key_child) 80 | } 81 | return json 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/node.js: -------------------------------------------------------------------------------- 1 | export default class Node { 2 | constructor (content) { 3 | this.content = content 4 | this.children = [] 5 | this.length = 0 6 | } 7 | 8 | get (fieldKey) { 9 | if (typeof this.content[fieldKey] !== 'undefined') { 10 | return this.content[fieldKey] 11 | } 12 | } 13 | 14 | set (fieldKey, value) { 15 | return !!(this.content[fieldKey] = value) 16 | } 17 | 18 | add (child) { 19 | const node = child instanceof Node ? child : new Node(child) 20 | node.parent = this 21 | this.length++ 22 | this.children.push(node) 23 | return node 24 | } 25 | 26 | remove (callback) { 27 | const index = this.children.findIndex(callback) 28 | if (index > -1) { 29 | const removeItems = this.children.splice(index, 1) 30 | this.length-- 31 | return removeItems 32 | } 33 | return [] 34 | } 35 | 36 | sort (compare) { 37 | return this.children.sort(compare) 38 | } 39 | 40 | traversal (criteria, callback) { 41 | criteria = criteria || (() => true) 42 | this.children.filter(criteria).forEach(callback) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/tree-to-list.js: -------------------------------------------------------------------------------- 1 | import LTT from './tree' 2 | 3 | const defaultOptions = { 4 | key_id: 'id', 5 | key_parent: 'parent', 6 | key_child: 'child', 7 | key_last: null, 8 | uuid: false, 9 | empty_children: false 10 | } 11 | 12 | export default class TreeToList { 13 | constructor(tree, options = {}) { 14 | this.options = Object.assign({}, defaultOptions, options) 15 | this.tree = tree 16 | } 17 | 18 | toList (tree) { 19 | let tmp = [] 20 | // console.log(tree) 21 | const { key_child, empty_children } = this.options 22 | for (let n in tree) { 23 | if (tree[n][key_child] && tree[n][key_child].length) { 24 | tmp = [...tmp, ...this.toList(tree[n][key_child])] 25 | } 26 | 27 | if (empty_children) { 28 | delete tree[n][key_child] 29 | } 30 | 31 | tmp.push(tree[n]) 32 | } 33 | return tmp 34 | } 35 | 36 | GetList () { 37 | return this.toList(this.tree) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/tree.js: -------------------------------------------------------------------------------- 1 | import Node from './node' 2 | import { 3 | searchNode, 4 | traversalTree, 5 | serializeTree, 6 | removeEmptyChildren 7 | } from './utils' 8 | 9 | export default class Tree { 10 | constructor (object = undefined) { 11 | this.rootNode = null 12 | if (object) { 13 | this.rootNode = new Node(object) 14 | } 15 | } 16 | 17 | // only for rootNode 18 | get (path) { 19 | return this.rootNode.get(path) 20 | } 21 | 22 | // only for rootNode 23 | set (path, value) { 24 | this.rootNode.set(path, value) 25 | } 26 | 27 | add (callback, object) { 28 | const type = typeof callback 29 | if (type === 'string' && callback === 'root') { 30 | this.rootNode = new Node(object) 31 | return this 32 | } else if (type === 'function') { 33 | const target = searchNode(this, null, callback) 34 | if (target && target.add(object)) { 35 | return this 36 | } else { 37 | console.log('Warning', object) 38 | } 39 | } 40 | } 41 | 42 | contains (criteria) { 43 | return searchNode(this, null, criteria) 44 | } 45 | 46 | remove (criteria) { 47 | const targetNode = this.contains(criteria) 48 | if (targetNode) { 49 | return !!targetNode.parent.remove(criteria) 50 | } 51 | return false 52 | } 53 | 54 | move (search, destination) { 55 | const targetNode = this.contains(search) 56 | if (targetNode && this.remove(search)) { 57 | const destinationNode = this.contains(destination) 58 | return !!destinationNode.add(targetNode) 59 | } 60 | return false 61 | } 62 | 63 | traversal (criteria, callback) { 64 | traversalTree(this, null, criteria, callback) 65 | } 66 | 67 | sort (compare) { 68 | this.traversal(null, currentNode => { 69 | currentNode.sort(compare) 70 | }) 71 | } 72 | 73 | toJson (options = {}) { 74 | const optionsDefault = { 75 | key_children: 'children', 76 | empty_children: true 77 | } 78 | options = Object.assign(optionsDefault, options) 79 | const result = serializeTree(this, null, [], options) 80 | 81 | if (!options.empty_children) { 82 | removeEmptyChildren(result, null, options) 83 | } 84 | 85 | if (result && result.length > 0) { 86 | return result[0] 87 | } else { 88 | return [] 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * node-compare-by-id 3 | * Return callback to compare nodes by id 4 | * @param boolean vector If vector is true then sort asc else desc 5 | * @return function Compare function 6 | */ 7 | export let compareById = vector => { 8 | return (a, b) => { 9 | const aid = Number(a.get('id')) 10 | const bid = Number(b.get('id')) 11 | if (aid > bid) { 12 | return vector ? 1 : -1 13 | } else if (aid < bid) { 14 | return vector ? -1 : 1 15 | } else { 16 | return 0 17 | } 18 | } 19 | } 20 | 21 | /** 22 | * remove-empty-children (for json tree) 23 | * @param {*} jTree 24 | * @param {*} node 25 | * @param {*} options 26 | */ 27 | export let removeEmptyChildren = (jTree, node = null, options) => { 28 | const { key_children } = options 29 | node = node || jTree[0] 30 | if (node[key_children].length === 0) { 31 | delete node[key_children] 32 | } else { 33 | node[key_children].forEach(item => { 34 | removeEmptyChildren(jTree, item, options) 35 | }) 36 | } 37 | } 38 | 39 | /** 40 | * search-node 41 | * @param {*} tree 42 | * @param {*} node 43 | * @param {*} criteria 44 | * @param {*} options 45 | */ 46 | export let searchNode = (tree, node, criteria, options) => { 47 | const currentNode = node || tree.rootNode 48 | if (criteria(currentNode)) { 49 | return currentNode 50 | } 51 | const children = currentNode.children 52 | let target = null 53 | for (let i = 0; i < children.length; i++) { 54 | const item = children[i] 55 | target = searchNode(tree, item, criteria) 56 | if (target) { 57 | return target 58 | } 59 | } 60 | } 61 | 62 | /** 63 | * showTree 64 | * @param {*} tree 65 | * @param {*} node 66 | * @param {*} level 67 | */ 68 | export let showTree = (tree, node = null, level = 1) => { 69 | node = node || tree[0] 70 | if (node && node.content) { 71 | console.log(new Array(level).join('\t'), node.content) 72 | } 73 | if (node && node.children) { 74 | node.children.forEach(item => { 75 | showTree(tree, item, level + 1) 76 | }) 77 | } 78 | } 79 | 80 | /** 81 | * traversal-tree 82 | * @param {*} tree 83 | * @param {*} node 84 | * @param {*} criteria 85 | * @param {*} callback 86 | */ 87 | export let traversalTree = (tree, node = null, criteria, callback) => { 88 | const currentNode = node || tree.rootNode 89 | if (!node) { 90 | if (typeof criteria === 'function' && criteria(currentNode)) { 91 | callback(currentNode) 92 | } else if (criteria === null) { 93 | callback(currentNode) 94 | } 95 | } 96 | currentNode.traversal(criteria, callback) 97 | const children = currentNode.children 98 | 99 | children.forEach(item => { 100 | traversalTree(tree, item, criteria, callback) 101 | }) 102 | } 103 | 104 | /** 105 | * serializeTree 106 | * @param {*} tree 107 | * @param {*} node 108 | * @param {*} target 109 | * @param {*} options 110 | */ 111 | export let serializeTree = (tree, node = null, target = [], options) => { 112 | const { key_children } = options 113 | node = node || tree.rootNode 114 | if (!node) { 115 | return null 116 | } 117 | const index = target.push(Object.assign({ [key_children]: [] }, node.content)) 118 | node.children.forEach(item => { 119 | serializeTree(tree, item, target[index - 1][key_children], options) 120 | }) 121 | return target 122 | } 123 | -------------------------------------------------------------------------------- /test/after_build/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "browsers": ["> 1%", "last 2 versions", "safari >= 7", "not ie <= 8"], 8 | "node": "6.0.0" 9 | } 10 | } 11 | ] 12 | ], 13 | "plugins": ["transform-runtime"], 14 | "env": { 15 | "test": { 16 | "presets": [ 17 | [ 18 | "env", 19 | { 20 | "targets": { 21 | "browsers": [ 22 | "> 1%", 23 | "last 2 versions", 24 | "safari >= 7", 25 | "not ie <= 8" 26 | ], 27 | "node": "6.0.0" 28 | } 29 | } 30 | ] 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/after_build/index.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | require('./test') 3 | -------------------------------------------------------------------------------- /test/after_build/test.js: -------------------------------------------------------------------------------- 1 | import JsTreeList from '../../bin/js-tree-list.min.js' 2 | // const JsTreeList = require('../../bin/js-tree-list.min.js') 3 | console.log(JsTreeList) 4 | var list = [ 5 | { 6 | id: 1, 7 | parent: 0 8 | }, 9 | { 10 | id: 2, 11 | parent: 1 12 | }, 13 | { 14 | id: 3, 15 | parent: 1 16 | }, 17 | { 18 | id: 4, 19 | parent: 2 20 | }, 21 | { 22 | id: 5, 23 | parent: 2 24 | }, 25 | { 26 | id: 6, 27 | parent: 0 28 | }, 29 | { 30 | id: 7, 31 | parent: 0 32 | }, 33 | { 34 | id: 8, 35 | parent: 7 36 | }, 37 | { 38 | id: 9, 39 | parent: 8 40 | }, 41 | { 42 | id: 10, 43 | parent: 0 44 | } 45 | ] 46 | 47 | var ltt = new JsTreeList.ListToTree(list, { 48 | key_id: 'id', 49 | key_parent: 'parent' 50 | }) 51 | 52 | var tree = ltt.GetTree() 53 | 54 | console.log(tree) 55 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "globals": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/jest.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | rootDir: path.resolve(__dirname, '../../'), 5 | moduleFileExtensions: ['js', 'json'], 6 | moduleNameMapper: { 7 | '^@/(.*)$': '/src/$1' 8 | }, 9 | transform: { 10 | '^.+\\.js$': '/node_modules/babel-jest' 11 | }, 12 | testPathIgnorePatterns: [ 13 | '/test/e2e', 14 | '/test/after_build', 15 | '/rollup.config.js', 16 | '/releases.js' 17 | ], 18 | setupFiles: ['/test/unit/setup'], 19 | mapCoverage: true, 20 | coverageDirectory: '/test/unit/coverage', 21 | collectCoverageFrom: ['src/**/*.{js}', '!**/node_modules/**'] 22 | } 23 | -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | // setup.js 2 | -------------------------------------------------------------------------------- /test/unit/test/generate-tree-default.js: -------------------------------------------------------------------------------- 1 | import Tree from '../../../src/tree' 2 | 3 | export default () => { 4 | const object = { id: 1, title: 'Root' } 5 | const tree = new Tree(object) 6 | 7 | const list = [ 8 | { id: 2, parent: 1 }, 9 | { id: 3, parent: 1 }, 10 | { id: 4, parent: 3 }, 11 | { id: 5, parent: 4 }, 12 | { id: 6, parent: 5 }, 13 | { id: 7, parent: 2 }, 14 | { id: 8, parent: 7 } 15 | ] 16 | .map(item => { 17 | item.title = `Node ${item.id}` 18 | return item 19 | }) 20 | .forEach(item => { 21 | tree.add(parentNode => { 22 | return parentNode.get('id') === item.parent 23 | }, item) 24 | }) 25 | 26 | return tree 27 | } 28 | -------------------------------------------------------------------------------- /test/unit/test/index.test.js: -------------------------------------------------------------------------------- 1 | import JsTreeList from '../../../src/index' 2 | 3 | test('Get a Class', () => { 4 | expect(typeof JsTreeList).toMatch('object') 5 | }) 6 | -------------------------------------------------------------------------------- /test/unit/test/list-to-tree-uuid.test.js: -------------------------------------------------------------------------------- 1 | import LTT from '../../../src/list-to-tree' 2 | import { compareById, showTree } from '../../../src/utils' 3 | import uuidv4 from 'uuid/v4' 4 | 5 | describe('UUID List to Tree:', () => { 6 | let tree = null 7 | 8 | beforeEach(() => { 9 | // let list = Array.apply(null, { length: 30 }).map(() => { 10 | // return { 11 | // id: uuidv4(), 12 | // parent: '' 13 | // } 14 | // }) 15 | 16 | let list = [ 17 | { 18 | id: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b', 19 | parent: 0 20 | }, 21 | { 22 | id: 'cb33614c-58d8-4d7d-930d-40bfff15de26', 23 | parent: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b' 24 | }, 25 | { 26 | id: 'd9702c3b-405d-4cc6-ad61-b7bca1efd710', 27 | parent: 'cb33614c-58d8-4d7d-930d-40bfff15de26' 28 | } 29 | ] 30 | 31 | // console.log(list) 32 | 33 | let ltt = new LTT(list, { 34 | key_id: 'id', 35 | key_parent: 'parent', 36 | key_child: 'children', 37 | uuid: true 38 | }) 39 | tree = ltt.GetTree() 40 | }) 41 | 42 | test('It is workly', () => { 43 | expect(tree.length).toBe(1) 44 | }) 45 | 46 | // test('First node check id', () => { 47 | // let firstNode = tree[0] 48 | // expect(firstNode.id).toBe(1) 49 | // }) 50 | 51 | // test('First node check parent', () => { 52 | // let firstNode = tree[0] 53 | // expect(firstNode.parent).toBe(0) 54 | // }) 55 | 56 | // test('First node check child', () => { 57 | // let child = tree[0].child 58 | // expect(child.length).toBe(2) 59 | // }) 60 | 61 | // test('First child - check id', () => { 62 | // let child = tree[0].child 63 | // let node = child[0] 64 | // expect(node.id).toBe(2) 65 | // }) 66 | 67 | // test('First child - check parent', () => { 68 | // let child = tree[0].child 69 | // let node = child[0] 70 | // expect(node.parent).toBe(1) 71 | // }) 72 | 73 | // test('Child node have a child key', () => { 74 | // let child = tree[0].child 75 | // let node = child[0] 76 | // expect('child' in node).toBe(true) 77 | // }) 78 | }) 79 | 80 | describe('ID is Equal:', () => { 81 | let list = [] 82 | 83 | let key_id = 'id' 84 | let key_parent = 'parent' 85 | let key_child = 'children' 86 | 87 | beforeEach(() => { 88 | list = [ 89 | { 90 | id: 1, 91 | parent: 0 92 | }, 93 | { 94 | id: 2, 95 | parent: 1 96 | }, 97 | { 98 | id: 2, 99 | parent: 1 100 | } 101 | ] 102 | }) 103 | 104 | test('Sort tree', () => { 105 | let ltt = new LTT(list, { 106 | key_id, 107 | key_parent, 108 | key_child 109 | }) 110 | ltt.sort(compareById(false)) 111 | const json = ltt.GetTree() 112 | showTree(json) 113 | expect(typeof json).toBe('object') 114 | }) 115 | }) 116 | 117 | describe('UUID List to Tree:', () => { 118 | let tree = null 119 | 120 | beforeEach(() => { 121 | // let listTmp = Array.apply(null, { length: 30 }).map(() => { 122 | // return { 123 | // id: uuidv4(), 124 | // parent: '' 125 | // } 126 | // }) 127 | // console.log(listTmp) 128 | 129 | let list = [ 130 | { 131 | id: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b', 132 | parent: 0, 133 | last: '3b83e600-7115-48d4-85e0-8e422d59ebc8' 134 | }, 135 | { 136 | id: '3b83e600-7115-48d4-85e0-8e422d59ebc8', 137 | parent: 0, 138 | last: 0 139 | }, 140 | { 141 | id: '9d70510c-9c4f-42e3-8858-05457ac65b2c', 142 | parent: 0, 143 | last: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b' 144 | }, 145 | { 146 | id: 'cb33614c-58d8-4d7d-930d-40bfff15de26', 147 | parent: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b', 148 | last: 0, 149 | }, 150 | { 151 | id: 'd9702c3b-405d-4cc6-ad61-b7bca1efd710', 152 | parent: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b', 153 | last: 'cb33614c-58d8-4d7d-930d-40bfff15de26' 154 | }, 155 | { 156 | id: '5cd82d47-fd4e-48b5-a00a-1aaf74f65cca', 157 | parent: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b', 158 | last: '3a501280-9a23-454e-a022-6131c6b9af9b' 159 | }, 160 | { 161 | id: '3a501280-9a23-454e-a022-6131c6b9af9b', 162 | parent: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b', 163 | last: 'd9702c3b-405d-4cc6-ad61-b7bca1efd710' 164 | } 165 | ] 166 | 167 | let ltt = new LTT(list, { 168 | key_id: 'id', 169 | key_parent: 'parent', 170 | key_child: 'children', 171 | key_last: 'last', 172 | uuid: true 173 | }) 174 | tree = ltt.GetTree() 175 | }) 176 | 177 | test('It is workly', () => { 178 | // console.log(tree) 179 | // console.log(tree[1]['children']) 180 | expect(tree.length).toBe(3) 181 | expect(tree[1]['children'].length).toBe(4) 182 | }) 183 | 184 | test('Last Root is ok.', () => { 185 | expect(tree[0]['id']).toBe('3b83e600-7115-48d4-85e0-8e422d59ebc8') 186 | expect(tree[1]['id']).toBe('ca6c9883-005a-4ee5-a84d-34bb19a7818b') 187 | expect(tree[1]['children'][0]['id']).toBe('cb33614c-58d8-4d7d-930d-40bfff15de26') 188 | expect(tree[1]['children'][1]['id']).toBe('d9702c3b-405d-4cc6-ad61-b7bca1efd710') 189 | expect(tree[1]['children'][2]['id']).toBe('3a501280-9a23-454e-a022-6131c6b9af9b') 190 | }) 191 | }) 192 | 193 | describe('UUID List to Tree,parent is null:', () => { 194 | let tree = null 195 | 196 | beforeEach(() => { 197 | let list = [ 198 | { 199 | id: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b', 200 | parent: null 201 | }, 202 | { 203 | id: 'cb33614c-58d8-4d7d-930d-40bfff15de26', 204 | parent: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b' 205 | }, 206 | { 207 | id: 'd9702c3b-405d-4cc6-ad61-b7bca1efd710', 208 | parent: 'cb33614c-58d8-4d7d-930d-40bfff15de26' 209 | } 210 | ] 211 | 212 | // console.log(list) 213 | 214 | let ltt = new LTT(list, { 215 | key_id: 'id', 216 | key_parent: 'parent', 217 | key_child: 'children', 218 | uuid: true 219 | }) 220 | tree = ltt.GetTree() 221 | }) 222 | 223 | test('It is workly', () => { 224 | expect(tree.length).toBe(1) 225 | }) 226 | }) 227 | 228 | describe('UUID List to empty_children is true:', () => { 229 | let tree = null 230 | 231 | beforeEach(() => { 232 | let list = [ 233 | { 234 | id: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b', 235 | parent: null, 236 | children: [] 237 | }, 238 | { 239 | id: 'cb33614c-58d8-4d7d-930d-40bfff15de26', 240 | parent: 'ca6c9883-005a-4ee5-a84d-34bb19a7818b', 241 | children: [] 242 | }, 243 | { 244 | id: 'd9702c3b-405d-4cc6-ad61-b7bca1efd710', 245 | parent: 'cb33614c-58d8-4d7d-930d-40bfff15de26', 246 | children: [] 247 | } 248 | ] 249 | 250 | // console.log(list) 251 | 252 | let ltt = new LTT(list, { 253 | key_id: 'id', 254 | key_parent: 'parent', 255 | key_child: 'children', 256 | empty_children: true, 257 | uuid: true 258 | }) 259 | tree = ltt.GetTree() 260 | }) 261 | 262 | test('It is workly', () => { 263 | console.log(tree[0].children[0].children[0].children.length) 264 | expect(tree.length).toBe(1) 265 | }) 266 | }) -------------------------------------------------------------------------------- /test/unit/test/list-to-tree.test.js: -------------------------------------------------------------------------------- 1 | import LTT from '../../../src/list-to-tree' 2 | import { compareById, showTree } from '../../../src/utils' 3 | 4 | describe('Base usage:', () => { 5 | let tree = null 6 | let key_id = 'id' 7 | let key_parent = 'parent' 8 | 9 | beforeEach(() => { 10 | let list = [ 11 | { 12 | id: 1, 13 | parent: 0 14 | }, 15 | { 16 | id: 2, 17 | parent: 1 18 | }, 19 | { 20 | id: 3, 21 | parent: 1 22 | } 23 | ] 24 | let ltt = new LTT(list, { 25 | key_id: key_id, 26 | key_parent: key_parent 27 | }) 28 | tree = ltt.GetTree() 29 | }) 30 | 31 | test('It is workly', () => { 32 | expect(tree.length).toBe(1) 33 | }) 34 | 35 | test('First node check id', () => { 36 | let firstNode = tree[0] 37 | expect(firstNode[key_id]).toBe(1) 38 | }) 39 | 40 | test('First node check parent', () => { 41 | let firstNode = tree[0] 42 | expect(firstNode[key_parent]).toBe(0) 43 | }) 44 | 45 | test('First node check child', () => { 46 | let child = tree[0].child 47 | expect(child.length).toBe(2) 48 | }) 49 | 50 | test('First child - check id', () => { 51 | let child = tree[0].child 52 | let node = child[0] 53 | expect(node[key_id]).toBe(2) 54 | }) 55 | 56 | test('First child - check parent', () => { 57 | let child = tree[0].child 58 | let node = child[0] 59 | expect(node[key_parent]).toBe(1) 60 | }) 61 | 62 | test('Child node not have a child key', () => { 63 | let child = tree[0].child 64 | let node = child[0] 65 | expect('child' in node).toBe(false) 66 | }) 67 | }) 68 | 69 | describe('Big tree:', () => { 70 | let tree = null 71 | 72 | let key_id = 'id' 73 | 74 | let key_parent = 'parent' 75 | 76 | let key_child = 'child' 77 | 78 | beforeEach(() => { 79 | let list = [ 80 | { 81 | id: 1, 82 | parent: 0 83 | }, 84 | { 85 | id: 2, 86 | parent: 1 87 | }, 88 | { 89 | id: 3, 90 | parent: 1 91 | }, 92 | { 93 | id: 4, 94 | parent: 2 95 | }, 96 | { 97 | id: 5, 98 | parent: 2 99 | }, 100 | { 101 | id: 6, 102 | parent: 0 103 | }, 104 | { 105 | id: 7, 106 | parent: 0 107 | }, 108 | { 109 | id: 8, 110 | parent: 7 111 | }, 112 | { 113 | id: 9, 114 | parent: 8 115 | }, 116 | { 117 | id: 10, 118 | parent: 0 119 | } 120 | ] 121 | let ltt = new LTT(list, { 122 | key_id: key_id, 123 | key_parent: key_parent, 124 | key_child: key_child 125 | }) 126 | tree = ltt.GetTree() 127 | }) 128 | 129 | test('It is workly', () => { 130 | expect(tree.length).toBe(4) 131 | }) 132 | 133 | test('First node check id', () => { 134 | let firstNode = tree[0] 135 | expect(firstNode[key_id]).toBe(1) 136 | }) 137 | 138 | test('First node check parent', () => { 139 | let firstNode = tree[0] 140 | expect(firstNode[key_parent]).toBe(0) 141 | }) 142 | 143 | test('First node check child', () => { 144 | let child = tree[0][key_child] 145 | expect(child.length).toBe(2) 146 | }) 147 | 148 | test('First child - check id', () => { 149 | let child = tree[0][key_child] 150 | let node = child[0] 151 | expect(node[key_id]).toBe(2) 152 | }) 153 | 154 | test('First child - check parent', () => { 155 | let child = tree[0][key_child] 156 | let node = child[0] 157 | expect(node[key_parent]).toBe(1) 158 | }) 159 | 160 | test('Child node have a child key', () => { 161 | let child = tree[0][key_child] 162 | let node = child[0] 163 | expect(key_child in node).toBe(true) 164 | }) 165 | }) 166 | 167 | describe('Default keys:', () => { 168 | let tree = null 169 | 170 | beforeEach(() => { 171 | let list = [ 172 | { 173 | id: 1, 174 | parent: 0 175 | }, 176 | { 177 | id: 2, 178 | parent: 1 179 | }, 180 | { 181 | id: 3, 182 | parent: 1 183 | }, 184 | { 185 | id: 4, 186 | parent: 2 187 | }, 188 | { 189 | id: 5, 190 | parent: 2 191 | }, 192 | { 193 | id: 6, 194 | parent: 0 195 | }, 196 | { 197 | id: 7, 198 | parent: 0 199 | }, 200 | { 201 | id: 8, 202 | parent: 7 203 | }, 204 | { 205 | id: 9, 206 | parent: 8 207 | }, 208 | { 209 | id: 10, 210 | parent: 0 211 | } 212 | ] 213 | let ltt = new LTT(list) 214 | tree = ltt.GetTree() 215 | }) 216 | 217 | test('It is workly', () => { 218 | expect(tree.length).toBe(4) 219 | }) 220 | 221 | test('First node check id', () => { 222 | let firstNode = tree[0] 223 | expect(firstNode.id).toBe(1) 224 | }) 225 | 226 | test('First node check parent', () => { 227 | let firstNode = tree[0] 228 | expect(firstNode.parent).toBe(0) 229 | }) 230 | 231 | test('First node check child', () => { 232 | let child = tree[0].child 233 | expect(child.length).toBe(2) 234 | }) 235 | 236 | test('First child - check id', () => { 237 | let child = tree[0].child 238 | let node = child[0] 239 | expect(node.id).toBe(2) 240 | }) 241 | 242 | test('First child - check parent', () => { 243 | let child = tree[0].child 244 | let node = child[0] 245 | expect(node.parent).toBe(1) 246 | }) 247 | 248 | test('Child node have a child key', () => { 249 | let child = tree[0].child 250 | let node = child[0] 251 | expect('child' in node).toBe(true) 252 | }) 253 | }) 254 | 255 | describe('Disorderly keys:', () => { 256 | let tree = null 257 | 258 | beforeEach(() => { 259 | let list = [ 260 | { 261 | id: 2, 262 | parent: 1 263 | }, 264 | { 265 | id: 3, 266 | parent: 1 267 | }, 268 | { 269 | id: 4, 270 | parent: 2 271 | }, 272 | { 273 | id: 5, 274 | parent: 2 275 | }, 276 | { 277 | id: 6, 278 | parent: 0 279 | }, 280 | { 281 | id: 7, 282 | parent: 0 283 | }, 284 | { 285 | id: 8, 286 | parent: 7 287 | }, 288 | { 289 | id: 9, 290 | parent: 8 291 | }, 292 | { 293 | id: 10, 294 | parent: 0 295 | }, 296 | { 297 | id: 1, 298 | parent: 0 299 | } 300 | ] 301 | let ltt = new LTT(list) 302 | tree = ltt.GetTree() 303 | }) 304 | 305 | test('It is workly', () => { 306 | expect(tree.length).toBe(4) 307 | }) 308 | 309 | test('First node check id', () => { 310 | let firstNode = tree[0] 311 | expect(firstNode.id).toBe(1) 312 | }) 313 | 314 | test('First node check parent', () => { 315 | let firstNode = tree[0] 316 | expect(firstNode.parent).toBe(0) 317 | }) 318 | 319 | test('First node check child', () => { 320 | let child = tree[0].child 321 | expect(child.length).toBe(2) 322 | }) 323 | 324 | test('First child - check id', () => { 325 | let child = tree[0].child 326 | let node = child[0] 327 | expect(node.id).toBe(2) 328 | }) 329 | 330 | test('First child - check parent', () => { 331 | let child = tree[0].child 332 | let node = child[0] 333 | expect(node.parent).toBe(1) 334 | }) 335 | 336 | test('Child node have a child key', () => { 337 | let child = tree[0].child 338 | let node = child[0] 339 | expect('child' in node).toBe(true) 340 | }) 341 | }) 342 | 343 | describe('Other keys:', () => { 344 | let tree = null 345 | 346 | let key_id = 'xid' 347 | 348 | let key_parent = 'xparent' 349 | 350 | let key_child = 'xchild' 351 | 352 | beforeEach(() => { 353 | let list = [ 354 | { 355 | xid: 1, 356 | xparent: 0 357 | }, 358 | { 359 | xid: 2, 360 | xparent: 1 361 | }, 362 | { 363 | xid: 3, 364 | xparent: 1 365 | } 366 | ] 367 | let ltt = new LTT(list, { 368 | key_id: key_id, 369 | key_parent: key_parent, 370 | key_child: key_child 371 | }) 372 | tree = ltt.GetTree() 373 | }) 374 | 375 | test('It is workly', () => { 376 | expect(tree.length).toBe(1) 377 | }) 378 | 379 | test('First node check id', () => { 380 | let firstNode = tree[0] 381 | expect(firstNode[key_id]).toBe(1) 382 | }) 383 | 384 | test('First node check parent', () => { 385 | let firstNode = tree[0] 386 | expect(firstNode[key_parent]).toBe(0) 387 | }) 388 | 389 | test('First node check child', () => { 390 | let child = tree[0][key_child] 391 | expect(child.length).toBe(2) 392 | }) 393 | 394 | test('First child - check id', () => { 395 | let child = tree[0][key_child] 396 | let node = child[0] 397 | expect(node[key_id]).toBe(2) 398 | }) 399 | 400 | test('First child - check parent', () => { 401 | let child = tree[0][key_child] 402 | let node = child[0] 403 | expect(node[key_parent]).toBe(1) 404 | }) 405 | 406 | test('Child node not have a child key', () => { 407 | let child = tree[0][key_child] 408 | let node = child[0] 409 | expect('child' in node).toBe(false) 410 | }) 411 | }) 412 | 413 | describe('Big tree:', () => { 414 | let tree = null 415 | let list = [] 416 | 417 | let key_id = 'id' 418 | 419 | let key_parent = 'parent' 420 | 421 | let key_child = 'child' 422 | 423 | beforeEach(() => { 424 | list = [ 425 | { 426 | id: 1, 427 | parent: 0 428 | }, 429 | { 430 | id: 2, 431 | parent: 1 432 | }, 433 | { 434 | id: 3, 435 | parent: 1 436 | }, 437 | { 438 | id: 4, 439 | parent: 2 440 | }, 441 | { 442 | id: 5, 443 | parent: 2 444 | }, 445 | { 446 | id: 6, 447 | parent: 0 448 | }, 449 | { 450 | id: 7, 451 | parent: 0 452 | }, 453 | { 454 | id: 8, 455 | parent: 7 456 | }, 457 | { 458 | id: 9, 459 | parent: 8 460 | }, 461 | { 462 | id: 10, 463 | parent: 0 464 | } 465 | ] 466 | let ltt = new LTT(list, { 467 | key_id: key_id, 468 | key_parent: key_parent, 469 | key_child: key_child 470 | }) 471 | tree = ltt.GetTree() 472 | }) 473 | 474 | test('Sort tree', () => { 475 | let ltt = new LTT(list, { 476 | key_id: key_id, 477 | key_parent: key_parent, 478 | key_child: key_child 479 | }) 480 | ltt.sort(compareById(false)) 481 | const json = ltt.GetTree() 482 | 483 | expect(json[0].id).toBe(10) 484 | expect(json[json.length - 1].id).toBe(1) 485 | expect(json[1].child[0].id).toBe(8) 486 | }) 487 | }) 488 | 489 | describe('Other tree:', () => { 490 | let tree = null 491 | let list = [] 492 | 493 | let key_id = 'id' 494 | 495 | let key_parent = 'parent' 496 | 497 | let key_child = 'child' 498 | 499 | beforeEach(() => { 500 | list = [ 501 | { 502 | id: 1, 503 | parent: 0, 504 | content: 1 505 | }, 506 | { 507 | id: 2, 508 | parent: 1, 509 | content: 1 510 | }, 511 | { 512 | id: 3, 513 | parent: 1, 514 | content: 2 515 | } 516 | ] 517 | let ltt = new LTT(list, { 518 | key_id: key_id, 519 | key_parent: key_parent, 520 | key_child: key_child 521 | }) 522 | tree = ltt.GetTree() 523 | }) 524 | 525 | test('Sort tree', () => { 526 | let ltt = new LTT(list, { 527 | key_id: key_id, 528 | key_parent: key_parent, 529 | key_child: key_child 530 | }) 531 | ltt.sort(compareById(false)) 532 | const json = ltt.GetTree() 533 | 534 | showTree(json) 535 | expect(json[0].id).toBe(1) 536 | expect(json[json.length - 1].id).toBe(1) 537 | expect(json[0].child[0].id).toBe(3) 538 | }) 539 | }) 540 | 541 | describe('No parent:', () => { 542 | let tree = null 543 | let key_id = 'id' 544 | let key_parent = 'parent' 545 | 546 | beforeEach(() => { 547 | let list = [ 548 | { 549 | id: 1, 550 | parent: '' 551 | }, 552 | { 553 | id: 2, 554 | parent: 0 555 | }, 556 | { 557 | id: 3, 558 | parent: 0 559 | } 560 | ] 561 | let ltt = new LTT(list, { 562 | key_id: key_id, 563 | key_parent: key_parent 564 | }) 565 | tree = ltt.GetTree() 566 | }) 567 | 568 | test('It is workly', () => { 569 | expect(tree.length).toBe(2) 570 | }) 571 | }) 572 | 573 | describe('Parent not equal id:', () => { 574 | let tree = null 575 | let key_id = 'id' 576 | let key_parent = 'parent' 577 | 578 | beforeEach(() => { 579 | let list = [ 580 | { 581 | id: 4, 582 | parent: 0 583 | }, 584 | { 585 | id: 2, 586 | parent: 3 587 | }, 588 | { 589 | id: 3, 590 | parent: 5 591 | } 592 | ] 593 | let ltt = new LTT(list, { 594 | key_id: key_id, 595 | key_parent: key_parent 596 | }) 597 | tree = ltt.GetTree() 598 | }) 599 | 600 | test('It is workly', () => { 601 | expect(tree.length).toBe(1) 602 | }) 603 | }) 604 | -------------------------------------------------------------------------------- /test/unit/test/node.test.js: -------------------------------------------------------------------------------- 1 | import Node from '../../../src/node' 2 | import { compareById } from '../../../src/utils' 3 | 4 | const rootContent = { 5 | id: 1, 6 | name: 'Root' 7 | } 8 | 9 | let node = new Node(rootContent) 10 | 11 | describe('Node', () => { 12 | beforeEach(() => { 13 | node = new Node(rootContent) 14 | }) 15 | 16 | describe('Constructor', () => { 17 | test('Check children and content field', () => { 18 | const { children, content } = node 19 | 20 | expect(Array.isArray(children)).toBe(true) 21 | expect(children).toHaveLength(0) 22 | expect(content.name).toEqual(rootContent.name) 23 | }) 24 | 25 | test('Check correct work getter', () => { 26 | expect(rootContent.name).toEqual(node.get('name')) 27 | expect(node.get('lastname')).toEqual(undefined) 28 | }) 29 | }) 30 | 31 | describe('Get', () => { 32 | test('Method get with correct path', () => { 33 | expect(node.get('id')).toEqual(1) 34 | expect(node.get('name')).toEqual('Root') 35 | }) 36 | 37 | test('Method get with incorrect path', () => { 38 | expect(node.get('uid')).toEqual(undefined) 39 | }) 40 | }) 41 | 42 | describe('Set', () => { 43 | test('Method set with correct path', () => { 44 | expect(node.set('id', 100)).toEqual(true) 45 | expect(node.get('id')).toEqual(100) 46 | }) 47 | 48 | test('Method set with incorrect path', () => { 49 | expect(node.set('uid', 101)).toEqual(true) 50 | expect(node.get('uid')).toEqual(101) 51 | }) 52 | }) 53 | 54 | describe('Add', () => { 55 | test('Add one node', () => { 56 | const childNode = node.add({ id: 2, name: 'Two node' }) 57 | 58 | expect(childNode instanceof Node).toEqual(true) 59 | expect(node.children).toHaveLength(1) 60 | expect(node.length).toEqual(1) 61 | }) 62 | 63 | test('Add nodes', () => { 64 | node.add({ id: 2, name: 'Two node' }) 65 | node.add({ id: 3, name: 'Three node' }) 66 | 67 | expect(node.children).toHaveLength(2) 68 | expect(node.length).toEqual(2) 69 | }) 70 | }) 71 | 72 | describe('Remove', () => { 73 | test('Remove exists child node', () => { 74 | node.add({ id: 2, name: 'Two node' }) 75 | node.add({ id: 3, name: 'Three node' }) 76 | const removedNodes = node.remove(itemNode => { 77 | return itemNode.get('id') === 3 78 | }) 79 | 80 | expect(node.length).toEqual(1) 81 | expect(removedNodes.length).toEqual(1) 82 | }) 83 | 84 | test('Remove not exists node', () => { 85 | node.add({ id: 2, name: 'Two node' }) 86 | node.add({ id: 3, name: 'Three node' }) 87 | const removedNodes = node.remove(itemNode => { 88 | return itemNode.get('id') === 333 89 | }) 90 | 91 | expect(node.length).toEqual(2) 92 | expect(removedNodes.length).toEqual(0) 93 | }) 94 | }) 95 | 96 | describe('Sort', () => { 97 | test('Order desc', () => { 98 | node.add({ id: 2, name: 'Two node' }) 99 | node.add({ id: 3, name: 'Three node' }) 100 | node.add({ id: 15, name: 'Fifteen node' }) 101 | node.add({ id: 4, name: 'Four node' }) 102 | node.sort(compareById(false)) 103 | 104 | expect(node.children[0].get('id')).toEqual(15) 105 | expect(node.children[1].get('id')).toEqual(4) 106 | expect(node.children[2].get('id')).toEqual(3) 107 | expect(node.children[3].get('id')).toEqual(2) 108 | }) 109 | 110 | test('Order asc', () => { 111 | node.add({ id: 2, name: 'Two node' }) 112 | node.add({ id: 3, name: 'Three node' }) 113 | node.add({ id: 15, name: 'Fifteen node' }) 114 | node.add({ id: 4, name: 'Four node' }) 115 | node.sort(compareById(true)) 116 | 117 | expect(node.children[0].get('id')).toEqual(2) 118 | expect(node.children[1].get('id')).toEqual(3) 119 | expect(node.children[2].get('id')).toEqual(4) 120 | expect(node.children[3].get('id')).toEqual(15) 121 | }) 122 | }) 123 | 124 | describe('Traversal', () => { 125 | test('Change name for each child', () => { 126 | node.add({ id: 2, name: 'Two node' }) 127 | node.add({ id: 3, name: 'Three node' }) 128 | node.add({ id: 15, name: 'Fifteen node' }) 129 | node.add({ id: 4, name: 'Four node' }) 130 | node.traversal(null, currentNode => { 131 | const name = currentNode.get('name') 132 | currentNode.set('name', `${name}!`) 133 | }) 134 | expect(node.children[0].get('name')).toEqual('Two node!') 135 | expect(node.children[1].get('name')).toEqual('Three node!') 136 | }) 137 | 138 | test('Change name for item with id is 3', () => { 139 | node.add({ id: 2, name: 'Two node' }) 140 | node.add({ id: 3, name: 'Three node' }) 141 | node.add({ id: 15, name: 'Fifteen node' }) 142 | node.add({ id: 4, name: 'Four node' }) 143 | node.traversal( 144 | currentNode => currentNode.get('id') === 3, 145 | currentNode => { 146 | const name = currentNode.get('name') 147 | currentNode.set('name', `${name}!`) 148 | } 149 | ) 150 | expect(node.children[0].get('name')).toEqual('Two node') 151 | expect(node.children[1].get('name')).toEqual('Three node!') 152 | }) 153 | }) 154 | }) 155 | -------------------------------------------------------------------------------- /test/unit/test/tree-to-list.test.js: -------------------------------------------------------------------------------- 1 | import LTT from '../../../src/list-to-tree' 2 | import TTL from '../../../src/tree-to-list' 3 | 4 | describe('Base usage:', () => { 5 | let tree = null 6 | let list = null 7 | const key_id = 'id' 8 | const key_parent = 'parent' 9 | const key_child = 'children' 10 | 11 | beforeEach(() => { 12 | let originalList = [ 13 | { 14 | id: 1, 15 | parent: 0 16 | }, 17 | { 18 | id: 2, 19 | parent: 1 20 | }, 21 | { 22 | id: 3, 23 | parent: 1 24 | }, 25 | { 26 | id: 4, 27 | parent: 2 28 | }, 29 | { 30 | id: 5, 31 | parent: 4 32 | } 33 | ] 34 | 35 | tree = new LTT(originalList, { 36 | key_id, 37 | key_parent, 38 | key_child 39 | }).GetTree() 40 | 41 | list = new TTL(tree, { 42 | key_id, 43 | key_parent, 44 | key_child 45 | }).GetList() 46 | }) 47 | 48 | test('LTT is workly', () => { 49 | expect(tree.length).toBe(1) 50 | }) 51 | 52 | test('TTL is workly', () => { 53 | expect(list.length).toBe(5) 54 | }) 55 | 56 | test('Empty Children', () => { 57 | let listNoChild = new TTL(tree, { 58 | key_id, 59 | key_parent, 60 | key_child, 61 | empty_children: true 62 | }).GetList() 63 | 64 | let noChild = true 65 | for (const n in listNoChild) { 66 | if (listNoChild[n][key_child]) { 67 | noChild = false 68 | break 69 | } 70 | } 71 | expect(noChild).toBe(true) 72 | }) 73 | 74 | test('No Empty Children', () => { 75 | let listHaveChild = new TTL(tree, { 76 | key_id, 77 | key_parent, 78 | key_child, 79 | empty_children: false 80 | }).GetList() 81 | 82 | let noChild = true 83 | for (const n in listHaveChild) { 84 | if (listHaveChild[n][key_child]) { 85 | noChild = false 86 | break 87 | } 88 | } 89 | expect(noChild).toBe(false) 90 | }) 91 | }) -------------------------------------------------------------------------------- /test/unit/test/tree.test.js: -------------------------------------------------------------------------------- 1 | import Node from '../../../src/node' 2 | import Tree from '../../../src/tree' 3 | import { showTree, compareById, traversalTree } from '../../../src/utils' 4 | import generateTreeDefault from './generate-tree-default' 5 | 6 | let object = { id: 1, title: 'Root' } 7 | let tree = new Tree(object) 8 | 9 | describe('Tree', () => { 10 | beforeEach(() => { 11 | object = { id: 1, title: 'Root' } 12 | tree = new Tree(object) 13 | }) 14 | 15 | describe('Constructor', () => { 16 | test('It exists', () => { 17 | expect(tree instanceof Tree).toEqual(true) 18 | }) 19 | 20 | test('With params', () => { 21 | expect(tree.rootNode instanceof Node).toEqual(true) 22 | }) 23 | 24 | test('Without params', () => { 25 | const tree = new Tree() 26 | 27 | expect(tree.rootNode instanceof Node).toEqual(false) 28 | expect(tree.rootNode).toEqual(null) 29 | }) 30 | }) 31 | 32 | describe('Add', () => { 33 | test('Add root', () => { 34 | const tree = new Tree() 35 | const resultTree = tree.add('root', object) 36 | 37 | expect(resultTree instanceof Tree).toEqual(true) 38 | expect(resultTree.rootNode instanceof Node).toEqual(true) 39 | }) 40 | 41 | test('Add regular node', () => { 42 | const regularObject = { id: 2, title: 'Node 2' } 43 | const resultTree = tree.add(parentNode => { 44 | return parentNode.get('id') === 1 45 | }, regularObject) 46 | 47 | expect(resultTree instanceof Tree).toEqual(true) 48 | expect(resultTree.rootNode instanceof Node).toEqual(true) 49 | 50 | expect(resultTree.rootNode.children).toHaveLength(1) 51 | expect(resultTree.rootNode.children[0].get('id')).toEqual(2) 52 | }) 53 | 54 | test('Add many nodes', () => { 55 | tree = generateTreeDefault() 56 | 57 | expect(tree instanceof Tree).toEqual(true) 58 | expect(tree.rootNode instanceof Node).toEqual(true) 59 | 60 | expect(tree.rootNode.get('id')).toEqual(1) 61 | expect(tree.rootNode.children[0].get('id')).toEqual(2) 62 | expect(tree.rootNode.children[1].get('id')).toEqual(3) 63 | 64 | expect(tree.rootNode.children[1].children[0].get('id')).toEqual(4) 65 | expect( 66 | tree.rootNode.children[1].children[0].children[0].get('id') 67 | ).toEqual(5) 68 | expect( 69 | tree.rootNode.children[1].children[0].children[0].children[0].get('id') 70 | ).toEqual(6) 71 | 72 | // showTree(tree); 73 | }) 74 | }) 75 | 76 | describe('Contains', () => { 77 | test('Search element when he does exists', () => { 78 | tree = generateTreeDefault() 79 | const targetNode = tree.contains(currentNode => { 80 | return currentNode.get('id') === 7 81 | }) 82 | 83 | expect(targetNode instanceof Node).toEqual(true) 84 | expect(targetNode.get('id')).toEqual(7) 85 | }) 86 | 87 | test('Search element when he does not exists', () => { 88 | tree = generateTreeDefault() 89 | const targetNode = tree.contains(currentNode => { 90 | return currentNode.get('id') === 100 91 | }) 92 | 93 | expect(targetNode).toEqual(undefined) 94 | }) 95 | }) 96 | 97 | describe('Remove', () => { 98 | test('Remove correct criteria', () => { 99 | tree = generateTreeDefault() 100 | const result = tree.remove(currentNode => { 101 | return currentNode.get('id') === 7 102 | }) 103 | const targetNode = tree.contains(currentNode => { 104 | return currentNode.get('id') === 7 105 | }) 106 | 107 | expect(result).toEqual(true) 108 | expect(targetNode).toEqual(undefined) 109 | }) 110 | 111 | test('Remove incorrect criteria', () => { 112 | tree = generateTreeDefault() 113 | const result = tree.remove(currentNode => { 114 | return currentNode.get('id') === 100 115 | }) 116 | const targetNode = tree.contains(currentNode => { 117 | return currentNode.get('id') === 100 118 | }) 119 | 120 | expect(result).toEqual(false) 121 | expect(targetNode).toEqual(undefined) 122 | }) 123 | }) 124 | 125 | describe('Move', () => { 126 | test('Move exists branch', () => { 127 | tree = generateTreeDefault() 128 | const search = currentNode => currentNode.get('id') === 7 129 | const destination = currentNode => currentNode.get('id') === 3 130 | const result = tree.move(search, destination) 131 | const targetNode = tree.contains(search) 132 | 133 | expect(result).toEqual(true) 134 | expect(targetNode.get('id')).toEqual(7) 135 | expect(targetNode.parent.get('id')).toEqual(3) 136 | 137 | // showTree(tree); 138 | }) 139 | 140 | test('Move not exists branch', () => { 141 | tree = generateTreeDefault() 142 | const search = currentNode => currentNode.get('id') === 100 143 | const destination = currentNode => currentNode.get('id') === 3 144 | const result = tree.move(search, destination) 145 | const targetNode = tree.contains(search) 146 | 147 | expect(result).toEqual(false) 148 | expect(targetNode).toEqual(undefined) 149 | }) 150 | }) 151 | 152 | describe('Traversal', () => { 153 | test('Add new field for item.id === 7', () => { 154 | tree = generateTreeDefault() 155 | const criteria = currentNode => currentNode.get('id') === 7 156 | tree.traversal(criteria, currentNode => { 157 | currentNode.set('some', true) 158 | }) 159 | // showTree(tree); 160 | tree.traversal(null, currentNode => { 161 | const some = currentNode.get('some') 162 | expect(some).toEqual(currentNode.get('id') === 7 ? true : undefined) 163 | }) 164 | }) 165 | 166 | test('Add new property for each node', () => { 167 | tree = generateTreeDefault() 168 | tree.traversal(null, currentNode => { 169 | currentNode.set('some', true) 170 | }) 171 | 172 | tree.traversal(null, currentNode => { 173 | const some = currentNode.get('some') 174 | expect(some).toEqual(true) 175 | }) 176 | }) 177 | 178 | test('Add new property only for even nodes', () => { 179 | tree = generateTreeDefault() 180 | tree.traversal(null, currentNode => { 181 | if (currentNode.get('id') % 2 === 0) { 182 | currentNode.set('some', true) 183 | } 184 | }) 185 | 186 | tree.traversal(null, currentNode => { 187 | const some = currentNode.get('some') 188 | if (currentNode.get('id') % 2 === 0) { 189 | expect(some).toEqual(true) 190 | } else { 191 | expect(some).toEqual(undefined) 192 | } 193 | }) 194 | }) 195 | 196 | test('typeof criteria is function', () => { 197 | tree = new Tree({}) 198 | 199 | traversalTree( 200 | tree, 201 | null, 202 | currentNode => { 203 | return true 204 | }, 205 | currentNode => { 206 | expect(typeof currentNode).toEqual('object') 207 | } 208 | ) 209 | }) 210 | }) 211 | 212 | describe('Sort', () => { 213 | test('Order desc', () => { 214 | tree = generateTreeDefault() 215 | tree.sort(compareById(false)) 216 | 217 | // showTree(tree); 218 | expect(tree.rootNode.children[0].get('id')).toEqual(3) 219 | expect(tree.rootNode.children[1].get('id')).toEqual(2) 220 | }) 221 | 222 | test('Order asc', () => { 223 | tree = generateTreeDefault() 224 | tree.sort(compareById(false)) 225 | tree.sort(compareById(true)) 226 | 227 | // showTree(tree); 228 | expect(tree.rootNode.children[0].get('id')).toEqual(2) 229 | expect(tree.rootNode.children[1].get('id')).toEqual(3) 230 | }) 231 | }) 232 | 233 | describe('toJson', () => { 234 | test('Searialize tree to json', () => { 235 | tree = generateTreeDefault() 236 | const json = tree.toJson() 237 | 238 | expect(json.id).toEqual(1) 239 | expect(json.children[0].id).toEqual(2) 240 | expect(json.children[0].children[0].id).toEqual(7) 241 | expect(json.children[0].children[0].children[0].id).toEqual(8) 242 | expect(json.children[1].id).toEqual(3) 243 | expect(json.children[1].children[0].id).toEqual(4) 244 | expect(json.children[1].children[0].children[0].id).toEqual(5) 245 | }) 246 | 247 | test('Searialize tree to json after sort desc', () => { 248 | tree = generateTreeDefault() 249 | tree.sort(compareById(false)) 250 | const json = tree.toJson() 251 | 252 | expect(json.id).toEqual(1) 253 | expect(json.children[1].id).toEqual(2) 254 | expect(json.children[1].children[0].id).toEqual(7) 255 | expect(json.children[1].children[0].children[0].id).toEqual(8) 256 | expect(json.children[0].id).toEqual(3) 257 | expect(json.children[0].children[0].id).toEqual(4) 258 | expect(json.children[0].children[0].children[0].id).toEqual(5) 259 | }) 260 | 261 | test('Searialize tree to json after remove element', () => { 262 | tree = generateTreeDefault() 263 | tree.remove(parentNode => parentNode.get('id') === 2) 264 | const json = tree.toJson() 265 | 266 | expect(json.id).toEqual(1) 267 | expect(json.children[0].id).toEqual(3) 268 | expect(json.children[0].children[0].id).toEqual(4) 269 | expect(json.children[0].children[0].children[0].id).toEqual(5) 270 | }) 271 | 272 | test('Searialize tree to json with options: key_children=child', () => { 273 | tree = generateTreeDefault() 274 | const json = tree.toJson({ 275 | key_children: 'child' 276 | }) 277 | 278 | expect(json.id).toEqual(1) 279 | expect(json.child[0].id).toEqual(2) 280 | expect(json.child[0].child[0].id).toEqual(7) 281 | expect(json.child[0].child[0].child[0].id).toEqual(8) 282 | expect(json.child[1].id).toEqual(3) 283 | expect(json.child[1].child[0].id).toEqual(4) 284 | }) 285 | 286 | describe('Options', () => { 287 | test('Flag: empty_children', () => { 288 | tree = generateTreeDefault() 289 | const json = tree.toJson({ 290 | empty_children: false 291 | }) 292 | 293 | expect(json.children[0].children[0].children[0].id).toEqual(8) 294 | expect(json.children[0].children[0].children[0].children).toEqual( 295 | undefined 296 | ) 297 | }) 298 | }) 299 | }) 300 | 301 | describe('toJson back [] or Tree is null', () => { 302 | test('Searialize tree to json', () => { 303 | let tree = new Tree() 304 | const json = tree.toJson() 305 | expect(typeof json).toEqual('object') 306 | }) 307 | }) 308 | 309 | describe('Options', () => { 310 | test('Flags: key_id and key_parent', () => { 311 | const object = { uid: 1, title: 'Root' } 312 | const tree = new Tree(object) 313 | 314 | const list = [ 315 | { uid: 2, _parent: 1 }, 316 | { uid: 3, _parent: 1 }, 317 | { uid: 4, _parent: 3 }, 318 | { uid: 5, _parent: 4 }, 319 | { uid: 6, _parent: 5 }, 320 | { uid: 7, _parent: 2 }, 321 | { uid: 8, _parent: 7 } 322 | ] 323 | .map(item => { 324 | item.title = `Node ${item.uid}` 325 | return item 326 | }) 327 | .forEach(item => { 328 | tree.add(parentNode => { 329 | return parentNode.get('uid') === item._parent 330 | }, item) 331 | }) 332 | 333 | // showTree(tree); 334 | // console.log(tree.toJson({ key_children: 'child' })); 335 | 336 | expect(tree instanceof Tree).toEqual(true) 337 | expect(tree.rootNode instanceof Node).toEqual(true) 338 | 339 | expect(tree.rootNode.get('uid')).toEqual(1) 340 | expect(tree.rootNode.children[0].get('uid')).toEqual(2) 341 | expect(tree.rootNode.children[1].get('uid')).toEqual(3) 342 | 343 | expect(tree.rootNode.children[1].children[0].get('uid')).toEqual(4) 344 | expect( 345 | tree.rootNode.children[1].children[0].children[0].get('uid') 346 | ).toEqual(5) 347 | expect( 348 | tree.rootNode.children[1].children[0].children[0].children[0].get('uid') 349 | ).toEqual(6) 350 | }) 351 | }) 352 | 353 | describe('Get', () => { 354 | test('Regular', () => { 355 | expect(tree.get('id')).toEqual(1) 356 | }) 357 | 358 | test('If not exists property', () => { 359 | expect(tree.get('uid')).toEqual(undefined) 360 | }) 361 | }) 362 | 363 | describe('Set', () => { 364 | test('Regular set', () => { 365 | expect(tree.get('id')).toEqual(1) 366 | tree.set('id', 101) 367 | expect(tree.get('id')).toEqual(101) 368 | }) 369 | 370 | test('Add new property', () => { 371 | expect(tree.get('some')).toEqual(undefined) 372 | tree.set('some', true) 373 | expect(tree.get('some')).toEqual(true) 374 | }) 375 | }) 376 | 377 | describe('Hide load', () => { 378 | test('2000 items', () => { 379 | function getRandomInt (min, max) { 380 | return Math.floor(Math.random() * (max - min + 1)) + min 381 | } 382 | const list = new Array(10000).fill().map((item, index) => { 383 | return { 384 | id: index + 1, 385 | parent: getRandomInt(0, index) 386 | } 387 | }) 388 | 389 | tree = new Tree({ id: 0 }) 390 | list.forEach((item, index) => { 391 | tree.add(parentNode => { 392 | return parentNode.get('id') === item.parent 393 | }, item) 394 | }) 395 | const jTree = tree.toJson({ 396 | empty_children: false 397 | }) 398 | }) 399 | }) 400 | }) 401 | --------------------------------------------------------------------------------