├── .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 | [](https://travis-ci.org/jsdom/js-symbol-tree) [](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 |
428 | - DISCONNECTED : 1
429 | - PRECEDING : 2
430 | - FOLLOWING : 4
431 | - CONTAINS : 8
432 | - CONTAINED_BY : 16
433 |
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 | *
541 | * - DISCONNECTED : 1
542 | * - PRECEDING : 2
543 | * - FOLLOWING : 4
544 | * - CONTAINS : 8
545 | * - CONTAINED_BY : 16
546 | *
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 | [](https://travis-ci.org/jsdom/js-symbol-tree) [](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 |
--------------------------------------------------------------------------------