 aacb45156b
			
		
	
	aacb45156b
	
	
	
		
			
			- Install Jest for unit testing with React Testing Library - Install Playwright for end-to-end testing - Configure Jest with proper TypeScript support and module mapping - Create test setup files and utilities for both unit and e2e tests Components: * Jest configuration with coverage thresholds * Playwright configuration with browser automation * Unit tests for LoginForm, AuthContext, and useSocketIO hook * E2E tests for authentication, dashboard, and agents workflows * GitHub Actions workflow for automated testing * Mock data and API utilities for consistent testing * Test documentation with best practices Testing features: - Unit tests with 70% coverage threshold - E2E tests with API mocking and user journey testing - CI/CD integration for automated test runs - Cross-browser testing support with Playwright - Authentication system testing end-to-end 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			839 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			839 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| /**
 | |
|  * @module symbol-tree
 | |
|  * @author Joris van der Wel <joris@jorisvanderwel.com>
 | |
|  */
 | |
| 
 | |
| const SymbolTreeNode = require('./SymbolTreeNode');
 | |
| const TreePosition = require('./TreePosition');
 | |
| const TreeIterator = require('./TreeIterator');
 | |
| 
 | |
| function returnTrue() {
 | |
|         return true;
 | |
| }
 | |
| 
 | |
| function reverseArrayIndex(array, reverseIndex) {
 | |
|         return array[array.length - 1 - reverseIndex]; // no need to check `index >= 0`
 | |
| }
 | |
| 
 | |
| class SymbolTree {
 | |
| 
 | |
|         /**
 | |
|          * @constructor
 | |
|          * @alias module:symbol-tree
 | |
|          * @param {string} [description='SymbolTree data'] Description used for the Symbol
 | |
|          */
 | |
|         constructor(description) {
 | |
|                 this.symbol = Symbol(description || 'SymbolTree data');
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * You can use this function to (optionally) initialize an object right after its creation,
 | |
|          * to take advantage of V8's fast properties. Also useful if you would like to
 | |
|          * freeze your object.
 | |
|          *
 | |
|          * `O(1)`
 | |
|          *
 | |
|          * @method
 | |
|          * @alias module:symbol-tree#initialize
 | |
|          * @param {Object} object
 | |
|          * @return {Object} object
 | |
|          */
 | |
|         initialize(object) {
 | |
|                 this._node(object);
 | |
| 
 | |
|                 return object;
 | |
|         }
 | |
| 
 | |
|         _node(object) {
 | |
|                 if (!object) {
 | |
|                         return null;
 | |
|                 }
 | |
| 
 | |
|                 const node = object[this.symbol];
 | |
| 
 | |
|                 if (node) {
 | |
|                         return node;
 | |
|                 }
 | |
| 
 | |
|                 return (object[this.symbol] = new SymbolTreeNode());
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Returns `true` if the object has any children. Otherwise it returns `false`.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method hasChildren
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Boolean}
 | |
|          */
 | |
|         hasChildren(object) {
 | |
|                 return this._node(object).hasChildren;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Returns the first child of the given object.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method firstChild
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Object}
 | |
|          */
 | |
|         firstChild(object) {
 | |
|                 return this._node(object).firstChild;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Returns the last child of the given object.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method lastChild
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Object}
 | |
|          */
 | |
|         lastChild(object) {
 | |
|                 return this._node(object).lastChild;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Returns the previous sibling of the given object.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method previousSibling
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Object}
 | |
|          */
 | |
|         previousSibling(object) {
 | |
|                 return this._node(object).previousSibling;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Returns the next sibling of the given object.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method nextSibling
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Object}
 | |
|          */
 | |
|         nextSibling(object) {
 | |
|                 return this._node(object).nextSibling;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Return the parent of the given object.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method parent
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Object}
 | |
|          */
 | |
|         parent(object) {
 | |
|                 return this._node(object).parent;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Find the inclusive descendant that is last in tree order of the given object.
 | |
|          *
 | |
|          * * `O(n)` (worst case) where `n` is the depth of the subtree of `object`
 | |
|          *
 | |
|          * @method lastInclusiveDescendant
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Object}
 | |
|          */
 | |
|         lastInclusiveDescendant(object) {
 | |
|                 let lastChild;
 | |
|                 let current = object;
 | |
| 
 | |
|                 while ((lastChild = this._node(current).lastChild)) {
 | |
|                         current = lastChild;
 | |
|                 }
 | |
| 
 | |
|                 return current;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Find the preceding object (A) of the given object (B).
 | |
|          * An object A is preceding an object B if A and B are in the same tree
 | |
|          * and A comes before B in tree order.
 | |
|          *
 | |
|          * * `O(n)` (worst case)
 | |
|          * * `O(1)` (amortized when walking the entire tree)
 | |
|          *
 | |
|          * @method preceding
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @param {Object} [options]
 | |
|          * @param {Object} [options.root] 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`
 | |
|          * @return {?Object}
 | |
|          */
 | |
|         preceding(object, options) {
 | |
|                 const treeRoot = options && options.root;
 | |
| 
 | |
|                 if (object === treeRoot) {
 | |
|                         return null;
 | |
|                 }
 | |
| 
 | |
|                 const previousSibling = this._node(object).previousSibling;
 | |
| 
 | |
|                 if (previousSibling) {
 | |
|                         return this.lastInclusiveDescendant(previousSibling);
 | |
|                 }
 | |
| 
 | |
|                 // if there is no previous sibling return the parent (might be null)
 | |
|                 return this._node(object).parent;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Find the following object (A) of the given object (B).
 | |
|          * An object A is following an object B if A and B are in the same tree
 | |
|          * and A comes after B in tree order.
 | |
|          *
 | |
|          * * `O(n)` (worst case) where `n` is the amount of objects in the entire tree
 | |
|          * * `O(1)` (amortized when walking the entire tree)
 | |
|          *
 | |
|          * @method following
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {!Object} object
 | |
|          * @param {Object} [options]
 | |
|          * @param {Object} [options.root] 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`
 | |
|          * @param {Boolean} [options.skipChildren=false] If set, ignore the children of `object`
 | |
|          * @return {?Object}
 | |
|          */
 | |
|         following(object, options) {
 | |
|                 const treeRoot = options && options.root;
 | |
|                 const skipChildren = options && options.skipChildren;
 | |
| 
 | |
|                 const firstChild = !skipChildren && this._node(object).firstChild;
 | |
| 
 | |
|                 if (firstChild) {
 | |
|                         return firstChild;
 | |
|                 }
 | |
| 
 | |
|                 let current = object;
 | |
| 
 | |
|                 do {
 | |
|                         if (current === treeRoot) {
 | |
|                                 return null;
 | |
|                         }
 | |
| 
 | |
|                         const nextSibling = this._node(current).nextSibling;
 | |
| 
 | |
|                         if (nextSibling) {
 | |
|                                 return nextSibling;
 | |
|                         }
 | |
| 
 | |
|                         current = this._node(current).parent;
 | |
|                 } while (current);
 | |
| 
 | |
|                 return null;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Append all children of the given object to an array.
 | |
|          *
 | |
|          * * `O(n)` where `n` is the amount of children of the given `parent`
 | |
|          *
 | |
|          * @method childrenToArray
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} parent
 | |
|          * @param {Object} [options]
 | |
|          * @param {Object[]} [options.array=[]]
 | |
|          * @param {Function} [options.filter] 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.
 | |
|          * @param {*} [options.thisArg] Value to use as `this` when executing `filter`.
 | |
|          * @return {Object[]}
 | |
|          */
 | |
|         childrenToArray(parent, options) {
 | |
|                 const array   = (options && options.array) || [];
 | |
|                 const filter  = (options && options.filter) || returnTrue;
 | |
|                 const thisArg = (options && options.thisArg) || undefined;
 | |
| 
 | |
|                 const parentNode = this._node(parent);
 | |
|                 let object = parentNode.firstChild;
 | |
|                 let index = 0;
 | |
| 
 | |
|                 while (object) {
 | |
|                         const node = this._node(object);
 | |
|                         node.setCachedIndex(parentNode, index);
 | |
| 
 | |
|                         if (filter.call(thisArg, object)) {
 | |
|                                 array.push(object);
 | |
|                         }
 | |
| 
 | |
|                         object = node.nextSibling;
 | |
|                         ++index;
 | |
|                 }
 | |
| 
 | |
|                 return array;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Append all inclusive ancestors of the given object to an array.
 | |
|          *
 | |
|          * * `O(n)` where `n` is the amount of ancestors of the given `object`
 | |
|          *
 | |
|          * @method ancestorsToArray
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @param {Object} [options]
 | |
|          * @param {Object[]} [options.array=[]]
 | |
|          * @param {Function} [options.filter] 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.
 | |
|          * @param {*} [options.thisArg] Value to use as `this` when executing `filter`.
 | |
|          * @return {Object[]}
 | |
|          */
 | |
|         ancestorsToArray(object, options) {
 | |
|                 const array   = (options && options.array) || [];
 | |
|                 const filter  = (options && options.filter) || returnTrue;
 | |
|                 const thisArg = (options && options.thisArg) || undefined;
 | |
| 
 | |
|                 let ancestor = object;
 | |
| 
 | |
|                 while (ancestor) {
 | |
|                         if (filter.call(thisArg, ancestor)) {
 | |
|                                 array.push(ancestor);
 | |
|                         }
 | |
|                         ancestor = this._node(ancestor).parent;
 | |
|                 }
 | |
| 
 | |
|                 return array;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Append all descendants of the given object to an array (in tree order).
 | |
|          *
 | |
|          * * `O(n)` where `n` is the amount of objects in the sub-tree of the given `object`
 | |
|          *
 | |
|          * @method treeToArray
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} root
 | |
|          * @param {Object} [options]
 | |
|          * @param {Object[]} [options.array=[]]
 | |
|          * @param {Function} [options.filter] 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.
 | |
|          * @param {*} [options.thisArg] Value to use as `this` when executing `filter`.
 | |
|          * @return {Object[]}
 | |
|          */
 | |
|         treeToArray(root, options) {
 | |
|                 const array   = (options && options.array) || [];
 | |
|                 const filter  = (options && options.filter) || returnTrue;
 | |
|                 const thisArg = (options && options.thisArg) || undefined;
 | |
| 
 | |
|                 let object = root;
 | |
| 
 | |
|                 while (object) {
 | |
|                         if (filter.call(thisArg, object)) {
 | |
|                                 array.push(object);
 | |
|                         }
 | |
|                         object = this.following(object, {root: root});
 | |
|                 }
 | |
| 
 | |
|                 return array;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Iterate over all children of the given object
 | |
|          *
 | |
|          * * `O(1)` for a single iteration
 | |
|          *
 | |
|          * @method childrenIterator
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} parent
 | |
|          * @param {Object} [options]
 | |
|          * @param {Boolean} [options.reverse=false]
 | |
|          * @return {Object} An iterable iterator (ES6)
 | |
|          */
 | |
|         childrenIterator(parent, options) {
 | |
|                 const reverse = options && options.reverse;
 | |
|                 const parentNode = this._node(parent);
 | |
| 
 | |
|                 return new TreeIterator(
 | |
|                         this,
 | |
|                         parent,
 | |
|                         reverse ? parentNode.lastChild : parentNode.firstChild,
 | |
|                         reverse ? TreeIterator.PREV : TreeIterator.NEXT
 | |
|                 );
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Iterate over all the previous siblings of the given object. (in reverse tree order)
 | |
|          *
 | |
|          * * `O(1)` for a single iteration
 | |
|          *
 | |
|          * @method previousSiblingsIterator
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Object} An iterable iterator (ES6)
 | |
|          */
 | |
|         previousSiblingsIterator(object) {
 | |
|                 return new TreeIterator(
 | |
|                         this,
 | |
|                         object,
 | |
|                         this._node(object).previousSibling,
 | |
|                         TreeIterator.PREV
 | |
|                 );
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Iterate over all the next siblings of the given object. (in tree order)
 | |
|          *
 | |
|          * * `O(1)` for a single iteration
 | |
|          *
 | |
|          * @method nextSiblingsIterator
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Object} An iterable iterator (ES6)
 | |
|          */
 | |
|         nextSiblingsIterator(object) {
 | |
|                 return new TreeIterator(
 | |
|                         this,
 | |
|                         object,
 | |
|                         this._node(object).nextSibling,
 | |
|                         TreeIterator.NEXT
 | |
|                 );
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Iterate over all inclusive ancestors of the given object
 | |
|          *
 | |
|          * * `O(1)` for a single iteration
 | |
|          *
 | |
|          * @method ancestorsIterator
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} object
 | |
|          * @return {Object} An iterable iterator (ES6)
 | |
|          */
 | |
|         ancestorsIterator(object) {
 | |
|                 return new TreeIterator(
 | |
|                         this,
 | |
|                         object,
 | |
|                         object,
 | |
|                         TreeIterator.PARENT
 | |
|                 );
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Iterate over all descendants of the given object (in tree order).
 | |
|          *
 | |
|          * Where `n` is the amount of objects in the sub-tree of the given `root`:
 | |
|          *
 | |
|          * * `O(n)` (worst case for a single iteration)
 | |
|          * * `O(n)` (amortized, when completing the iterator)
 | |
|          *
 | |
|          * @method treeIterator
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} root
 | |
|          * @param {Object} options
 | |
|          * @param {Boolean} [options.reverse=false]
 | |
|          * @return {Object} An iterable iterator (ES6)
 | |
|          */
 | |
|         treeIterator(root, options) {
 | |
|                 const reverse = options && options.reverse;
 | |
| 
 | |
|                 return new TreeIterator(
 | |
|                         this,
 | |
|                         root,
 | |
|                         reverse ? this.lastInclusiveDescendant(root) : root,
 | |
|                         reverse ? TreeIterator.PRECEDING : TreeIterator.FOLLOWING
 | |
|                 );
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Find the index of the given object (the number of preceding siblings).
 | |
|          *
 | |
|          * * `O(n)` where `n` is the amount of preceding siblings
 | |
|          * * `O(1)` (amortized, if the tree is not modified)
 | |
|          *
 | |
|          * @method index
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} child
 | |
|          * @return {Number} The number of preceding siblings, or -1 if the object has no parent
 | |
|          */
 | |
|         index(child) {
 | |
|                 const childNode = this._node(child);
 | |
|                 const parentNode = this._node(childNode.parent);
 | |
| 
 | |
|                 if (!parentNode) {
 | |
|                         // In principal, you could also find out the number of preceding siblings
 | |
|                         // for objects that do not have a parent. This method limits itself only to
 | |
|                         // objects that have a parent because that lets us optimize more.
 | |
|                         return -1;
 | |
|                 }
 | |
| 
 | |
|                 let currentIndex = childNode.getCachedIndex(parentNode);
 | |
| 
 | |
|                 if (currentIndex >= 0) {
 | |
|                         return currentIndex;
 | |
|                 }
 | |
| 
 | |
|                 currentIndex = 0;
 | |
|                 let object = parentNode.firstChild;
 | |
| 
 | |
|                 if (parentNode.childIndexCachedUpTo) {
 | |
|                         const cachedUpToNode = this._node(parentNode.childIndexCachedUpTo);
 | |
|                         object = cachedUpToNode.nextSibling;
 | |
|                         currentIndex = cachedUpToNode.getCachedIndex(parentNode) + 1;
 | |
|                 }
 | |
| 
 | |
|                 while (object) {
 | |
|                         const node = this._node(object);
 | |
|                         node.setCachedIndex(parentNode, currentIndex);
 | |
| 
 | |
|                         if (object === child) {
 | |
|                                 break;
 | |
|                         }
 | |
| 
 | |
|                         ++currentIndex;
 | |
|                         object = node.nextSibling;
 | |
|                 }
 | |
| 
 | |
|                 parentNode.childIndexCachedUpTo = child;
 | |
| 
 | |
|                 return currentIndex;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Calculate the number of children.
 | |
|          *
 | |
|          * * `O(n)` where `n` is the amount of children
 | |
|          * * `O(1)` (amortized, if the tree is not modified)
 | |
|          *
 | |
|          * @method childrenCount
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} parent
 | |
|          * @return {Number}
 | |
|          */
 | |
|         childrenCount(parent) {
 | |
|                 const parentNode = this._node(parent);
 | |
| 
 | |
|                 if (!parentNode.lastChild) {
 | |
|                         return 0;
 | |
|                 }
 | |
| 
 | |
|                 return this.index(parentNode.lastChild) + 1;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Compare the position of an object relative to another object. A bit set is returned:
 | |
|          *
 | |
|          * <ul>
 | |
|          *     <li>DISCONNECTED : 1</li>
 | |
|          *     <li>PRECEDING : 2</li>
 | |
|          *     <li>FOLLOWING : 4</li>
 | |
|          *     <li>CONTAINS : 8</li>
 | |
|          *     <li>CONTAINED_BY : 16</li>
 | |
|          * </ul>
 | |
|          *
 | |
|          * The semantics are the same as compareDocumentPosition in DOM, with the exception that
 | |
|          * DISCONNECTED never occurs with any other bit.
 | |
|          *
 | |
|          * where `n` and `m` are the amount of ancestors of `left` and `right`;
 | |
|          * where `o` is the amount of children of the lowest common ancestor of `left` and `right`:
 | |
|          *
 | |
|          * * `O(n + m + o)` (worst case)
 | |
|          * * `O(n + m)` (amortized, if the tree is not modified)
 | |
|          *
 | |
|          * @method compareTreePosition
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} left
 | |
|          * @param {Object} right
 | |
|          * @return {Number}
 | |
|          */
 | |
|         compareTreePosition(left, right) {
 | |
|                 // In DOM terms:
 | |
|                 // left = reference / context object
 | |
|                 // right = other
 | |
| 
 | |
|                 if (left === right) {
 | |
|                         return 0;
 | |
|                 }
 | |
| 
 | |
|                 /* jshint -W016 */
 | |
| 
 | |
|                 const leftAncestors = []; { // inclusive
 | |
|                         let leftAncestor = left;
 | |
| 
 | |
|                         while (leftAncestor) {
 | |
|                                 if (leftAncestor === right) {
 | |
|                                         return TreePosition.CONTAINS | TreePosition.PRECEDING;
 | |
|                                         // other is ancestor of reference
 | |
|                                 }
 | |
| 
 | |
|                                 leftAncestors.push(leftAncestor);
 | |
|                                 leftAncestor = this.parent(leftAncestor);
 | |
|                         }
 | |
|                 }
 | |
| 
 | |
| 
 | |
|                 const rightAncestors = []; {
 | |
|                         let rightAncestor = right;
 | |
| 
 | |
|                         while (rightAncestor) {
 | |
|                                 if (rightAncestor === left) {
 | |
|                                         return TreePosition.CONTAINED_BY | TreePosition.FOLLOWING;
 | |
|                                 }
 | |
| 
 | |
|                                 rightAncestors.push(rightAncestor);
 | |
|                                 rightAncestor = this.parent(rightAncestor);
 | |
|                         }
 | |
|                 }
 | |
| 
 | |
| 
 | |
|                 const root = reverseArrayIndex(leftAncestors, 0);
 | |
| 
 | |
|                 if (!root || root !== reverseArrayIndex(rightAncestors, 0)) {
 | |
|                         // note: unlike DOM, preceding / following is not set here
 | |
|                         return TreePosition.DISCONNECTED;
 | |
|                 }
 | |
| 
 | |
|                 // find the lowest common ancestor
 | |
|                 let commonAncestorIndex = 0;
 | |
|                 const ancestorsMinLength = Math.min(leftAncestors.length, rightAncestors.length);
 | |
| 
 | |
|                 for (let i = 0; i < ancestorsMinLength; ++i) {
 | |
|                         const leftAncestor  = reverseArrayIndex(leftAncestors, i);
 | |
|                         const rightAncestor = reverseArrayIndex(rightAncestors, i);
 | |
| 
 | |
|                         if (leftAncestor !== rightAncestor) {
 | |
|                                 break;
 | |
|                         }
 | |
| 
 | |
|                         commonAncestorIndex = i;
 | |
|                 }
 | |
| 
 | |
|                 // indexes within the common ancestor
 | |
|                 const leftIndex  = this.index(reverseArrayIndex(leftAncestors, commonAncestorIndex + 1));
 | |
|                 const rightIndex = this.index(reverseArrayIndex(rightAncestors, commonAncestorIndex + 1));
 | |
| 
 | |
|                 return rightIndex < leftIndex
 | |
|                         ? TreePosition.PRECEDING
 | |
|                         : TreePosition.FOLLOWING;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Remove the object from this tree.
 | |
|          * Has no effect if already removed.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method remove
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} removeObject
 | |
|          * @return {Object} removeObject
 | |
|          */
 | |
|         remove(removeObject) {
 | |
|                 const removeNode = this._node(removeObject);
 | |
|                 const parentNode = this._node(removeNode.parent);
 | |
|                 const prevNode = this._node(removeNode.previousSibling);
 | |
|                 const nextNode = this._node(removeNode.nextSibling);
 | |
| 
 | |
|                 if (parentNode) {
 | |
|                         if (parentNode.firstChild === removeObject) {
 | |
|                                 parentNode.firstChild = removeNode.nextSibling;
 | |
|                         }
 | |
| 
 | |
|                         if (parentNode.lastChild === removeObject) {
 | |
|                                 parentNode.lastChild = removeNode.previousSibling;
 | |
|                         }
 | |
|                 }
 | |
| 
 | |
|                 if (prevNode) {
 | |
|                         prevNode.nextSibling = removeNode.nextSibling;
 | |
|                 }
 | |
| 
 | |
|                 if (nextNode) {
 | |
|                         nextNode.previousSibling = removeNode.previousSibling;
 | |
|                 }
 | |
| 
 | |
|                 removeNode.parent = null;
 | |
|                 removeNode.previousSibling = null;
 | |
|                 removeNode.nextSibling = null;
 | |
|                 removeNode.cachedIndex = -1;
 | |
|                 removeNode.cachedIndexVersion = NaN;
 | |
| 
 | |
|                 if (parentNode) {
 | |
|                         parentNode.childrenChanged();
 | |
|                 }
 | |
| 
 | |
|                 return removeObject;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Insert the given object before the reference object.
 | |
|          * `newObject` is now the previous sibling of `referenceObject`.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method insertBefore
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} referenceObject
 | |
|          * @param {Object} newObject
 | |
|          * @throws {Error} If the newObject is already present in this SymbolTree
 | |
|          * @return {Object} newObject
 | |
|          */
 | |
|         insertBefore(referenceObject, newObject) {
 | |
|                 const referenceNode = this._node(referenceObject);
 | |
|                 const prevNode = this._node(referenceNode.previousSibling);
 | |
|                 const newNode = this._node(newObject);
 | |
|                 const parentNode = this._node(referenceNode.parent);
 | |
| 
 | |
|                 if (newNode.isAttached) {
 | |
|                         throw Error('Given object is already present in this SymbolTree, remove it first');
 | |
|                 }
 | |
| 
 | |
|                 newNode.parent = referenceNode.parent;
 | |
|                 newNode.previousSibling = referenceNode.previousSibling;
 | |
|                 newNode.nextSibling = referenceObject;
 | |
|                 referenceNode.previousSibling = newObject;
 | |
| 
 | |
|                 if (prevNode) {
 | |
|                         prevNode.nextSibling = newObject;
 | |
|                 }
 | |
| 
 | |
|                 if (parentNode && parentNode.firstChild === referenceObject) {
 | |
|                         parentNode.firstChild = newObject;
 | |
|                 }
 | |
| 
 | |
|                 if (parentNode) {
 | |
|                         parentNode.childrenChanged();
 | |
|                 }
 | |
| 
 | |
|                 return newObject;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Insert the given object after the reference object.
 | |
|          * `newObject` is now the next sibling of `referenceObject`.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method insertAfter
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} referenceObject
 | |
|          * @param {Object} newObject
 | |
|          * @throws {Error} If the newObject is already present in this SymbolTree
 | |
|          * @return {Object} newObject
 | |
|          */
 | |
|         insertAfter(referenceObject, newObject) {
 | |
|                 const referenceNode = this._node(referenceObject);
 | |
|                 const nextNode = this._node(referenceNode.nextSibling);
 | |
|                 const newNode = this._node(newObject);
 | |
|                 const parentNode = this._node(referenceNode.parent);
 | |
| 
 | |
|                 if (newNode.isAttached) {
 | |
|                         throw Error('Given object is already present in this SymbolTree, remove it first');
 | |
|                 }
 | |
| 
 | |
|                 newNode.parent = referenceNode.parent;
 | |
|                 newNode.previousSibling = referenceObject;
 | |
|                 newNode.nextSibling = referenceNode.nextSibling;
 | |
|                 referenceNode.nextSibling = newObject;
 | |
| 
 | |
|                 if (nextNode) {
 | |
|                         nextNode.previousSibling = newObject;
 | |
|                 }
 | |
| 
 | |
|                 if (parentNode && parentNode.lastChild === referenceObject) {
 | |
|                         parentNode.lastChild = newObject;
 | |
|                 }
 | |
| 
 | |
|                 if (parentNode) {
 | |
|                         parentNode.childrenChanged();
 | |
|                 }
 | |
| 
 | |
|                 return newObject;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Insert the given object as the first child of the given reference object.
 | |
|          * `newObject` is now the first child of `referenceObject`.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method prependChild
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} referenceObject
 | |
|          * @param {Object} newObject
 | |
|          * @throws {Error} If the newObject is already present in this SymbolTree
 | |
|          * @return {Object} newObject
 | |
|          */
 | |
|         prependChild(referenceObject, newObject) {
 | |
|                 const referenceNode = this._node(referenceObject);
 | |
|                 const newNode = this._node(newObject);
 | |
| 
 | |
|                 if (newNode.isAttached) {
 | |
|                         throw Error('Given object is already present in this SymbolTree, remove it first');
 | |
|                 }
 | |
| 
 | |
|                 if (referenceNode.hasChildren) {
 | |
|                         this.insertBefore(referenceNode.firstChild, newObject);
 | |
|                 }
 | |
|                 else {
 | |
|                         newNode.parent = referenceObject;
 | |
|                         referenceNode.firstChild = newObject;
 | |
|                         referenceNode.lastChild = newObject;
 | |
|                         referenceNode.childrenChanged();
 | |
|                 }
 | |
| 
 | |
|                 return newObject;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Insert the given object as the last child of the given reference object.
 | |
|          * `newObject` is now the last child of `referenceObject`.
 | |
|          *
 | |
|          * * `O(1)`
 | |
|          *
 | |
|          * @method appendChild
 | |
|          * @memberOf module:symbol-tree#
 | |
|          * @param {Object} referenceObject
 | |
|          * @param {Object} newObject
 | |
|          * @throws {Error} If the newObject is already present in this SymbolTree
 | |
|          * @return {Object} newObject
 | |
|          */
 | |
|         appendChild(referenceObject, newObject) {
 | |
|                 const referenceNode = this._node(referenceObject);
 | |
|                 const newNode = this._node(newObject);
 | |
| 
 | |
|                 if (newNode.isAttached) {
 | |
|                         throw Error('Given object is already present in this SymbolTree, remove it first');
 | |
|                 }
 | |
| 
 | |
|                 if (referenceNode.hasChildren) {
 | |
|                         this.insertAfter(referenceNode.lastChild, newObject);
 | |
|                 }
 | |
|                 else {
 | |
|                         newNode.parent = referenceObject;
 | |
|                         referenceNode.firstChild = newObject;
 | |
|                         referenceNode.lastChild = newObject;
 | |
|                         referenceNode.childrenChanged();
 | |
|                 }
 | |
| 
 | |
|                 return newObject;
 | |
|         }
 | |
| }
 | |
| 
 | |
| module.exports = SymbolTree;
 | |
| SymbolTree.TreePosition = TreePosition;
 |