├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── lib ├── SymbolTree.js ├── SymbolTreeNode.js ├── TreeIterator.js └── TreePosition.js ├── package.json ├── readme-header.md └── test └── SymbolTree.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 8 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "globals": {}, 8 | "parser": "babel-eslint", 9 | "parserOptions": { 10 | "ecmaVersion": 7, 11 | "sourceType": "script" 12 | }, 13 | "plugins": [ 14 | "import" 15 | ], 16 | "rules": { 17 | "array-bracket-spacing": [ 18 | "error", 19 | "never" 20 | ], 21 | "array-callback-return": "error", 22 | "arrow-parens": "off", 23 | "arrow-spacing": "error", 24 | "brace-style": [ 25 | "error", 26 | "stroustrup", 27 | { 28 | "allowSingleLine": true 29 | } 30 | ], 31 | "camelcase": [ 32 | "error", 33 | { 34 | "properties": "always" 35 | } 36 | ], 37 | "comma-dangle": [ 38 | "error", 39 | "always-multiline" 40 | ], 41 | "comma-spacing": [ 42 | "error", 43 | { 44 | "after": true 45 | } 46 | ], 47 | "comma-style": [ 48 | "error", 49 | "last" 50 | ], 51 | "complexity": "warn", 52 | "consistent-return": "error", 53 | "constructor-super": "error", 54 | "curly": "error", 55 | "dot-location": [ 56 | "error", 57 | "property" 58 | ], 59 | "dot-notation": [ 60 | "error", 61 | { 62 | "allowPattern": "^[a-z]+(_[a-z]+)+$" 63 | } 64 | ], 65 | "eol-last": "error", 66 | "eqeqeq": "error", 67 | "generator-star-spacing": [ 68 | "error", 69 | { 70 | "after": true, 71 | "before": false 72 | } 73 | ], 74 | "global-require": "error", 75 | "import/default": "error", 76 | "import/export": "error", 77 | "import/first": "error", 78 | "import/named": "error", 79 | "import/namespace": "error", 80 | "import/newline-after-import": "error", 81 | "import/no-absolute-path": "error", 82 | "import/no-extraneous-dependencies": [ 83 | "error", { 84 | "devDependencies": [ 85 | "test/**", 86 | "webpack.config.js" 87 | ] 88 | } 89 | ], 90 | "import/no-mutable-exports": "error", 91 | "import/no-named-default": "error", 92 | "import/no-unassigned-import": "error", 93 | "import/no-webpack-loader-syntax": "error", 94 | "import/order": [ 95 | "error", 96 | { 97 | "groups": [ 98 | ["builtin", "external"], 99 | ["index","sibling","parent","internal"] 100 | ], 101 | "newlines-between": "always" 102 | } 103 | ], 104 | "indent": [ 105 | "error", 106 | 8, 107 | { 108 | "SwitchCase": 1 109 | } 110 | ], 111 | "key-spacing": [ 112 | "error", 113 | { 114 | "afterColon": true, 115 | "beforeColon": false, 116 | "mode": "minimum" 117 | } 118 | ], 119 | "keyword-spacing": "error", 120 | "linebreak-style": [ 121 | "error", 122 | "unix" 123 | ], 124 | "max-len": [ 125 | "error", 126 | 140 127 | ], 128 | "max-nested-callbacks": [ 129 | "error", 130 | 4 131 | ], 132 | "max-params": [ 133 | "error", 134 | 5 135 | ], 136 | "new-cap": [ 137 | "error", 138 | { 139 | "capIsNewExceptions": [ 140 | "Array", 141 | "Boolean", 142 | "Error", 143 | "Number", 144 | "PageMod", 145 | "Object", 146 | "QueryInterface", 147 | "String", 148 | "Symbol" 149 | ] 150 | } 151 | ], 152 | "no-array-constructor": "error", 153 | "no-caller": "error", 154 | "no-case-declarations": "error", 155 | "no-catch-shadow": "error", 156 | "no-class-assign": "error", 157 | "no-cond-assign": [ 158 | "error", 159 | "except-parens" 160 | ], 161 | "no-confusing-arrow": [ 162 | "error", 163 | { 164 | "allowParens": true 165 | } 166 | ], 167 | "no-const-assign": "error", 168 | "no-div-regex": "error", 169 | "no-dupe-args": "error", 170 | "no-dupe-class-members": "error", 171 | "no-dupe-keys": "error", 172 | "no-duplicate-case": "error", 173 | "no-else-return": "error", 174 | "no-empty-character-class": "error", 175 | "no-empty-pattern": "error", 176 | "no-eval": "error", 177 | "no-ex-assign": "error", 178 | "no-fallthrough": "error", 179 | "no-floating-decimal": "error", 180 | "no-func-assign": "error", 181 | "no-implicit-coercion": [ 182 | "error", 183 | { 184 | "boolean": true, 185 | "number": true, 186 | "string": true 187 | } 188 | ], 189 | "no-implied-eval": "error", 190 | "no-inner-declarations": "error", 191 | "no-invalid-regexp": "error", 192 | "no-iterator": "error", 193 | "no-lonely-if": "error", 194 | "no-loop-func": "error", 195 | "no-multi-str": "error", 196 | "no-native-reassign": "error", 197 | "no-negated-in-lhs": "error", 198 | "no-new-func": "error", 199 | "no-new-object": "error", 200 | "no-new-symbol": "error", 201 | "no-new-wrappers": "error", 202 | "no-obj-calls": "error", 203 | "no-octal": "error", 204 | "no-octal-escape": "error", 205 | "no-param-reassign": "error", 206 | "no-proto": "error", 207 | "no-prototype-builtins": "error", 208 | "no-regex-spaces": "error", 209 | "no-return-assign": "error", 210 | "no-self-assign": "error", 211 | "no-self-compare": "error", 212 | "no-spaced-func": "error", 213 | "no-sparse-arrays": "error", 214 | "no-tabs": "error", 215 | "no-this-before-super": "error", 216 | "no-throw-literal": "error", 217 | "no-trailing-spaces": "error", 218 | "no-undef": "error", 219 | "no-unexpected-multiline": "error", 220 | "no-unmodified-loop-condition": "error", 221 | "no-unreachable": "error", 222 | "no-unsafe-finally": "error", 223 | "no-unsafe-negation": "error", 224 | "no-unused-vars": [ 225 | "error", 226 | { 227 | "args": "none" 228 | } 229 | ], 230 | "no-useless-call": "error", 231 | "no-useless-computed-key": "error", 232 | "no-var": "error", 233 | "no-void": "error", 234 | "no-with": "error", 235 | "object-curly-spacing": [ 236 | "error", 237 | "never" 238 | ], 239 | "one-var": [ 240 | "error", 241 | "never" 242 | ], 243 | "prefer-arrow-callback": "error", 244 | "prefer-const": "error", 245 | "prefer-rest-params": "error", 246 | "prefer-spread": "error", 247 | "quote-props": [ 248 | "error", 249 | "as-needed" 250 | ], 251 | "quotes": [ 252 | "error", 253 | "single", 254 | { 255 | "allowTemplateLiterals": true 256 | } 257 | ], 258 | "radix": "error", 259 | "rest-spread-spacing": "error", 260 | "semi": [ 261 | "error", 262 | "always" 263 | ], 264 | "semi-spacing": [ 265 | "error", 266 | { 267 | "after": true, 268 | "before": false 269 | } 270 | ], 271 | "sort-keys": [ 272 | "error", 273 | "asc", 274 | { 275 | "caseSensitive": true, 276 | "natural": true 277 | } 278 | ], 279 | "space-before-blocks": [ 280 | "error", 281 | "always" 282 | ], 283 | "space-before-function-paren": [ 284 | "error", 285 | { 286 | "anonymous": "ignore", 287 | "named": "never" 288 | } 289 | ], 290 | "space-in-parens": [ 291 | "error", 292 | "never" 293 | ], 294 | "space-infix-ops": "error", 295 | "space-unary-ops": [ 296 | "error", 297 | { 298 | "nonwords": false, 299 | "words": false 300 | } 301 | ], 302 | "spaced-comment": [ 303 | "error", 304 | "always" 305 | ], 306 | "strict": "error", 307 | "unicode-bom": "error", 308 | "use-isnan": "error", 309 | "valid-jsdoc": [ 310 | "error", 311 | { 312 | "prefer": { 313 | "arg": "param", 314 | "argument": "param", 315 | "class": "constructor", 316 | "returns": "return", 317 | "virtual": "abstract" 318 | }, 319 | "requireParamDescription": false, 320 | "requireReturn": false, 321 | "requireReturnDescription": false 322 | } 323 | ], 324 | "valid-typeof": [ 325 | "error", 326 | { 327 | "requireStringLiterals": true 328 | } 329 | ], 330 | "wrap-iife": "error", 331 | "yoda": [ 332 | "error", 333 | "never" 334 | ] 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /jsdoc 3 | /coverage 4 | /.idea 5 | /symbol-tree.iml 6 | /npm-debug.log 7 | /package-lock.json 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | install: 5 | - npm install 6 | script: npm run ci 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Joris van der Wel 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | symbol-tree 2 | =========== 3 | [![Travis CI Build Status](https://api.travis-ci.org/jsdom/js-symbol-tree.svg?branch=master)](https://travis-ci.org/jsdom/js-symbol-tree) [![Coverage Status](https://coveralls.io/repos/github/jsdom/js-symbol-tree/badge.svg?branch=master)](https://coveralls.io/github/jsdom/js-symbol-tree?branch=master) 4 | 5 | Turn any collection of objects into its own efficient tree or linked list using `Symbol`. 6 | 7 | This library has been designed to provide an efficient backing data structure for DOM trees. You can also use this library as an efficient linked list. Any meta data is stored on your objects directly, which ensures any kind of insertion or deletion is performed in constant time. Because an ES6 `Symbol` is used, the meta data does not interfere with your object in any way. 8 | 9 | Node.js 4+, io.js and modern browsers are supported. 10 | 11 | Example 12 | ------- 13 | A linked list: 14 | 15 | ```javascript 16 | const SymbolTree = require('symbol-tree'); 17 | const tree = new SymbolTree(); 18 | 19 | let a = {foo: 'bar'}; // or `new Whatever()` 20 | let b = {foo: 'baz'}; 21 | let c = {foo: 'qux'}; 22 | 23 | tree.insertBefore(b, a); // insert a before b 24 | tree.insertAfter(b, c); // insert c after b 25 | 26 | console.log(tree.nextSibling(a) === b); 27 | console.log(tree.nextSibling(b) === c); 28 | console.log(tree.previousSibling(c) === b); 29 | 30 | tree.remove(b); 31 | console.log(tree.nextSibling(a) === c); 32 | ``` 33 | 34 | A tree: 35 | 36 | ```javascript 37 | const SymbolTree = require('symbol-tree'); 38 | const tree = new SymbolTree(); 39 | 40 | let parent = {}; 41 | let a = {}; 42 | let b = {}; 43 | let c = {}; 44 | 45 | tree.prependChild(parent, a); // insert a as the first child 46 | tree.appendChild(parent,c ); // insert c as the last child 47 | tree.insertAfter(a, b); // insert b after a, it now has the same parent as a 48 | 49 | console.log(tree.firstChild(parent) === a); 50 | console.log(tree.nextSibling(tree.firstChild(parent)) === b); 51 | console.log(tree.lastChild(parent) === c); 52 | 53 | let grandparent = {}; 54 | tree.prependChild(grandparent, parent); 55 | console.log(tree.firstChild(tree.firstChild(grandparent)) === a); 56 | ``` 57 | 58 | See [api.md](api.md) for more documentation. 59 | 60 | Testing 61 | ------- 62 | Make sure you install the dependencies first: 63 | 64 | npm install 65 | 66 | You can now run the unit tests by executing: 67 | 68 | npm test 69 | 70 | The line and branch coverage should be 100%. 71 | 72 | API Documentation 73 | ----------------- 74 | 75 | 76 | ## symbol-tree 77 | **Author**: Joris van der Wel 78 | 79 | * [symbol-tree](#module_symbol-tree) 80 | * [SymbolTree](#exp_module_symbol-tree--SymbolTree) ⏏ 81 | * [new SymbolTree([description])](#new_module_symbol-tree--SymbolTree_new) 82 | * [.initialize(object)](#module_symbol-tree--SymbolTree+initialize) ⇒ Object 83 | * [.hasChildren(object)](#module_symbol-tree--SymbolTree+hasChildren) ⇒ Boolean 84 | * [.firstChild(object)](#module_symbol-tree--SymbolTree+firstChild) ⇒ Object 85 | * [.lastChild(object)](#module_symbol-tree--SymbolTree+lastChild) ⇒ Object 86 | * [.previousSibling(object)](#module_symbol-tree--SymbolTree+previousSibling) ⇒ Object 87 | * [.nextSibling(object)](#module_symbol-tree--SymbolTree+nextSibling) ⇒ Object 88 | * [.parent(object)](#module_symbol-tree--SymbolTree+parent) ⇒ Object 89 | * [.lastInclusiveDescendant(object)](#module_symbol-tree--SymbolTree+lastInclusiveDescendant) ⇒ Object 90 | * [.preceding(object, [options])](#module_symbol-tree--SymbolTree+preceding) ⇒ Object 91 | * [.following(object, [options])](#module_symbol-tree--SymbolTree+following) ⇒ Object 92 | * [.childrenToArray(parent, [options])](#module_symbol-tree--SymbolTree+childrenToArray) ⇒ Array.<Object> 93 | * [.ancestorsToArray(object, [options])](#module_symbol-tree--SymbolTree+ancestorsToArray) ⇒ Array.<Object> 94 | * [.treeToArray(root, [options])](#module_symbol-tree--SymbolTree+treeToArray) ⇒ Array.<Object> 95 | * [.childrenIterator(parent, [options])](#module_symbol-tree--SymbolTree+childrenIterator) ⇒ Object 96 | * [.previousSiblingsIterator(object)](#module_symbol-tree--SymbolTree+previousSiblingsIterator) ⇒ Object 97 | * [.nextSiblingsIterator(object)](#module_symbol-tree--SymbolTree+nextSiblingsIterator) ⇒ Object 98 | * [.ancestorsIterator(object)](#module_symbol-tree--SymbolTree+ancestorsIterator) ⇒ Object 99 | * [.treeIterator(root, [options])](#module_symbol-tree--SymbolTree+treeIterator) ⇒ Object 100 | * [.index(child)](#module_symbol-tree--SymbolTree+index) ⇒ Number 101 | * [.childrenCount(parent)](#module_symbol-tree--SymbolTree+childrenCount) ⇒ Number 102 | * [.compareTreePosition(left, right)](#module_symbol-tree--SymbolTree+compareTreePosition) ⇒ Number 103 | * [.remove(removeObject)](#module_symbol-tree--SymbolTree+remove) ⇒ Object 104 | * [.insertBefore(referenceObject, newObject)](#module_symbol-tree--SymbolTree+insertBefore) ⇒ Object 105 | * [.insertAfter(referenceObject, newObject)](#module_symbol-tree--SymbolTree+insertAfter) ⇒ Object 106 | * [.prependChild(referenceObject, newObject)](#module_symbol-tree--SymbolTree+prependChild) ⇒ Object 107 | * [.appendChild(referenceObject, newObject)](#module_symbol-tree--SymbolTree+appendChild) ⇒ Object 108 | 109 | 110 | 111 | ### SymbolTree ⏏ 112 | **Kind**: Exported class 113 | 114 | 115 | #### new SymbolTree([description]) 116 | 117 | | Param | Type | Default | Description | 118 | | --- | --- | --- | --- | 119 | | [description] | string | "'SymbolTree data'" | Description used for the Symbol | 120 | 121 | 122 | 123 | #### symbolTree.initialize(object) ⇒ Object 124 | You can use this function to (optionally) initialize an object right after its creation, 125 | to take advantage of V8's fast properties. Also useful if you would like to 126 | freeze your object. 127 | 128 | * `O(1)` 129 | 130 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 131 | **Returns**: Object - object 132 | 133 | | Param | Type | 134 | | --- | --- | 135 | | object | Object | 136 | 137 | 138 | 139 | #### symbolTree.hasChildren(object) ⇒ Boolean 140 | Returns `true` if the object has any children. Otherwise it returns `false`. 141 | 142 | * `O(1)` 143 | 144 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 145 | 146 | | Param | Type | 147 | | --- | --- | 148 | | object | Object | 149 | 150 | 151 | 152 | #### symbolTree.firstChild(object) ⇒ Object 153 | Returns the first child of the given object. 154 | 155 | * `O(1)` 156 | 157 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 158 | 159 | | Param | Type | 160 | | --- | --- | 161 | | object | Object | 162 | 163 | 164 | 165 | #### symbolTree.lastChild(object) ⇒ Object 166 | Returns the last child of the given object. 167 | 168 | * `O(1)` 169 | 170 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 171 | 172 | | Param | Type | 173 | | --- | --- | 174 | | object | Object | 175 | 176 | 177 | 178 | #### symbolTree.previousSibling(object) ⇒ Object 179 | Returns the previous sibling of the given object. 180 | 181 | * `O(1)` 182 | 183 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 184 | 185 | | Param | Type | 186 | | --- | --- | 187 | | object | Object | 188 | 189 | 190 | 191 | #### symbolTree.nextSibling(object) ⇒ Object 192 | Returns the next sibling of the given object. 193 | 194 | * `O(1)` 195 | 196 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 197 | 198 | | Param | Type | 199 | | --- | --- | 200 | | object | Object | 201 | 202 | 203 | 204 | #### symbolTree.parent(object) ⇒ Object 205 | Return the parent of the given object. 206 | 207 | * `O(1)` 208 | 209 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 210 | 211 | | Param | Type | 212 | | --- | --- | 213 | | object | Object | 214 | 215 | 216 | 217 | #### symbolTree.lastInclusiveDescendant(object) ⇒ Object 218 | Find the inclusive descendant that is last in tree order of the given object. 219 | 220 | * `O(n)` (worst case) where `n` is the depth of the subtree of `object` 221 | 222 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 223 | 224 | | Param | Type | 225 | | --- | --- | 226 | | object | Object | 227 | 228 | 229 | 230 | #### symbolTree.preceding(object, [options]) ⇒ Object 231 | Find the preceding object (A) of the given object (B). 232 | An object A is preceding an object B if A and B are in the same tree 233 | and A comes before B in tree order. 234 | 235 | * `O(n)` (worst case) 236 | * `O(1)` (amortized when walking the entire tree) 237 | 238 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 239 | 240 | | Param | Type | Description | 241 | | --- | --- | --- | 242 | | object | Object | | 243 | | [options] | Object | | 244 | | [options.root] | Object | If set, `root` must be an inclusive ancestor of the return value (or else null is returned). This check _assumes_ that `root` is also an inclusive ancestor of the given `object` | 245 | 246 | 247 | 248 | #### symbolTree.following(object, [options]) ⇒ Object 249 | Find the following object (A) of the given object (B). 250 | An object A is following an object B if A and B are in the same tree 251 | and A comes after B in tree order. 252 | 253 | * `O(n)` (worst case) where `n` is the amount of objects in the entire tree 254 | * `O(1)` (amortized when walking the entire tree) 255 | 256 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 257 | 258 | | Param | Type | Default | Description | 259 | | --- | --- | --- | --- | 260 | | object | Object | | | 261 | | [options] | Object | | | 262 | | [options.root] | Object | | If set, `root` must be an inclusive ancestor of the return value (or else null is returned). This check _assumes_ that `root` is also an inclusive ancestor of the given `object` | 263 | | [options.skipChildren] | Boolean | false | If set, ignore the children of `object` | 264 | 265 | 266 | 267 | #### symbolTree.childrenToArray(parent, [options]) ⇒ Array.<Object> 268 | Append all children of the given object to an array. 269 | 270 | * `O(n)` where `n` is the amount of children of the given `parent` 271 | 272 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 273 | 274 | | Param | Type | Default | Description | 275 | | --- | --- | --- | --- | 276 | | parent | Object | | | 277 | | [options] | Object | | | 278 | | [options.array] | Array.<Object> | [] | | 279 | | [options.filter] | function | | Function to test each object before it is added to the array. Invoked with arguments (object). Should return `true` if an object is to be included. | 280 | | [options.thisArg] | \* | | Value to use as `this` when executing `filter`. | 281 | 282 | 283 | 284 | #### symbolTree.ancestorsToArray(object, [options]) ⇒ Array.<Object> 285 | Append all inclusive ancestors of the given object to an array. 286 | 287 | * `O(n)` where `n` is the amount of ancestors of the given `object` 288 | 289 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 290 | 291 | | Param | Type | Default | Description | 292 | | --- | --- | --- | --- | 293 | | object | Object | | | 294 | | [options] | Object | | | 295 | | [options.array] | Array.<Object> | [] | | 296 | | [options.filter] | function | | Function to test each object before it is added to the array. Invoked with arguments (object). Should return `true` if an object is to be included. | 297 | | [options.thisArg] | \* | | Value to use as `this` when executing `filter`. | 298 | 299 | 300 | 301 | #### symbolTree.treeToArray(root, [options]) ⇒ Array.<Object> 302 | Append all descendants of the given object to an array (in tree order). 303 | 304 | * `O(n)` where `n` is the amount of objects in the sub-tree of the given `object` 305 | 306 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 307 | 308 | | Param | Type | Default | Description | 309 | | --- | --- | --- | --- | 310 | | root | Object | | | 311 | | [options] | Object | | | 312 | | [options.array] | Array.<Object> | [] | | 313 | | [options.filter] | function | | Function to test each object before it is added to the array. Invoked with arguments (object). Should return `true` if an object is to be included. | 314 | | [options.thisArg] | \* | | Value to use as `this` when executing `filter`. | 315 | 316 | 317 | 318 | #### symbolTree.childrenIterator(parent, [options]) ⇒ Object 319 | Iterate over all children of the given object 320 | 321 | * `O(1)` for a single iteration 322 | 323 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 324 | **Returns**: Object - An iterable iterator (ES6) 325 | 326 | | Param | Type | Default | 327 | | --- | --- | --- | 328 | | parent | Object | | 329 | | [options] | Object | | 330 | | [options.reverse] | Boolean | false | 331 | 332 | 333 | 334 | #### symbolTree.previousSiblingsIterator(object) ⇒ Object 335 | Iterate over all the previous siblings of the given object. (in reverse tree order) 336 | 337 | * `O(1)` for a single iteration 338 | 339 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 340 | **Returns**: Object - An iterable iterator (ES6) 341 | 342 | | Param | Type | 343 | | --- | --- | 344 | | object | Object | 345 | 346 | 347 | 348 | #### symbolTree.nextSiblingsIterator(object) ⇒ Object 349 | Iterate over all the next siblings of the given object. (in tree order) 350 | 351 | * `O(1)` for a single iteration 352 | 353 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 354 | **Returns**: Object - An iterable iterator (ES6) 355 | 356 | | Param | Type | 357 | | --- | --- | 358 | | object | Object | 359 | 360 | 361 | 362 | #### symbolTree.ancestorsIterator(object) ⇒ Object 363 | Iterate over all inclusive ancestors of the given object 364 | 365 | * `O(1)` for a single iteration 366 | 367 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 368 | **Returns**: Object - An iterable iterator (ES6) 369 | 370 | | Param | Type | 371 | | --- | --- | 372 | | object | Object | 373 | 374 | 375 | 376 | #### symbolTree.treeIterator(root, [options]) ⇒ Object 377 | Iterate over all descendants of the given object (in tree order). 378 | 379 | Where `n` is the amount of objects in the sub-tree of the given `root`: 380 | 381 | * `O(n)` (worst case for a single iteration) 382 | * `O(n)` (amortized, when completing the iterator) 383 | 384 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 385 | **Returns**: Object - An iterable iterator (ES6) 386 | 387 | | Param | Type | Default | 388 | | --- | --- | --- | 389 | | root | Object | | 390 | | [options] | Object | | 391 | | [options.reverse] | Boolean | false | 392 | 393 | 394 | 395 | #### symbolTree.index(child) ⇒ Number 396 | Find the index of the given object (the number of preceding siblings). 397 | 398 | * `O(n)` where `n` is the amount of preceding siblings 399 | * `O(1)` (amortized, if the tree is not modified) 400 | 401 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 402 | **Returns**: Number - The number of preceding siblings, or -1 if the object has no parent 403 | 404 | | Param | Type | 405 | | --- | --- | 406 | | child | Object | 407 | 408 | 409 | 410 | #### symbolTree.childrenCount(parent) ⇒ Number 411 | Calculate the number of children. 412 | 413 | * `O(n)` where `n` is the amount of children 414 | * `O(1)` (amortized, if the tree is not modified) 415 | 416 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 417 | 418 | | Param | Type | 419 | | --- | --- | 420 | | parent | Object | 421 | 422 | 423 | 424 | #### symbolTree.compareTreePosition(left, right) ⇒ Number 425 | Compare the position of an object relative to another object. A bit set is returned: 426 | 427 | 434 | 435 | The semantics are the same as compareDocumentPosition in DOM, with the exception that 436 | DISCONNECTED never occurs with any other bit. 437 | 438 | where `n` and `m` are the amount of ancestors of `left` and `right`; 439 | where `o` is the amount of children of the lowest common ancestor of `left` and `right`: 440 | 441 | * `O(n + m + o)` (worst case) 442 | * `O(n + m)` (amortized, if the tree is not modified) 443 | 444 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 445 | 446 | | Param | Type | 447 | | --- | --- | 448 | | left | Object | 449 | | right | Object | 450 | 451 | 452 | 453 | #### symbolTree.remove(removeObject) ⇒ Object 454 | Remove the object from this tree. 455 | Has no effect if already removed. 456 | 457 | * `O(1)` 458 | 459 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 460 | **Returns**: Object - removeObject 461 | 462 | | Param | Type | 463 | | --- | --- | 464 | | removeObject | Object | 465 | 466 | 467 | 468 | #### symbolTree.insertBefore(referenceObject, newObject) ⇒ Object 469 | Insert the given object before the reference object. 470 | `newObject` is now the previous sibling of `referenceObject`. 471 | 472 | * `O(1)` 473 | 474 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 475 | **Returns**: Object - newObject 476 | **Throws**: 477 | 478 | - Error If the newObject is already present in this SymbolTree 479 | 480 | 481 | | Param | Type | 482 | | --- | --- | 483 | | referenceObject | Object | 484 | | newObject | Object | 485 | 486 | 487 | 488 | #### symbolTree.insertAfter(referenceObject, newObject) ⇒ Object 489 | Insert the given object after the reference object. 490 | `newObject` is now the next sibling of `referenceObject`. 491 | 492 | * `O(1)` 493 | 494 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 495 | **Returns**: Object - newObject 496 | **Throws**: 497 | 498 | - Error If the newObject is already present in this SymbolTree 499 | 500 | 501 | | Param | Type | 502 | | --- | --- | 503 | | referenceObject | Object | 504 | | newObject | Object | 505 | 506 | 507 | 508 | #### symbolTree.prependChild(referenceObject, newObject) ⇒ Object 509 | Insert the given object as the first child of the given reference object. 510 | `newObject` is now the first child of `referenceObject`. 511 | 512 | * `O(1)` 513 | 514 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 515 | **Returns**: Object - newObject 516 | **Throws**: 517 | 518 | - Error If the newObject is already present in this SymbolTree 519 | 520 | 521 | | Param | Type | 522 | | --- | --- | 523 | | referenceObject | Object | 524 | | newObject | Object | 525 | 526 | 527 | 528 | #### symbolTree.appendChild(referenceObject, newObject) ⇒ Object 529 | Insert the given object as the last child of the given reference object. 530 | `newObject` is now the last child of `referenceObject`. 531 | 532 | * `O(1)` 533 | 534 | **Kind**: instance method of [SymbolTree](#exp_module_symbol-tree--SymbolTree) 535 | **Returns**: Object - newObject 536 | **Throws**: 537 | 538 | - Error If the newObject is already present in this SymbolTree 539 | 540 | 541 | | Param | Type | 542 | | --- | --- | 543 | | referenceObject | Object | 544 | | newObject | Object | 545 | 546 | -------------------------------------------------------------------------------- /lib/SymbolTree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @module symbol-tree 5 | * @author Joris van der Wel 6 | */ 7 | 8 | const SymbolTreeNode = require('./SymbolTreeNode'); 9 | const TreePosition = require('./TreePosition'); 10 | const TreeIterator = require('./TreeIterator'); 11 | 12 | function returnTrue() { 13 | return true; 14 | } 15 | 16 | function reverseArrayIndex(array, reverseIndex) { 17 | return array[array.length - 1 - reverseIndex]; // no need to check `index >= 0` 18 | } 19 | 20 | class SymbolTree { 21 | 22 | /** 23 | * @constructor 24 | * @alias module:symbol-tree 25 | * @param {string} [description='SymbolTree data'] Description used for the Symbol 26 | */ 27 | constructor(description) { 28 | this.symbol = Symbol(description || 'SymbolTree data'); 29 | } 30 | 31 | /** 32 | * You can use this function to (optionally) initialize an object right after its creation, 33 | * to take advantage of V8's fast properties. Also useful if you would like to 34 | * freeze your object. 35 | * 36 | * * `O(1)` 37 | * 38 | * @method 39 | * @alias module:symbol-tree#initialize 40 | * @param {Object} object 41 | * @return {Object} object 42 | */ 43 | initialize(object) { 44 | this._node(object); 45 | 46 | return object; 47 | } 48 | 49 | _node(object) { 50 | if (!object) { 51 | return null; 52 | } 53 | 54 | const node = object[this.symbol]; 55 | 56 | if (node) { 57 | return node; 58 | } 59 | 60 | return (object[this.symbol] = new SymbolTreeNode()); 61 | } 62 | 63 | /** 64 | * Returns `true` if the object has any children. Otherwise it returns `false`. 65 | * 66 | * * `O(1)` 67 | * 68 | * @method hasChildren 69 | * @memberOf module:symbol-tree# 70 | * @param {Object} object 71 | * @return {Boolean} 72 | */ 73 | hasChildren(object) { 74 | return this._node(object).hasChildren; 75 | } 76 | 77 | /** 78 | * Returns the first child of the given object. 79 | * 80 | * * `O(1)` 81 | * 82 | * @method firstChild 83 | * @memberOf module:symbol-tree# 84 | * @param {Object} object 85 | * @return {Object} 86 | */ 87 | firstChild(object) { 88 | return this._node(object).firstChild; 89 | } 90 | 91 | /** 92 | * Returns the last child of the given object. 93 | * 94 | * * `O(1)` 95 | * 96 | * @method lastChild 97 | * @memberOf module:symbol-tree# 98 | * @param {Object} object 99 | * @return {Object} 100 | */ 101 | lastChild(object) { 102 | return this._node(object).lastChild; 103 | } 104 | 105 | /** 106 | * Returns the previous sibling of the given object. 107 | * 108 | * * `O(1)` 109 | * 110 | * @method previousSibling 111 | * @memberOf module:symbol-tree# 112 | * @param {Object} object 113 | * @return {Object} 114 | */ 115 | previousSibling(object) { 116 | return this._node(object).previousSibling; 117 | } 118 | 119 | /** 120 | * Returns the next sibling of the given object. 121 | * 122 | * * `O(1)` 123 | * 124 | * @method nextSibling 125 | * @memberOf module:symbol-tree# 126 | * @param {Object} object 127 | * @return {Object} 128 | */ 129 | nextSibling(object) { 130 | return this._node(object).nextSibling; 131 | } 132 | 133 | /** 134 | * Return the parent of the given object. 135 | * 136 | * * `O(1)` 137 | * 138 | * @method parent 139 | * @memberOf module:symbol-tree# 140 | * @param {Object} object 141 | * @return {Object} 142 | */ 143 | parent(object) { 144 | return this._node(object).parent; 145 | } 146 | 147 | /** 148 | * Find the inclusive descendant that is last in tree order of the given object. 149 | * 150 | * * `O(n)` (worst case) where `n` is the depth of the subtree of `object` 151 | * 152 | * @method lastInclusiveDescendant 153 | * @memberOf module:symbol-tree# 154 | * @param {Object} object 155 | * @return {Object} 156 | */ 157 | lastInclusiveDescendant(object) { 158 | let lastChild; 159 | let current = object; 160 | 161 | while ((lastChild = this._node(current).lastChild)) { 162 | current = lastChild; 163 | } 164 | 165 | return current; 166 | } 167 | 168 | /** 169 | * Find the preceding object (A) of the given object (B). 170 | * An object A is preceding an object B if A and B are in the same tree 171 | * and A comes before B in tree order. 172 | * 173 | * * `O(n)` (worst case) 174 | * * `O(1)` (amortized when walking the entire tree) 175 | * 176 | * @method preceding 177 | * @memberOf module:symbol-tree# 178 | * @param {Object} object 179 | * @param {Object} [options] 180 | * @param {Object} [options.root] If set, `root` must be an inclusive ancestor 181 | * of the return value (or else null is returned). This check _assumes_ 182 | * that `root` is also an inclusive ancestor of the given `object` 183 | * @return {?Object} 184 | */ 185 | preceding(object, options) { 186 | const treeRoot = options && options.root; 187 | 188 | if (object === treeRoot) { 189 | return null; 190 | } 191 | 192 | const previousSibling = this._node(object).previousSibling; 193 | 194 | if (previousSibling) { 195 | return this.lastInclusiveDescendant(previousSibling); 196 | } 197 | 198 | // if there is no previous sibling return the parent (might be null) 199 | return this._node(object).parent; 200 | } 201 | 202 | /** 203 | * Find the following object (A) of the given object (B). 204 | * An object A is following an object B if A and B are in the same tree 205 | * and A comes after B in tree order. 206 | * 207 | * * `O(n)` (worst case) where `n` is the amount of objects in the entire tree 208 | * * `O(1)` (amortized when walking the entire tree) 209 | * 210 | * @method following 211 | * @memberOf module:symbol-tree# 212 | * @param {!Object} object 213 | * @param {Object} [options] 214 | * @param {Object} [options.root] If set, `root` must be an inclusive ancestor 215 | * of the return value (or else null is returned). This check _assumes_ 216 | * that `root` is also an inclusive ancestor of the given `object` 217 | * @param {Boolean} [options.skipChildren=false] If set, ignore the children of `object` 218 | * @return {?Object} 219 | */ 220 | following(object, options) { 221 | const treeRoot = options && options.root; 222 | const skipChildren = options && options.skipChildren; 223 | 224 | const firstChild = !skipChildren && this._node(object).firstChild; 225 | 226 | if (firstChild) { 227 | return firstChild; 228 | } 229 | 230 | let current = object; 231 | 232 | do { 233 | if (current === treeRoot) { 234 | return null; 235 | } 236 | 237 | const nextSibling = this._node(current).nextSibling; 238 | 239 | if (nextSibling) { 240 | return nextSibling; 241 | } 242 | 243 | current = this._node(current).parent; 244 | } while (current); 245 | 246 | return null; 247 | } 248 | 249 | /** 250 | * Append all children of the given object to an array. 251 | * 252 | * * `O(n)` where `n` is the amount of children of the given `parent` 253 | * 254 | * @method childrenToArray 255 | * @memberOf module:symbol-tree# 256 | * @param {Object} parent 257 | * @param {Object} [options] 258 | * @param {Object[]} [options.array=[]] 259 | * @param {Function} [options.filter] Function to test each object before it is added to the array. 260 | * Invoked with arguments (object). Should return `true` if an object 261 | * is to be included. 262 | * @param {*} [options.thisArg] Value to use as `this` when executing `filter`. 263 | * @return {Object[]} 264 | */ 265 | childrenToArray(parent, options) { 266 | const array = (options && options.array) || []; 267 | const filter = (options && options.filter) || returnTrue; 268 | const thisArg = (options && options.thisArg) || undefined; 269 | 270 | const parentNode = this._node(parent); 271 | let object = parentNode.firstChild; 272 | let index = 0; 273 | 274 | while (object) { 275 | const node = this._node(object); 276 | node.setCachedIndex(parentNode, index); 277 | 278 | if (filter.call(thisArg, object)) { 279 | array.push(object); 280 | } 281 | 282 | object = node.nextSibling; 283 | ++index; 284 | } 285 | 286 | return array; 287 | } 288 | 289 | /** 290 | * Append all inclusive ancestors of the given object to an array. 291 | * 292 | * * `O(n)` where `n` is the amount of ancestors of the given `object` 293 | * 294 | * @method ancestorsToArray 295 | * @memberOf module:symbol-tree# 296 | * @param {Object} object 297 | * @param {Object} [options] 298 | * @param {Object[]} [options.array=[]] 299 | * @param {Function} [options.filter] Function to test each object before it is added to the array. 300 | * Invoked with arguments (object). Should return `true` if an object 301 | * is to be included. 302 | * @param {*} [options.thisArg] Value to use as `this` when executing `filter`. 303 | * @return {Object[]} 304 | */ 305 | ancestorsToArray(object, options) { 306 | const array = (options && options.array) || []; 307 | const filter = (options && options.filter) || returnTrue; 308 | const thisArg = (options && options.thisArg) || undefined; 309 | 310 | let ancestor = object; 311 | 312 | while (ancestor) { 313 | if (filter.call(thisArg, ancestor)) { 314 | array.push(ancestor); 315 | } 316 | ancestor = this._node(ancestor).parent; 317 | } 318 | 319 | return array; 320 | } 321 | 322 | /** 323 | * Append all descendants of the given object to an array (in tree order). 324 | * 325 | * * `O(n)` where `n` is the amount of objects in the sub-tree of the given `object` 326 | * 327 | * @method treeToArray 328 | * @memberOf module:symbol-tree# 329 | * @param {Object} root 330 | * @param {Object} [options] 331 | * @param {Object[]} [options.array=[]] 332 | * @param {Function} [options.filter] Function to test each object before it is added to the array. 333 | * Invoked with arguments (object). Should return `true` if an object 334 | * is to be included. 335 | * @param {*} [options.thisArg] Value to use as `this` when executing `filter`. 336 | * @return {Object[]} 337 | */ 338 | treeToArray(root, options) { 339 | const array = (options && options.array) || []; 340 | const filter = (options && options.filter) || returnTrue; 341 | const thisArg = (options && options.thisArg) || undefined; 342 | 343 | let object = root; 344 | 345 | while (object) { 346 | if (filter.call(thisArg, object)) { 347 | array.push(object); 348 | } 349 | object = this.following(object, {root: root}); 350 | } 351 | 352 | return array; 353 | } 354 | 355 | /** 356 | * Iterate over all children of the given object 357 | * 358 | * * `O(1)` for a single iteration 359 | * 360 | * @method childrenIterator 361 | * @memberOf module:symbol-tree# 362 | * @param {Object} parent 363 | * @param {Object} [options] 364 | * @param {Boolean} [options.reverse=false] 365 | * @return {Object} An iterable iterator (ES6) 366 | */ 367 | childrenIterator(parent, options) { 368 | const reverse = options && options.reverse; 369 | const parentNode = this._node(parent); 370 | 371 | return new TreeIterator( 372 | this, 373 | parent, 374 | reverse ? parentNode.lastChild : parentNode.firstChild, 375 | reverse ? TreeIterator.PREV : TreeIterator.NEXT 376 | ); 377 | } 378 | 379 | /** 380 | * Iterate over all the previous siblings of the given object. (in reverse tree order) 381 | * 382 | * * `O(1)` for a single iteration 383 | * 384 | * @method previousSiblingsIterator 385 | * @memberOf module:symbol-tree# 386 | * @param {Object} object 387 | * @return {Object} An iterable iterator (ES6) 388 | */ 389 | previousSiblingsIterator(object) { 390 | return new TreeIterator( 391 | this, 392 | object, 393 | this._node(object).previousSibling, 394 | TreeIterator.PREV 395 | ); 396 | } 397 | 398 | /** 399 | * Iterate over all the next siblings of the given object. (in tree order) 400 | * 401 | * * `O(1)` for a single iteration 402 | * 403 | * @method nextSiblingsIterator 404 | * @memberOf module:symbol-tree# 405 | * @param {Object} object 406 | * @return {Object} An iterable iterator (ES6) 407 | */ 408 | nextSiblingsIterator(object) { 409 | return new TreeIterator( 410 | this, 411 | object, 412 | this._node(object).nextSibling, 413 | TreeIterator.NEXT 414 | ); 415 | } 416 | 417 | /** 418 | * Iterate over all inclusive ancestors of the given object 419 | * 420 | * * `O(1)` for a single iteration 421 | * 422 | * @method ancestorsIterator 423 | * @memberOf module:symbol-tree# 424 | * @param {Object} object 425 | * @return {Object} An iterable iterator (ES6) 426 | */ 427 | ancestorsIterator(object) { 428 | return new TreeIterator( 429 | this, 430 | object, 431 | object, 432 | TreeIterator.PARENT 433 | ); 434 | } 435 | 436 | /** 437 | * Iterate over all descendants of the given object (in tree order). 438 | * 439 | * Where `n` is the amount of objects in the sub-tree of the given `root`: 440 | * 441 | * * `O(n)` (worst case for a single iteration) 442 | * * `O(n)` (amortized, when completing the iterator) 443 | * 444 | * @method treeIterator 445 | * @memberOf module:symbol-tree# 446 | * @param {Object} root 447 | * @param {Object} [options] 448 | * @param {Boolean} [options.reverse=false] 449 | * @return {Object} An iterable iterator (ES6) 450 | */ 451 | treeIterator(root, options) { 452 | const reverse = options && options.reverse; 453 | 454 | return new TreeIterator( 455 | this, 456 | root, 457 | reverse ? this.lastInclusiveDescendant(root) : root, 458 | reverse ? TreeIterator.PRECEDING : TreeIterator.FOLLOWING 459 | ); 460 | } 461 | 462 | /** 463 | * Find the index of the given object (the number of preceding siblings). 464 | * 465 | * * `O(n)` where `n` is the amount of preceding siblings 466 | * * `O(1)` (amortized, if the tree is not modified) 467 | * 468 | * @method index 469 | * @memberOf module:symbol-tree# 470 | * @param {Object} child 471 | * @return {Number} The number of preceding siblings, or -1 if the object has no parent 472 | */ 473 | index(child) { 474 | const childNode = this._node(child); 475 | const parentNode = this._node(childNode.parent); 476 | 477 | if (!parentNode) { 478 | // In principal, you could also find out the number of preceding siblings 479 | // for objects that do not have a parent. This method limits itself only to 480 | // objects that have a parent because that lets us optimize more. 481 | return -1; 482 | } 483 | 484 | let currentIndex = childNode.getCachedIndex(parentNode); 485 | 486 | if (currentIndex >= 0) { 487 | return currentIndex; 488 | } 489 | 490 | currentIndex = 0; 491 | let object = parentNode.firstChild; 492 | 493 | if (parentNode.childIndexCachedUpTo) { 494 | const cachedUpToNode = this._node(parentNode.childIndexCachedUpTo); 495 | object = cachedUpToNode.nextSibling; 496 | currentIndex = cachedUpToNode.getCachedIndex(parentNode) + 1; 497 | } 498 | 499 | while (object) { 500 | const node = this._node(object); 501 | node.setCachedIndex(parentNode, currentIndex); 502 | 503 | if (object === child) { 504 | break; 505 | } 506 | 507 | ++currentIndex; 508 | object = node.nextSibling; 509 | } 510 | 511 | parentNode.childIndexCachedUpTo = child; 512 | 513 | return currentIndex; 514 | } 515 | 516 | /** 517 | * Calculate the number of children. 518 | * 519 | * * `O(n)` where `n` is the amount of children 520 | * * `O(1)` (amortized, if the tree is not modified) 521 | * 522 | * @method childrenCount 523 | * @memberOf module:symbol-tree# 524 | * @param {Object} parent 525 | * @return {Number} 526 | */ 527 | childrenCount(parent) { 528 | const parentNode = this._node(parent); 529 | 530 | if (!parentNode.lastChild) { 531 | return 0; 532 | } 533 | 534 | return this.index(parentNode.lastChild) + 1; 535 | } 536 | 537 | /** 538 | * Compare the position of an object relative to another object. A bit set is returned: 539 | * 540 | * 547 | * 548 | * The semantics are the same as compareDocumentPosition in DOM, with the exception that 549 | * DISCONNECTED never occurs with any other bit. 550 | * 551 | * where `n` and `m` are the amount of ancestors of `left` and `right`; 552 | * where `o` is the amount of children of the lowest common ancestor of `left` and `right`: 553 | * 554 | * * `O(n + m + o)` (worst case) 555 | * * `O(n + m)` (amortized, if the tree is not modified) 556 | * 557 | * @method compareTreePosition 558 | * @memberOf module:symbol-tree# 559 | * @param {Object} left 560 | * @param {Object} right 561 | * @return {Number} 562 | */ 563 | compareTreePosition(left, right) { 564 | // In DOM terms: 565 | // left = reference / context object 566 | // right = other 567 | 568 | if (left === right) { 569 | return 0; 570 | } 571 | 572 | /* jshint -W016 */ 573 | 574 | const leftAncestors = []; { // inclusive 575 | let leftAncestor = left; 576 | 577 | while (leftAncestor) { 578 | if (leftAncestor === right) { 579 | return TreePosition.CONTAINS | TreePosition.PRECEDING; 580 | // other is ancestor of reference 581 | } 582 | 583 | leftAncestors.push(leftAncestor); 584 | leftAncestor = this.parent(leftAncestor); 585 | } 586 | } 587 | 588 | 589 | const rightAncestors = []; { 590 | let rightAncestor = right; 591 | 592 | while (rightAncestor) { 593 | if (rightAncestor === left) { 594 | return TreePosition.CONTAINED_BY | TreePosition.FOLLOWING; 595 | } 596 | 597 | rightAncestors.push(rightAncestor); 598 | rightAncestor = this.parent(rightAncestor); 599 | } 600 | } 601 | 602 | 603 | const root = reverseArrayIndex(leftAncestors, 0); 604 | 605 | if (!root || root !== reverseArrayIndex(rightAncestors, 0)) { 606 | // note: unlike DOM, preceding / following is not set here 607 | return TreePosition.DISCONNECTED; 608 | } 609 | 610 | // find the lowest common ancestor 611 | let commonAncestorIndex = 0; 612 | const ancestorsMinLength = Math.min(leftAncestors.length, rightAncestors.length); 613 | 614 | for (let i = 0; i < ancestorsMinLength; ++i) { 615 | const leftAncestor = reverseArrayIndex(leftAncestors, i); 616 | const rightAncestor = reverseArrayIndex(rightAncestors, i); 617 | 618 | if (leftAncestor !== rightAncestor) { 619 | break; 620 | } 621 | 622 | commonAncestorIndex = i; 623 | } 624 | 625 | // indexes within the common ancestor 626 | const leftIndex = this.index(reverseArrayIndex(leftAncestors, commonAncestorIndex + 1)); 627 | const rightIndex = this.index(reverseArrayIndex(rightAncestors, commonAncestorIndex + 1)); 628 | 629 | return rightIndex < leftIndex 630 | ? TreePosition.PRECEDING 631 | : TreePosition.FOLLOWING; 632 | } 633 | 634 | /** 635 | * Remove the object from this tree. 636 | * Has no effect if already removed. 637 | * 638 | * * `O(1)` 639 | * 640 | * @method remove 641 | * @memberOf module:symbol-tree# 642 | * @param {Object} removeObject 643 | * @return {Object} removeObject 644 | */ 645 | remove(removeObject) { 646 | const removeNode = this._node(removeObject); 647 | const parentNode = this._node(removeNode.parent); 648 | const prevNode = this._node(removeNode.previousSibling); 649 | const nextNode = this._node(removeNode.nextSibling); 650 | 651 | if (parentNode) { 652 | if (parentNode.firstChild === removeObject) { 653 | parentNode.firstChild = removeNode.nextSibling; 654 | } 655 | 656 | if (parentNode.lastChild === removeObject) { 657 | parentNode.lastChild = removeNode.previousSibling; 658 | } 659 | } 660 | 661 | if (prevNode) { 662 | prevNode.nextSibling = removeNode.nextSibling; 663 | } 664 | 665 | if (nextNode) { 666 | nextNode.previousSibling = removeNode.previousSibling; 667 | } 668 | 669 | removeNode.parent = null; 670 | removeNode.previousSibling = null; 671 | removeNode.nextSibling = null; 672 | removeNode.cachedIndex = -1; 673 | removeNode.cachedIndexVersion = NaN; 674 | 675 | if (parentNode) { 676 | parentNode.childrenChanged(); 677 | } 678 | 679 | return removeObject; 680 | } 681 | 682 | /** 683 | * Insert the given object before the reference object. 684 | * `newObject` is now the previous sibling of `referenceObject`. 685 | * 686 | * * `O(1)` 687 | * 688 | * @method insertBefore 689 | * @memberOf module:symbol-tree# 690 | * @param {Object} referenceObject 691 | * @param {Object} newObject 692 | * @throws {Error} If the newObject is already present in this SymbolTree 693 | * @return {Object} newObject 694 | */ 695 | insertBefore(referenceObject, newObject) { 696 | const referenceNode = this._node(referenceObject); 697 | const prevNode = this._node(referenceNode.previousSibling); 698 | const newNode = this._node(newObject); 699 | const parentNode = this._node(referenceNode.parent); 700 | 701 | if (newNode.isAttached) { 702 | throw Error('Given object is already present in this SymbolTree, remove it first'); 703 | } 704 | 705 | newNode.parent = referenceNode.parent; 706 | newNode.previousSibling = referenceNode.previousSibling; 707 | newNode.nextSibling = referenceObject; 708 | referenceNode.previousSibling = newObject; 709 | 710 | if (prevNode) { 711 | prevNode.nextSibling = newObject; 712 | } 713 | 714 | if (parentNode && parentNode.firstChild === referenceObject) { 715 | parentNode.firstChild = newObject; 716 | } 717 | 718 | if (parentNode) { 719 | parentNode.childrenChanged(); 720 | } 721 | 722 | return newObject; 723 | } 724 | 725 | /** 726 | * Insert the given object after the reference object. 727 | * `newObject` is now the next sibling of `referenceObject`. 728 | * 729 | * * `O(1)` 730 | * 731 | * @method insertAfter 732 | * @memberOf module:symbol-tree# 733 | * @param {Object} referenceObject 734 | * @param {Object} newObject 735 | * @throws {Error} If the newObject is already present in this SymbolTree 736 | * @return {Object} newObject 737 | */ 738 | insertAfter(referenceObject, newObject) { 739 | const referenceNode = this._node(referenceObject); 740 | const nextNode = this._node(referenceNode.nextSibling); 741 | const newNode = this._node(newObject); 742 | const parentNode = this._node(referenceNode.parent); 743 | 744 | if (newNode.isAttached) { 745 | throw Error('Given object is already present in this SymbolTree, remove it first'); 746 | } 747 | 748 | newNode.parent = referenceNode.parent; 749 | newNode.previousSibling = referenceObject; 750 | newNode.nextSibling = referenceNode.nextSibling; 751 | referenceNode.nextSibling = newObject; 752 | 753 | if (nextNode) { 754 | nextNode.previousSibling = newObject; 755 | } 756 | 757 | if (parentNode && parentNode.lastChild === referenceObject) { 758 | parentNode.lastChild = newObject; 759 | } 760 | 761 | if (parentNode) { 762 | parentNode.childrenChanged(); 763 | } 764 | 765 | return newObject; 766 | } 767 | 768 | /** 769 | * Insert the given object as the first child of the given reference object. 770 | * `newObject` is now the first child of `referenceObject`. 771 | * 772 | * * `O(1)` 773 | * 774 | * @method prependChild 775 | * @memberOf module:symbol-tree# 776 | * @param {Object} referenceObject 777 | * @param {Object} newObject 778 | * @throws {Error} If the newObject is already present in this SymbolTree 779 | * @return {Object} newObject 780 | */ 781 | prependChild(referenceObject, newObject) { 782 | const referenceNode = this._node(referenceObject); 783 | const newNode = this._node(newObject); 784 | 785 | if (newNode.isAttached) { 786 | throw Error('Given object is already present in this SymbolTree, remove it first'); 787 | } 788 | 789 | if (referenceNode.hasChildren) { 790 | this.insertBefore(referenceNode.firstChild, newObject); 791 | } 792 | else { 793 | newNode.parent = referenceObject; 794 | referenceNode.firstChild = newObject; 795 | referenceNode.lastChild = newObject; 796 | referenceNode.childrenChanged(); 797 | } 798 | 799 | return newObject; 800 | } 801 | 802 | /** 803 | * Insert the given object as the last child of the given reference object. 804 | * `newObject` is now the last child of `referenceObject`. 805 | * 806 | * * `O(1)` 807 | * 808 | * @method appendChild 809 | * @memberOf module:symbol-tree# 810 | * @param {Object} referenceObject 811 | * @param {Object} newObject 812 | * @throws {Error} If the newObject is already present in this SymbolTree 813 | * @return {Object} newObject 814 | */ 815 | appendChild(referenceObject, newObject) { 816 | const referenceNode = this._node(referenceObject); 817 | const newNode = this._node(newObject); 818 | 819 | if (newNode.isAttached) { 820 | throw Error('Given object is already present in this SymbolTree, remove it first'); 821 | } 822 | 823 | if (referenceNode.hasChildren) { 824 | this.insertAfter(referenceNode.lastChild, newObject); 825 | } 826 | else { 827 | newNode.parent = referenceObject; 828 | referenceNode.firstChild = newObject; 829 | referenceNode.lastChild = newObject; 830 | referenceNode.childrenChanged(); 831 | } 832 | 833 | return newObject; 834 | } 835 | } 836 | 837 | module.exports = SymbolTree; 838 | SymbolTree.TreePosition = TreePosition; 839 | -------------------------------------------------------------------------------- /lib/SymbolTreeNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = class SymbolTreeNode { 4 | constructor() { 5 | this.parent = null; 6 | this.previousSibling = null; 7 | this.nextSibling = null; 8 | 9 | this.firstChild = null; 10 | this.lastChild = null; 11 | 12 | /** This value is incremented anytime a children is added or removed */ 13 | this.childrenVersion = 0; 14 | /** The last child object which has a cached index */ 15 | this.childIndexCachedUpTo = null; 16 | 17 | /** This value represents the cached node index, as long as 18 | * cachedIndexVersion matches with the childrenVersion of the parent */ 19 | this.cachedIndex = -1; 20 | this.cachedIndexVersion = NaN; // NaN is never equal to anything 21 | } 22 | 23 | get isAttached() { 24 | return Boolean(this.parent || this.previousSibling || this.nextSibling); 25 | } 26 | 27 | get hasChildren() { 28 | return Boolean(this.firstChild); 29 | } 30 | 31 | childrenChanged() { 32 | /* jshint -W016 */ 33 | // integer wrap around 34 | this.childrenVersion = (this.childrenVersion + 1) & 0xFFFFFFFF; 35 | this.childIndexCachedUpTo = null; 36 | } 37 | 38 | getCachedIndex(parentNode) { 39 | // (assumes parentNode is actually the parent) 40 | if (this.cachedIndexVersion !== parentNode.childrenVersion) { 41 | this.cachedIndexVersion = NaN; 42 | // cachedIndex is no longer valid 43 | return -1; 44 | } 45 | 46 | return this.cachedIndex; // -1 if not cached 47 | } 48 | 49 | setCachedIndex(parentNode, index) { 50 | // (assumes parentNode is actually the parent) 51 | this.cachedIndexVersion = parentNode.childrenVersion; 52 | this.cachedIndex = index; 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /lib/TreeIterator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const TREE = Symbol(); 4 | const ROOT = Symbol(); 5 | const NEXT = Symbol(); 6 | const ITERATE_FUNC = Symbol(); 7 | 8 | class TreeIterator { 9 | constructor(tree, root, firstResult, iterateFunction) { 10 | this[TREE] = tree; 11 | this[ROOT] = root; 12 | this[NEXT] = firstResult; 13 | this[ITERATE_FUNC] = iterateFunction; 14 | } 15 | 16 | next() { 17 | const tree = this[TREE]; 18 | const iterateFunc = this[ITERATE_FUNC]; 19 | const root = this[ROOT]; 20 | 21 | if (!this[NEXT]) { 22 | return { 23 | done: true, 24 | value: root, 25 | }; 26 | } 27 | 28 | const value = this[NEXT]; 29 | 30 | if (iterateFunc === 1) { 31 | this[NEXT] = tree._node(value).previousSibling; 32 | } 33 | else if (iterateFunc === 2) { 34 | this[NEXT] = tree._node(value).nextSibling; 35 | } 36 | else if (iterateFunc === 3) { 37 | this[NEXT] = tree._node(value).parent; 38 | } 39 | else if (iterateFunc === 4) { 40 | this[NEXT] = tree.preceding(value, {root: root}); 41 | } 42 | else /* if (iterateFunc === 5)*/ { 43 | this[NEXT] = tree.following(value, {root: root}); 44 | } 45 | 46 | return { 47 | done: false, 48 | value: value, 49 | }; 50 | } 51 | } 52 | 53 | Object.defineProperty(TreeIterator.prototype, Symbol.iterator, { 54 | value: function() { 55 | return this; 56 | }, 57 | writable: false, 58 | }); 59 | 60 | TreeIterator.PREV = 1; 61 | TreeIterator.NEXT = 2; 62 | TreeIterator.PARENT = 3; 63 | TreeIterator.PRECEDING = 4; 64 | TreeIterator.FOLLOWING = 5; 65 | 66 | Object.freeze(TreeIterator); 67 | Object.freeze(TreeIterator.prototype); 68 | 69 | module.exports = TreeIterator; 70 | -------------------------------------------------------------------------------- /lib/TreePosition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable sort-keys */ 4 | module.exports = Object.freeze({ 5 | // same as DOM DOCUMENT_POSITION_ 6 | DISCONNECTED: 1, 7 | PRECEDING: 2, 8 | FOLLOWING: 4, 9 | CONTAINS: 8, 10 | CONTAINED_BY: 16, 11 | }); 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symbol-tree", 3 | "version": "3.2.4", 4 | "description": "Turn any collection of objects into its own efficient tree or linked list using Symbol", 5 | "main": "lib/SymbolTree.js", 6 | "scripts": { 7 | "lint": "eslint lib test", 8 | "test": "istanbul cover test/SymbolTree.js", 9 | "posttest": "npm run lint", 10 | "ci": "istanbul cover test/SymbolTree.js --report lcovonly && cat ./coverage/lcov.info | coveralls", 11 | "postci": "npm run posttest", 12 | "predocumentation": "cp readme-header.md README.md", 13 | "documentation": "jsdoc2md --files lib/SymbolTree.js >> README.md" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/jsdom/js-symbol-tree.git" 18 | }, 19 | "keywords": [ 20 | "list", 21 | "queue", 22 | "stack", 23 | "linked-list", 24 | "tree", 25 | "es6", 26 | "dom", 27 | "symbol" 28 | ], 29 | "files": [ 30 | "lib" 31 | ], 32 | "author": "Joris van der Wel ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/jsdom/js-symbol-tree/issues" 36 | }, 37 | "homepage": "https://github.com/jsdom/js-symbol-tree#symbol-tree", 38 | "devDependencies": { 39 | "babel-eslint": "^10.0.1", 40 | "coveralls": "^3.0.0", 41 | "eslint": "^6.8.0", 42 | "eslint-plugin-import": "^2.2.0", 43 | "istanbul": "^0.4.5", 44 | "jsdoc-to-markdown": "^5.0.0", 45 | "tape": "^5.0.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /readme-header.md: -------------------------------------------------------------------------------- 1 | symbol-tree 2 | =========== 3 | [![Travis CI Build Status](https://api.travis-ci.org/jsdom/js-symbol-tree.svg?branch=master)](https://travis-ci.org/jsdom/js-symbol-tree) [![Coverage Status](https://coveralls.io/repos/github/jsdom/js-symbol-tree/badge.svg?branch=master)](https://coveralls.io/github/jsdom/js-symbol-tree?branch=master) 4 | 5 | Turn any collection of objects into its own efficient tree or linked list using `Symbol`. 6 | 7 | This library has been designed to provide an efficient backing data structure for DOM trees. You can also use this library as an efficient linked list. Any meta data is stored on your objects directly, which ensures any kind of insertion or deletion is performed in constant time. Because an ES6 `Symbol` is used, the meta data does not interfere with your object in any way. 8 | 9 | Node.js 4+, io.js and modern browsers are supported. 10 | 11 | Example 12 | ------- 13 | A linked list: 14 | 15 | ```javascript 16 | const SymbolTree = require('symbol-tree'); 17 | const tree = new SymbolTree(); 18 | 19 | let a = {foo: 'bar'}; // or `new Whatever()` 20 | let b = {foo: 'baz'}; 21 | let c = {foo: 'qux'}; 22 | 23 | tree.insertBefore(b, a); // insert a before b 24 | tree.insertAfter(b, c); // insert c after b 25 | 26 | console.log(tree.nextSibling(a) === b); 27 | console.log(tree.nextSibling(b) === c); 28 | console.log(tree.previousSibling(c) === b); 29 | 30 | tree.remove(b); 31 | console.log(tree.nextSibling(a) === c); 32 | ``` 33 | 34 | A tree: 35 | 36 | ```javascript 37 | const SymbolTree = require('symbol-tree'); 38 | const tree = new SymbolTree(); 39 | 40 | let parent = {}; 41 | let a = {}; 42 | let b = {}; 43 | let c = {}; 44 | 45 | tree.prependChild(parent, a); // insert a as the first child 46 | tree.appendChild(parent,c ); // insert c as the last child 47 | tree.insertAfter(a, b); // insert b after a, it now has the same parent as a 48 | 49 | console.log(tree.firstChild(parent) === a); 50 | console.log(tree.nextSibling(tree.firstChild(parent)) === b); 51 | console.log(tree.lastChild(parent) === c); 52 | 53 | let grandparent = {}; 54 | tree.prependChild(grandparent, parent); 55 | console.log(tree.firstChild(tree.firstChild(grandparent)) === a); 56 | ``` 57 | 58 | See [api.md](api.md) for more documentation. 59 | 60 | Testing 61 | ------- 62 | Make sure you install the dependencies first: 63 | 64 | npm install 65 | 66 | You can now run the unit tests by executing: 67 | 68 | npm test 69 | 70 | The line and branch coverage should be 100%. 71 | 72 | API Documentation 73 | ----------------- 74 | -------------------------------------------------------------------------------- /test/SymbolTree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | 5 | const SymbolTree = require('..'); 6 | 7 | function o() { 8 | // return an object that is unique in a deepEqual check 9 | 10 | return { 11 | unique: Symbol(), 12 | }; 13 | } 14 | 15 | test('test case internal prerequisite', (t) => { 16 | const a = o(); 17 | t.notDeepEqual([o()], [o()]); 18 | t.deepEqual([a], [a]); 19 | t.end(); 20 | }); 21 | 22 | test('initialize', (t) => { 23 | const tree = new SymbolTree(); 24 | const obj = {foo: 'bar'}; 25 | 26 | t.equal(obj, tree.initialize(obj)); 27 | t.deepEqual(['foo'], Object.getOwnPropertyNames(obj), 28 | 'initialize() should not introduce any enumerable properties'); 29 | 30 | t.end(); 31 | }); 32 | 33 | test('unassociated object', (t) => { 34 | const tree = new SymbolTree(); 35 | const a = o(); 36 | 37 | t.equal(false, tree.hasChildren(a)); 38 | t.equal(null, tree.firstChild(a)); 39 | t.equal(null, tree.lastChild(a)); 40 | t.equal(null, tree.previousSibling(a)); 41 | t.equal(null, tree.nextSibling(a)); 42 | t.equal(null, tree.parent(a)); 43 | 44 | t.end(); 45 | }); 46 | 47 | test('insertBefore without parent or siblings', (t) => { 48 | const tree = new SymbolTree(); 49 | const a = o(); 50 | const b = o(); 51 | 52 | t.equal(a, tree.insertBefore(b, a)); 53 | 54 | t.equal(false, tree.hasChildren(a)); 55 | t.equal(null, tree.firstChild(a)); 56 | t.equal(null, tree.lastChild(a)); 57 | t.equal(null, tree.parent(a)); 58 | t.equal(false, tree.hasChildren(b)); 59 | t.equal(null, tree.firstChild(b)); 60 | t.equal(null, tree.lastChild(b)); 61 | t.equal(null, tree.parent(b)); 62 | 63 | t.equal(null, tree.previousSibling(a)); 64 | t.equal(b, tree.nextSibling(a)); 65 | t.equal(a, tree.previousSibling(b)); 66 | t.equal(null, tree.nextSibling(b)); 67 | 68 | t.end(); 69 | }); 70 | 71 | test('insertAfter without parent or siblings', (t) => { 72 | const tree = new SymbolTree(); 73 | const a = o(); 74 | const b = o(); 75 | 76 | t.equal(b, tree.insertAfter(a, b)); 77 | 78 | t.equal(false, tree.hasChildren(a)); 79 | t.equal(null, tree.firstChild(a)); 80 | t.equal(null, tree.lastChild(a)); 81 | t.equal(null, tree.parent(a)); 82 | t.equal(false, tree.hasChildren(b)); 83 | t.equal(null, tree.firstChild(b)); 84 | t.equal(null, tree.lastChild(b)); 85 | t.equal(null, tree.parent(b)); 86 | 87 | t.equal(null, tree.previousSibling(a)); 88 | t.equal(b, tree.nextSibling(a)); 89 | t.equal(a, tree.previousSibling(b)); 90 | t.equal(null, tree.nextSibling(b)); 91 | 92 | t.end(); 93 | }); 94 | 95 | test('prependChild without children', (t) => { 96 | const tree = new SymbolTree(); 97 | const parent = o(); 98 | const a = o(); 99 | 100 | t.equal(a, tree.prependChild(parent, a)); 101 | 102 | t.equal(false, tree.hasChildren(a)); 103 | t.equal(null, tree.firstChild(a)); 104 | t.equal(null, tree.lastChild(a)); 105 | t.equal(null, tree.previousSibling(a)); 106 | t.equal(null, tree.nextSibling(a)); 107 | t.equal(parent, tree.parent(a)); 108 | 109 | t.equal(true, tree.hasChildren(parent)); 110 | t.equal(a, tree.firstChild(parent)); 111 | t.equal(a, tree.lastChild(parent)); 112 | t.equal(null, tree.previousSibling(a)); 113 | t.equal(null, tree.nextSibling(parent)); 114 | t.equal(null, tree.parent(parent)); 115 | 116 | t.end(); 117 | }); 118 | 119 | test('appendChild without children', (t) => { 120 | const tree = new SymbolTree(); 121 | const parent = o(); 122 | const a = o(); 123 | 124 | t.equal(a, tree.appendChild(parent, a)); 125 | 126 | t.equal(false, tree.hasChildren(a)); 127 | t.equal(null, tree.firstChild(a)); 128 | t.equal(null, tree.lastChild(a)); 129 | t.equal(null, tree.previousSibling(a)); 130 | t.equal(null, tree.nextSibling(a)); 131 | t.equal(parent, tree.parent(a)); 132 | 133 | t.equal(true, tree.hasChildren(parent)); 134 | t.equal(a, tree.firstChild(parent)); 135 | t.equal(a, tree.lastChild(parent)); 136 | t.equal(null, tree.previousSibling(a)); 137 | t.equal(null, tree.nextSibling(parent)); 138 | t.equal(null, tree.parent(parent)); 139 | 140 | t.end(); 141 | }); 142 | 143 | test('prependChild with children', (t) => { 144 | const tree = new SymbolTree(); 145 | const parent = o(); 146 | const a = o(); 147 | const b = o(); 148 | 149 | tree.prependChild(parent, b); 150 | tree.prependChild(parent, a); 151 | 152 | t.equal(true, tree.hasChildren(parent)); 153 | t.equal(a, tree.firstChild(parent)); 154 | t.equal(b, tree.lastChild(parent)); 155 | 156 | t.equal(parent, tree.parent(a)); 157 | t.equal(null, tree.previousSibling(a)); 158 | t.equal(b, tree.nextSibling(a)); 159 | 160 | t.equal(parent, tree.parent(b)); 161 | t.equal(a, tree.previousSibling(b)); 162 | t.equal(null, tree.nextSibling(b)); 163 | t.end(); 164 | }); 165 | 166 | test('appendChild with children', (t) => { 167 | const tree = new SymbolTree(); 168 | const parent = o(); 169 | const a = o(); 170 | const b = o(); 171 | 172 | tree.appendChild(parent, a); 173 | tree.appendChild(parent, b); 174 | 175 | t.equal(true, tree.hasChildren(parent)); 176 | t.equal(a, tree.firstChild(parent)); 177 | t.equal(b, tree.lastChild(parent)); 178 | 179 | t.equal(parent, tree.parent(a)); 180 | t.equal(null, tree.previousSibling(a)); 181 | t.equal(b, tree.nextSibling(a)); 182 | 183 | t.equal(parent, tree.parent(b)); 184 | t.equal(a, tree.previousSibling(b)); 185 | t.equal(null, tree.nextSibling(b)); 186 | t.end(); 187 | }); 188 | 189 | test('insertBefore with parent', (t) => { 190 | const tree = new SymbolTree(); 191 | const parent = o(); 192 | const a = o(); 193 | const b = o(); 194 | 195 | tree.prependChild(parent, b); 196 | tree.insertBefore(b, a); 197 | 198 | t.equal(true, tree.hasChildren(parent)); 199 | t.equal(a, tree.firstChild(parent)); 200 | t.equal(b, tree.lastChild(parent)); 201 | 202 | t.equal(parent, tree.parent(a)); 203 | t.equal(null, tree.previousSibling(a)); 204 | t.equal(b, tree.nextSibling(a)); 205 | 206 | t.equal(parent, tree.parent(b)); 207 | t.equal(a, tree.previousSibling(b)); 208 | t.equal(null, tree.nextSibling(b)); 209 | t.end(); 210 | }); 211 | 212 | test('insertAfter with parent', (t) => { 213 | const tree = new SymbolTree(); 214 | const parent = o(); 215 | const a = o(); 216 | const b = o(); 217 | 218 | tree.appendChild(parent, a); 219 | tree.insertAfter(a, b); 220 | 221 | t.equal(true, tree.hasChildren(parent)); 222 | t.equal(a, tree.firstChild(parent)); 223 | t.equal(b, tree.lastChild(parent)); 224 | 225 | t.equal(parent, tree.parent(a)); 226 | t.equal(null, tree.previousSibling(a)); 227 | t.equal(b, tree.nextSibling(a)); 228 | 229 | t.equal(parent, tree.parent(b)); 230 | t.equal(a, tree.previousSibling(b)); 231 | t.equal(null, tree.nextSibling(b)); 232 | t.end(); 233 | }); 234 | 235 | test('insertBefore with siblings', (t) => { 236 | const tree = new SymbolTree(); 237 | const a = o(); 238 | const b = o(); 239 | const c = o(); 240 | 241 | tree.insertBefore(c, a); 242 | tree.insertBefore(c, b); 243 | 244 | t.equal(null, tree.previousSibling(a)); 245 | t.equal(b, tree.nextSibling(a)); 246 | 247 | t.equal(a, tree.previousSibling(b)); 248 | t.equal(c, tree.nextSibling(b)); 249 | 250 | t.equal(b, tree.previousSibling(c)); 251 | t.equal(null, tree.nextSibling(c)); 252 | 253 | t.end(); 254 | }); 255 | 256 | test('insertAfter with siblings', (t) => { 257 | const tree = new SymbolTree(); 258 | const a = o(); 259 | const b = o(); 260 | const c = o(); 261 | 262 | tree.insertAfter(a, c); 263 | tree.insertAfter(a, b); 264 | 265 | t.equal(null, tree.previousSibling(a)); 266 | t.equal(b, tree.nextSibling(a)); 267 | 268 | t.equal(a, tree.previousSibling(b)); 269 | t.equal(c, tree.nextSibling(b)); 270 | 271 | t.equal(b, tree.previousSibling(c)); 272 | t.equal(null, tree.nextSibling(c)); 273 | 274 | t.end(); 275 | }); 276 | 277 | test('remove with previous sibling', (t) => { 278 | const tree = new SymbolTree(); 279 | const a = o(); 280 | const b = o(); 281 | 282 | tree.insertAfter(a, b); 283 | tree.remove(b); 284 | 285 | t.equal(null, tree.previousSibling(a)); 286 | t.equal(null, tree.nextSibling(a)); 287 | t.equal(null, tree.parent(a)); 288 | 289 | t.equal(null, tree.previousSibling(b)); 290 | t.equal(null, tree.nextSibling(b)); 291 | t.equal(null, tree.parent(b)); 292 | 293 | t.end(); 294 | }); 295 | 296 | test('remove with next sibling', (t) => { 297 | const tree = new SymbolTree(); 298 | const a = o(); 299 | const b = o(); 300 | 301 | tree.insertAfter(a, b); 302 | tree.remove(a); 303 | 304 | t.equal(null, tree.previousSibling(a)); 305 | t.equal(null, tree.nextSibling(a)); 306 | t.equal(null, tree.parent(a)); 307 | 308 | t.equal(null, tree.previousSibling(b)); 309 | t.equal(null, tree.nextSibling(b)); 310 | t.equal(null, tree.parent(b)); 311 | 312 | t.end(); 313 | }); 314 | 315 | test('remove with siblings', (t) => { 316 | const tree = new SymbolTree(); 317 | const a = o(); 318 | const b = o(); 319 | const c = o(); 320 | 321 | tree.insertAfter(a, b); 322 | tree.insertAfter(b, c); 323 | tree.remove(b); 324 | 325 | t.equal(null, tree.previousSibling(a)); 326 | t.equal(c, tree.nextSibling(a)); 327 | t.equal(null, tree.parent(a)); 328 | 329 | t.equal(null, tree.previousSibling(b)); 330 | t.equal(null, tree.nextSibling(b)); 331 | t.equal(null, tree.parent(b)); 332 | 333 | t.equal(a, tree.previousSibling(c)); 334 | t.equal(null, tree.nextSibling(c)); 335 | t.equal(null, tree.parent(c)); 336 | 337 | t.end(); 338 | }); 339 | 340 | test('remove with parent', (t) => { 341 | const tree = new SymbolTree(); 342 | const parent = o(); 343 | const a = o(); 344 | 345 | tree.prependChild(parent, a); 346 | tree.remove(a); 347 | 348 | t.equal(null, tree.parent(a)); 349 | t.equal(null, tree.firstChild(parent)); 350 | t.equal(null, tree.lastChild(parent)); 351 | 352 | t.end(); 353 | }); 354 | 355 | test('remove with children', (t) => { 356 | const tree = new SymbolTree(); 357 | const parent = o(); 358 | const a = o(); 359 | 360 | tree.prependChild(parent, a); 361 | tree.remove(parent); 362 | 363 | t.equal(parent, tree.parent(a)); 364 | t.equal(a, tree.firstChild(parent)); 365 | t.equal(a, tree.lastChild(parent)); 366 | 367 | t.end(); 368 | }); 369 | 370 | test('remove with parent and siblings', (t) => { 371 | const tree = new SymbolTree(); 372 | const parent = o(); 373 | const a = o(); 374 | const b = o(); 375 | const c = o(); 376 | 377 | tree.prependChild(parent, a); 378 | tree.insertAfter(a, b); 379 | tree.insertAfter(b, c); 380 | tree.remove(b); 381 | 382 | t.equal(a, tree.firstChild(parent)); 383 | t.equal(c, tree.lastChild(parent)); 384 | 385 | t.equal(null, tree.previousSibling(a)); 386 | t.equal(c, tree.nextSibling(a)); 387 | t.equal(parent, tree.parent(a)); 388 | 389 | t.equal(null, tree.previousSibling(b)); 390 | t.equal(null, tree.nextSibling(b)); 391 | t.equal(null, tree.parent(b)); 392 | 393 | t.equal(a, tree.previousSibling(c)); 394 | t.equal(null, tree.nextSibling(c)); 395 | t.equal(parent, tree.parent(c)); 396 | 397 | t.end(); 398 | }); 399 | 400 | test('inserting an already associated object should fail', (t) => { 401 | const tree = new SymbolTree(); 402 | const a = o(); 403 | const b = o(); 404 | 405 | tree.insertBefore(b, a); 406 | 407 | // jscs:disable requireBlocksOnNewline 408 | 409 | // `nextSibling` check 410 | t.throws(() => { tree.insertBefore(b, a); }, /already present/); 411 | t.throws(() => { tree.insertAfter(b, a); }, /already present/); 412 | t.throws(() => { tree.prependChild(b, a); }, /already present/); 413 | t.throws(() => { tree.appendChild(b, a); }, /already present/); 414 | 415 | // `previousSibling` check 416 | t.throws(() => { tree.insertBefore(a, b); }, /already present/); 417 | t.throws(() => { tree.insertAfter(a, b); }, /already present/); 418 | t.throws(() => { tree.prependChild(a, b); }, /already present/); 419 | t.throws(() => { tree.appendChild(a, b); }, /already present/); 420 | 421 | tree.remove(a); 422 | 423 | tree.prependChild(b, a); 424 | // `parent` check 425 | t.throws(() => { tree.insertBefore(b, a); }, /already present/); 426 | t.throws(() => { tree.insertAfter(b, a); }, /already present/); 427 | t.throws(() => { tree.prependChild(b, a); }, /already present/); 428 | t.throws(() => { tree.appendChild(b, a); }, /already present/); 429 | 430 | // jscs:enable requireBlocksOnNewline 431 | 432 | t.end(); 433 | }); 434 | 435 | test('Multiple SymbolTree instances should not conflict', (t) => { 436 | const tree1 = new SymbolTree(); 437 | const tree2 = new SymbolTree(); 438 | const a = o(); 439 | const b = o(); 440 | 441 | tree1.insertBefore(b, a); 442 | tree2.insertBefore(a, b); 443 | 444 | t.equal(null, tree1.previousSibling(a)); 445 | t.equal(b, tree1.nextSibling(a)); 446 | t.equal(a, tree1.previousSibling(b)); 447 | t.equal(null, tree1.nextSibling(b)); 448 | 449 | t.equal(null, tree2.previousSibling(b)); 450 | t.equal(a, tree2.nextSibling(b)); 451 | t.equal(b, tree2.previousSibling(a)); 452 | t.equal(null, tree2.nextSibling(a)); 453 | 454 | t.end(); 455 | }); 456 | 457 | test('lastInclusiveDescendant', (t) => { 458 | const tree = new SymbolTree(); 459 | const a = o(); 460 | const aa = o(); 461 | const ab = o(); 462 | const aba = o(); 463 | const abaa = o(); 464 | const b = o(); 465 | 466 | tree.appendChild(a, aa); 467 | tree.appendChild(a, ab); 468 | tree.appendChild(ab, aba); 469 | tree.appendChild(aba, abaa); 470 | tree.insertAfter(a, b); 471 | 472 | t.equal(abaa, tree.lastInclusiveDescendant(a)); 473 | 474 | t.end(); 475 | }); 476 | 477 | test('look up preceding with a previous sibling', (t) => { 478 | const tree = new SymbolTree(); 479 | const a = o(); 480 | const b = o(); 481 | 482 | tree.insertAfter(a, b); 483 | 484 | t.equal(null, tree.preceding(a)); 485 | t.equal(a, tree.preceding(b)); 486 | 487 | t.end(); 488 | }); 489 | 490 | test('look up preceding with a previous sibling with a child', (t) => { 491 | const tree = new SymbolTree(); 492 | const a = o(); 493 | const aa = o(); 494 | const ab = o(); 495 | const b = o(); 496 | 497 | tree.appendChild(a, aa); 498 | tree.appendChild(a, ab); 499 | tree.insertAfter(a, b); 500 | 501 | t.equal(null, tree.preceding(a)); 502 | t.equal(a, tree.preceding(aa)); 503 | t.equal(aa, tree.preceding(ab)); 504 | t.equal(ab, tree.preceding(b)); 505 | 506 | t.end(); 507 | }); 508 | 509 | test('look up preceding with a previous sibling with a descendants', (t) => { 510 | const tree = new SymbolTree(); 511 | const a = o(); 512 | const aa = o(); 513 | const ab = o(); 514 | const aba = o(); 515 | const abaa = o(); 516 | const b = o(); 517 | 518 | tree.appendChild(a, aa); 519 | tree.appendChild(a, ab); 520 | tree.appendChild(ab, aba); 521 | tree.appendChild(aba, abaa); 522 | tree.insertAfter(a, b); 523 | 524 | t.equal(abaa, tree.preceding(b)); 525 | 526 | t.end(); 527 | }); 528 | 529 | test('look up preceding using a specified root', (t) => { 530 | const tree = new SymbolTree(); 531 | const a = o(); 532 | const aa = o(); 533 | 534 | tree.appendChild(a, aa); 535 | 536 | t.equal(null, tree.preceding(a, {root: a})); 537 | t.equal(a, tree.preceding(aa, {root: a})); 538 | t.equal(null, tree.preceding(aa, {root: aa})); 539 | 540 | t.end(); 541 | }); 542 | 543 | test('following with a child', (t) => { 544 | const tree = new SymbolTree(); 545 | const a = o(); 546 | const aa = o(); 547 | 548 | tree.appendChild(a, aa); 549 | 550 | t.equal(aa, tree.following(a)); 551 | t.equal(null, tree.following(aa)); 552 | 553 | t.end(); 554 | }); 555 | 556 | test('following with a nextSibling sibling', (t) => { 557 | const tree = new SymbolTree(); 558 | const a = o(); 559 | const b = o(); 560 | 561 | tree.insertAfter(a, b); 562 | 563 | t.equal(b, tree.following(a)); 564 | t.equal(null, tree.following(b)); 565 | 566 | t.end(); 567 | }); 568 | 569 | test('following with sibling of parent', (t) => { 570 | const tree = new SymbolTree(); 571 | const a = o(); 572 | const aa = o(); 573 | const b = o(); 574 | 575 | tree.appendChild(a, aa); 576 | tree.insertAfter(a, b); 577 | 578 | t.equal(b, tree.following(aa)); 579 | 580 | t.end(); 581 | }); 582 | 583 | test('following with sibling of grandparent', (t) => { 584 | const tree = new SymbolTree(); 585 | const a = o(); 586 | const aa = o(); 587 | const aaa = o(); 588 | const b = o(); 589 | 590 | tree.appendChild(a, aa); 591 | tree.appendChild(aa, aaa); 592 | tree.insertAfter(a, b); 593 | 594 | t.equal(b, tree.following(aaa)); 595 | 596 | t.end(); 597 | }); 598 | 599 | test('following using a specified root', (t) => { 600 | const tree = new SymbolTree(); 601 | const a = o(); 602 | const aa = o(); 603 | const aaa = o(); 604 | const b = o(); 605 | 606 | tree.appendChild(a, aa); 607 | tree.appendChild(aa, aaa); 608 | tree.insertAfter(a, b); 609 | 610 | t.equal(null, tree.following(aaa, {root: aaa})); 611 | t.equal(null, tree.following(aaa, {root: aa})); 612 | t.equal(null, tree.following(aaa, {root: a})); 613 | t.equal(aa, tree.following(a, {root: a})); 614 | t.equal(aaa, tree.following(aa, {root: a})); 615 | 616 | t.end(); 617 | }); 618 | 619 | test('following with skipChildren', (t) => { 620 | const tree = new SymbolTree(); 621 | const a = o(); 622 | const aa = o(); 623 | const b = o(); 624 | 625 | tree.appendChild(a, aa); 626 | tree.insertAfter(a, b); 627 | 628 | t.equal(b, tree.following(a, {skipChildren: true})); 629 | 630 | t.end(); 631 | }); 632 | 633 | test('childrenToArray', (t) => { 634 | const tree = new SymbolTree(); 635 | const a = o(); 636 | const aa = o(); 637 | const ab = o(); 638 | const aba = o(); 639 | const ac = o(); 640 | const b = o(); 641 | 642 | tree.appendChild(a, aa); 643 | tree.appendChild(a, ab); 644 | tree.appendChild(ab, aba); 645 | tree.appendChild(a, ac); 646 | tree.insertAfter(a, b); 647 | 648 | t.deepEqual([aa, ab, ac], tree.childrenToArray(a)); 649 | 650 | const arr = ['a', 5]; 651 | tree.childrenToArray(a, {array: arr}); 652 | t.deepEqual(['a', 5, aa, ab, ac], arr); 653 | 654 | t.end(); 655 | }); 656 | 657 | test('childrenToArray with filter', (t) => { 658 | const tree = new SymbolTree(); 659 | const a = o(); 660 | const aa = o(); 661 | const ab = o(); 662 | const aba = o(); 663 | const ac = o(); 664 | const b = o(); 665 | 666 | tree.appendChild(a, aa); 667 | tree.appendChild(a, ab); 668 | tree.appendChild(ab, aba); 669 | tree.appendChild(a, ac); 670 | tree.insertAfter(a, b); 671 | 672 | const filter = function(object) { 673 | t.equal(this, undefined); 674 | 675 | return object !== ab; 676 | }; 677 | 678 | t.deepEqual([aa, ac], tree.childrenToArray(a, {filter: filter})); 679 | 680 | const thisArg = {a: 123}; 681 | const filterThis = function(object) { 682 | t.equal(this, thisArg); 683 | 684 | return object !== ab; 685 | }; 686 | 687 | t.deepEqual([aa, ac], tree.childrenToArray(a, {filter: filterThis, thisArg: thisArg})); 688 | 689 | t.end(); 690 | }); 691 | 692 | test('children iterator', (t) => { 693 | const tree = new SymbolTree(); 694 | const a = o(); 695 | const aa = o(); 696 | const ab = o(); 697 | const aba = o(); 698 | const ac = o(); 699 | const b = o(); 700 | 701 | tree.appendChild(a, aa); 702 | tree.appendChild(a, ab); 703 | tree.appendChild(ab, aba); 704 | tree.appendChild(a, ac); 705 | tree.insertAfter(a, b); 706 | 707 | const results = []; 708 | 709 | for (const object of tree.childrenIterator(a)) { 710 | results.push(object); 711 | } 712 | t.deepEqual([aa, ab, ac], results); 713 | 714 | t.end(); 715 | }); 716 | 717 | test('children iterator reverse', (t) => { 718 | const tree = new SymbolTree(); 719 | const a = o(); 720 | const aa = o(); 721 | const ab = o(); 722 | const aba = o(); 723 | const ac = o(); 724 | const b = o(); 725 | 726 | tree.appendChild(a, aa); 727 | tree.appendChild(a, ab); 728 | tree.appendChild(ab, aba); 729 | tree.appendChild(a, ac); 730 | tree.insertAfter(a, b); 731 | 732 | const results = []; 733 | 734 | for (const object of tree.childrenIterator(a, {reverse: true})) { 735 | results.push(object); 736 | } 737 | t.deepEqual([ac, ab, aa], results); 738 | 739 | t.end(); 740 | }); 741 | 742 | test('children iterator return value using a generator', (t) => { 743 | const tree = new SymbolTree(); 744 | const a = o(); 745 | const aa = o(); 746 | const ab = o(); 747 | const ac = o(); 748 | 749 | tree.appendChild(a, aa); 750 | tree.appendChild(a, ab); 751 | tree.appendChild(a, ac); 752 | 753 | function* generator(it) { 754 | const returnValue = yield* it; 755 | t.equal(a, returnValue); 756 | } 757 | 758 | const results = []; 759 | 760 | for (const object of generator(tree.childrenIterator(a))) { 761 | results.push(object); 762 | } 763 | t.deepEqual([aa, ab, ac], results); 764 | 765 | t.end(); 766 | }); 767 | 768 | test('previous sibling iterator', (t) => { 769 | const tree = new SymbolTree(); 770 | const a = o(); 771 | const aa = o(); 772 | const ab = o(); 773 | const aba = o(); 774 | const ac = o(); 775 | const ad = o(); 776 | const ae = o(); 777 | const b = o(); 778 | 779 | tree.appendChild(a, aa); 780 | tree.appendChild(a, ab); 781 | tree.appendChild(ab, aba); 782 | tree.appendChild(a, ac); 783 | tree.appendChild(a, ad); 784 | tree.appendChild(a, ae); 785 | tree.insertAfter(a, b); 786 | 787 | const results = []; 788 | 789 | for (const object of tree.previousSiblingsIterator(ad)) { 790 | results.push(object); 791 | } 792 | t.deepEqual([ac, ab, aa], results); 793 | 794 | t.end(); 795 | }); 796 | 797 | test('nextSibling sibling iterator', (t) => { 798 | const tree = new SymbolTree(); 799 | const a = o(); 800 | const aa = o(); 801 | const ab = o(); 802 | const aba = o(); 803 | const ac = o(); 804 | const ad = o(); 805 | const ae = o(); 806 | const b = o(); 807 | 808 | tree.appendChild(a, aa); 809 | tree.appendChild(a, ab); 810 | tree.appendChild(ab, aba); 811 | tree.appendChild(a, ac); 812 | tree.appendChild(a, ad); 813 | tree.appendChild(a, ae); 814 | tree.insertAfter(a, b); 815 | 816 | const results = []; 817 | 818 | for (const object of tree.nextSiblingsIterator(ab)) { 819 | results.push(object); 820 | } 821 | t.deepEqual([ac, ad, ae], results); 822 | 823 | t.end(); 824 | }); 825 | 826 | test('ancestorsToArray', (t) => { 827 | const tree = new SymbolTree(); 828 | const a = o(); 829 | const aa = o(); 830 | const ab = o(); 831 | const aba = o(); 832 | const abaa = o(); 833 | const b = o(); 834 | 835 | tree.appendChild(a, aa); 836 | tree.appendChild(a, ab); 837 | tree.appendChild(ab, aba); 838 | tree.appendChild(aba, abaa); 839 | tree.insertAfter(a, b); 840 | 841 | t.deepEqual([abaa, aba, ab, a], tree.ancestorsToArray(abaa)); 842 | t.deepEqual([aba, ab, a], tree.ancestorsToArray(aba)); 843 | t.deepEqual([b], tree.ancestorsToArray(b)); 844 | 845 | const arr = ['a', 5]; 846 | tree.ancestorsToArray(abaa, {array: arr}); 847 | t.deepEqual(['a', 5, abaa, aba, ab, a], arr); 848 | 849 | t.end(); 850 | }); 851 | 852 | test('ancestorsToArray with filter', (t) => { 853 | const tree = new SymbolTree(); 854 | const a = o(); 855 | const aa = o(); 856 | const ab = o(); 857 | const aba = o(); 858 | const abaa = o(); 859 | const b = o(); 860 | 861 | tree.appendChild(a, aa); 862 | tree.appendChild(a, ab); 863 | tree.appendChild(ab, aba); 864 | tree.appendChild(aba, abaa); 865 | tree.insertAfter(a, b); 866 | 867 | const thisArg = {foo: 'bar'}; 868 | const filter = function(object) { 869 | t.equal(this, thisArg); 870 | 871 | return object !== abaa && object !== ab; 872 | }; 873 | 874 | t.deepEqual([aba, a], tree.ancestorsToArray(abaa, {filter: filter, thisArg: thisArg})); 875 | 876 | t.end(); 877 | }); 878 | 879 | test('ancestors iterator', (t) => { 880 | const tree = new SymbolTree(); 881 | const a = o(); 882 | const aa = o(); 883 | const ab = o(); 884 | const aba = o(); 885 | const abaa = o(); 886 | const b = o(); 887 | 888 | tree.appendChild(a, aa); 889 | tree.appendChild(a, ab); 890 | tree.appendChild(ab, aba); 891 | tree.appendChild(aba, abaa); 892 | tree.insertAfter(a, b); 893 | 894 | const results = []; 895 | const iterator = tree.ancestorsIterator(abaa); 896 | 897 | for (const object of iterator) { 898 | results.push(object); 899 | } 900 | t.deepEqual([abaa, aba, ab, a], results); 901 | t.deepEqual({done: true, value: abaa}, iterator.next()); 902 | t.deepEqual({done: true, value: abaa}, iterator.next()); // should keep returning done: true 903 | 904 | t.end(); 905 | }); 906 | 907 | test('treeToArray', (t) => { 908 | const tree = new SymbolTree(); 909 | const a = o(); 910 | const aa = o(); 911 | const ab = o(); 912 | const aba = o(); 913 | const abaa = o(); 914 | const b = o(); 915 | 916 | tree.appendChild(a, aa); 917 | tree.appendChild(a, ab); 918 | tree.appendChild(ab, aba); 919 | tree.appendChild(aba, abaa); 920 | tree.insertAfter(a, b); 921 | 922 | t.deepEqual([a, aa, ab, aba, abaa], tree.treeToArray(a)); 923 | 924 | const arr = ['a', 5]; 925 | tree.treeToArray(a, {array: arr}); 926 | t.deepEqual(['a', 5, a, aa, ab, aba, abaa], arr); 927 | 928 | t.end(); 929 | }); 930 | 931 | test('treeToArray with filter', (t) => { 932 | const tree = new SymbolTree(); 933 | const a = o(); 934 | const aa = o(); 935 | const ab = o(); 936 | const aba = o(); 937 | const abaa = o(); 938 | const b = o(); 939 | 940 | tree.appendChild(a, aa); 941 | tree.appendChild(a, ab); 942 | tree.appendChild(ab, aba); 943 | tree.appendChild(aba, abaa); 944 | tree.insertAfter(a, b); 945 | 946 | const filter = function(object) { 947 | t.equal(this, undefined); 948 | 949 | return object !== a && object !== aba; 950 | }; 951 | 952 | t.deepEqual([aa, ab, abaa], tree.treeToArray(a, {filter: filter})); 953 | 954 | const thisArg = {foo: 'bar'}; 955 | const filterThis = function(object) { 956 | t.equal(this, thisArg); 957 | 958 | return object !== a && object !== aba; 959 | }; 960 | 961 | t.deepEqual([aa, ab, abaa], tree.treeToArray(a, {filter: filterThis, thisArg: thisArg})); 962 | 963 | t.end(); 964 | }); 965 | 966 | test('tree iterator', (t) => { 967 | const tree = new SymbolTree(); 968 | const a = o(); 969 | const aa = o(); 970 | const ab = o(); 971 | const aba = o(); 972 | const abaa = o(); 973 | const ac = o(); 974 | const b = o(); 975 | 976 | tree.appendChild(a, aa); 977 | tree.appendChild(a, ab); 978 | tree.appendChild(ab, aba); 979 | tree.appendChild(aba, abaa); 980 | tree.appendChild(a, ac); 981 | tree.insertAfter(a, b); 982 | 983 | const results = []; 984 | const iterator = tree.treeIterator(a); 985 | 986 | for (const object of iterator) { 987 | results.push(object); 988 | } 989 | t.deepEqual([a, aa, ab, aba, abaa, ac], results); 990 | t.deepEqual({done: true, value: a}, iterator.next()); 991 | t.deepEqual({done: true, value: a}, iterator.next()); // should keep returning done: true 992 | 993 | t.end(); 994 | }); 995 | 996 | test('tree iterator reverse', (t) => { 997 | const tree = new SymbolTree(); 998 | const a = o(); 999 | const aa = o(); 1000 | const ab = o(); 1001 | const aba = o(); 1002 | const abaa = o(); 1003 | const ac = o(); 1004 | const b = o(); 1005 | 1006 | tree.appendChild(a, aa); 1007 | tree.appendChild(a, ab); 1008 | tree.appendChild(ab, aba); 1009 | tree.appendChild(aba, abaa); 1010 | tree.appendChild(a, ac); 1011 | tree.insertAfter(a, b); 1012 | 1013 | const results = []; 1014 | const iterator = tree.treeIterator(a, {reverse: true}); 1015 | 1016 | for (const object of iterator) { 1017 | results.push(object); 1018 | } 1019 | t.deepEqual([ac, abaa, aba, ab, aa, a], results); 1020 | t.deepEqual({done: true, value: a}, iterator.next()); 1021 | t.deepEqual({done: true, value: a}, iterator.next()); // should keep returning done: true 1022 | 1023 | t.end(); 1024 | }); 1025 | 1026 | test('look up the index of an object', (t) => { 1027 | const tree = new SymbolTree(); 1028 | const a = o(); 1029 | const aa = o(); 1030 | const ab = o(); 1031 | const aba = o(); 1032 | const ac = o(); 1033 | const b = o(); 1034 | 1035 | tree.appendChild(a, aa); 1036 | tree.appendChild(a, ab); 1037 | tree.appendChild(ab, aba); 1038 | tree.appendChild(a, ac); 1039 | tree.insertAfter(a, b); 1040 | 1041 | t.equal(-1, tree.index(a), 'should return -1 if an object has no parent'); 1042 | t.equal(0, tree.index(aa)); 1043 | t.equal(1, tree.index(ab)); 1044 | t.equal(0, tree.index(aba)); 1045 | t.equal(2, tree.index(ac)); 1046 | t.equal(-1, tree.index(b)); 1047 | 1048 | t.end(); 1049 | }); 1050 | 1051 | test('cached index', (t) => { 1052 | const tree = new SymbolTree(); 1053 | const a = o(); 1054 | const aa = o(); 1055 | const ab = o(); 1056 | const aba = o(); 1057 | const ac = o(); 1058 | const b = o(); 1059 | 1060 | tree.appendChild(a, aa); 1061 | tree.appendChild(a, ab); 1062 | tree.appendChild(ab, aba); 1063 | tree.appendChild(a, ac); 1064 | tree.insertAfter(a, b); 1065 | 1066 | // looking up ac, will also set the cached index for aa and ab, so check that those are valid 1067 | t.equal(2, tree.index(ac)); 1068 | t.equal(1, tree.index(ab)); 1069 | t.equal(0, tree.index(aa)); 1070 | 1071 | // removing something should invalidate the cache 1072 | tree.remove(ab); 1073 | t.equal(1, tree.index(ac)); 1074 | t.equal(-1, tree.index(ab)); 1075 | t.equal(0, tree.index(aa)); 1076 | 1077 | // insertAfter should invalidate 1078 | tree.insertAfter(aa, ab); 1079 | t.equal(0, tree.index(aa)); 1080 | t.equal(1, tree.index(ab)); 1081 | t.equal(2, tree.index(ac)); 1082 | 1083 | // insertBefore should invalidate 1084 | const foo = o(); 1085 | tree.insertBefore(ab, foo); 1086 | t.equal(0, tree.index(aa)); 1087 | t.equal(2, tree.index(ab)); 1088 | t.equal(3, tree.index(ac)); 1089 | 1090 | t.end(); 1091 | }); 1092 | 1093 | test('cached index warmed up by childrenToArray', (t) => { 1094 | const tree = new SymbolTree(); 1095 | const a = o(); 1096 | const aa = o(); 1097 | const ab = o(); 1098 | const aba = o(); 1099 | const ac = o(); 1100 | const b = o(); 1101 | 1102 | tree.appendChild(a, aa); 1103 | tree.appendChild(a, ab); 1104 | tree.appendChild(ab, aba); 1105 | tree.appendChild(a, ac); 1106 | tree.insertAfter(a, b); 1107 | 1108 | tree.childrenToArray(a); 1109 | t.equal(0, tree.index(aa)); 1110 | t.equal(1, tree.index(ab)); 1111 | t.equal(2, tree.index(ac)); 1112 | 1113 | tree.appendChild(a, o()); 1114 | t.equal(2, tree.index(ac)); 1115 | tree.childrenToArray(a); 1116 | t.equal(2, tree.index(ac)); 1117 | 1118 | t.end(); 1119 | }); 1120 | 1121 | test('regression test: remove() should invalidate the child index cache', (t) => { 1122 | const tree = new SymbolTree(); 1123 | const a = o(); 1124 | const aa = o(); 1125 | const b = o(); 1126 | const ba = o(); 1127 | const bb = o(); 1128 | 1129 | tree.appendChild(a, aa); 1130 | tree.appendChild(b, ba); 1131 | tree.appendChild(b, bb); 1132 | 1133 | t.equal(0, tree.index(ba)); 1134 | tree.remove(ba); 1135 | t.equal(-1, tree.index(ba)); 1136 | tree.appendChild(a, ba); 1137 | t.equal(1, tree.index(ba)); 1138 | 1139 | t.end(); 1140 | }); 1141 | 1142 | test('children count', (t) => { 1143 | // no need to test the caching since we already tested for that in childrenCount 1144 | const tree = new SymbolTree(); 1145 | const a = o(); 1146 | const aa = o(); 1147 | const ab = o(); 1148 | const aba = o(); 1149 | const ac = o(); 1150 | const b = o(); 1151 | 1152 | tree.appendChild(a, aa); 1153 | tree.appendChild(a, ab); 1154 | tree.appendChild(ab, aba); 1155 | tree.appendChild(a, ac); 1156 | tree.insertAfter(a, b); 1157 | 1158 | t.equal(3, tree.childrenCount(a), 'foo'); 1159 | t.equal(0, tree.childrenCount(aa)); 1160 | t.equal(1, tree.childrenCount(ab)); 1161 | t.equal(0, tree.childrenCount(b)); 1162 | 1163 | t.end(); 1164 | }); 1165 | 1166 | 1167 | test('compare tree position', (t) => { 1168 | const tree = new SymbolTree(); 1169 | const a = o(); 1170 | const aa = o(); 1171 | const aaa = o(); 1172 | const ab = o(); 1173 | const aba = o(); 1174 | const abaa = o(); 1175 | const ac = o(); 1176 | 1177 | const b = o(); 1178 | const ba = o(); 1179 | 1180 | tree.appendChild(a, aa); 1181 | tree.appendChild(aa, aaa); 1182 | tree.appendChild(a, ab); 1183 | tree.appendChild(ab, aba); 1184 | tree.appendChild(aba, abaa); 1185 | tree.appendChild(a, ac); 1186 | 1187 | tree.insertAfter(a, b); 1188 | tree.appendChild(b, ba); 1189 | 1190 | t.equal(0, tree.compareTreePosition(a, a), 'object equal'); 1191 | 1192 | t.equal(1, tree.compareTreePosition(a, o()), 'object disconnected'); 1193 | t.equal(1, tree.compareTreePosition(a, b), 'object disconnected'); 1194 | 1195 | t.equal(20, tree.compareTreePosition(a, aa), 'contained by & following'); 1196 | t.equal(10, tree.compareTreePosition(aa, a), 'contains & preceding'); 1197 | t.equal(20, tree.compareTreePosition(a, abaa), 'contained by & following'); 1198 | t.equal(10, tree.compareTreePosition(abaa, a), 'contains & preceding'); 1199 | 1200 | t.equal(4, tree.compareTreePosition(aa, ab), 'following'); 1201 | t.equal(2, tree.compareTreePosition(ab, aa), 'preceding'); 1202 | t.equal(4, tree.compareTreePosition(aa, aba), 'following'); 1203 | t.equal(2, tree.compareTreePosition(aba, aa), 'preceding'); 1204 | t.equal(4, tree.compareTreePosition(aa, abaa), 'following'); 1205 | t.equal(2, tree.compareTreePosition(abaa, aa), 'preceding'); 1206 | t.equal(4, tree.compareTreePosition(aaa, abaa), 'following'); 1207 | t.equal(2, tree.compareTreePosition(abaa, aaa), 'preceding'); 1208 | 1209 | t.end(); 1210 | }); 1211 | --------------------------------------------------------------------------------