Added more tests for ve.*Node and ve.dm.*Node classes

In this commit several methods (child node add/remove and parent/root modification) were also moved to ve.dm.BranchNode ve.dm.Node respectively. ve.Node and ve.BranchNode are immutable. ve.dm.Node and ve.dm.BranchNode are mutable. Other subclasses of ve.Node and ve.BranchNode should implement functionality to mimic changes made to a data model.

Change-Id: Ia9ff78764f8f50f99fc8f9f9593657c0a0bf287e
This commit is contained in:
Trevor Parscal 2012-04-19 16:03:59 -07:00
parent c9ce7dbffe
commit fd49e8df32
11 changed files with 287 additions and 139 deletions

View file

@ -7,17 +7,41 @@
* @extends {ve.BranchNode}
* @extends {ve.dm.Node}
* @param {String} type Symbolic name of node type
* @param {Array} [children] Child nodes to attach
* @param {ve.dm.Node[]} [children] Child nodes to attach
* @param {Object} [attributes] Reference to map of attribute key/value pairs
*/
ve.dm.BranchNode = function( type, children, attributes ) {
// Inheritance
ve.BranchNode.call( this, children );
ve.BranchNode.call( this );
ve.dm.Node.call( this, type, 0, attributes );
if ( ve.isArray( children ) && children.length ) {
for ( var i = 0; i < children.length; i++ ) {
this.push( children[i] );
}
}
};
/* Methods */
/**
* Sets the root node to this and all of its descendants, recursively.
*
* @method
* @see {ve.Node.prototype.setRoot}
* @param {ve.Node} root Node to use as root
*/
ve.dm.BranchNode.prototype.setRoot = function( root ) {
if ( root == this.root ) {
// Nothing to do, don't recurse into all descendants
return;
}
this.root = root;
for ( var i = 0; i < this.children.length; i++ ) {
this.children[i].setRoot( root );
}
};
/**
* Adds a node to the end of this node's children.
*
@ -50,6 +74,21 @@ ve.dm.BranchNode.prototype.pop = function() {
}
};
/**
* Adds a node to the beginning of this node's children.
*
* @method
* @param {ve.dm.BranchNode} childModel Item to add
* @returns {Integer} New number of children
* @emits beforeSplice (0, 0, [childModel])
* @emits afterSplice (0, 0, [childModel])
* @emits update
*/
ve.dm.BranchNode.prototype.unshift = function( childModel ) {
this.splice( 0, 0, childModel );
return this.children.length;
};
/**
* Removes a node from the beginning of this node's children
*
@ -67,21 +106,6 @@ ve.dm.BranchNode.prototype.shift = function() {
}
};
/**
* Adds a node to the beginning of this node's children.
*
* @method
* @param {ve.dm.BranchNode} childModel Item to add
* @returns {Integer} New number of children
* @emits beforeSplice (0, 0, [childModel])
* @emits afterSplice (0, 0, [childModel])
* @emits update
*/
ve.dm.BranchNode.prototype.unshift = function( childModel ) {
this.splice( 0, 0, childModel );
return this.children.length;
};
/**
* Adds and removes nodes from this node's children.
*
@ -107,16 +131,16 @@ ve.dm.BranchNode.prototype.splice = function( index, howmany ) {
}
args[i].attach( this );
args[i].on( 'update', this.emitUpdate );
diff += args[i].getElementLength();
diff += args[i].getLength();
}
}
var removals = this.children.splice.apply( this.children, args );
for ( i = 0, length = removals.length; i < length; i++ ) {
removals[i].detach();
removals[i].removeListener( 'update', this.emitUpdate );
diff -= removals[i].getElementLength();
diff -= removals[i].getLength();
}
this.adjustContentLength( diff, true );
this.adjustLength( diff, true );
this.emit.apply( this, ['afterSplice'].concat( args ) );
this.emit( 'update' );
return removals;

View file

@ -105,6 +105,66 @@ ve.dm.Node.prototype.getAttribute = function( key ) {
return this.attributes[key];
};
/**
* Gets a reference to this node's parent.
*
* @method
* @returns {ve.Node} Reference to this node's parent
*/
ve.Node.prototype.getParent = function() {
return this.parent;
};
/**
* Gets the root node in the tree this node is currently attached to.
*
* @method
* @returns {ve.Node} Root node
*/
ve.Node.prototype.getRoot = function() {
return this.root;
};
/**
* Sets the root node to this and all of its children.
*
* This method is overridden by nodes with children.
*
* @method
* @param {ve.Node} root Node to use as root
*/
ve.Node.prototype.setRoot = function( root ) {
// TODO events?
this.root = root;
};
/**
* Attaches this node to another as a child.
*
* @method
* @param {ve.Node} parent Node to attach to
* @emits attach (parent)
*/
ve.Node.prototype.attach = function( parent ) {
this.emit( 'beforeAttach', parent );
this.parent = parent;
this.setRoot( parent.getRoot() );
this.emit( 'afterAttach', parent );
};
/**
* Detaches this node from its parent.
*
* @method
* @emits detach
*/
ve.Node.prototype.detach = function() {
this.emit( 'beforeDetach' );
this.parent = null;
this.setRoot( this );
this.emit( 'afterDetach' );
};
/* Inheritance */
ve.extendClass( ve.dm.Node, ve.Node );

View file

@ -7,7 +7,7 @@
* @extends {ve.TwigNode}
* @extends {ve.dm.BranchNode}
* @param {String} type Symbolic name of node type
* @param {Array} [children] Child nodes to attach
* @param {ve.dm.Node[]} [children] Child nodes to attach
* @param {Object} [attributes] Reference to map of attribute key/value pairs
*/
ve.dm.TwigNode = function( type, children, attributes ) {

View file

@ -1,6 +1,10 @@
/**
* Mixin for branch node functionality
*
* Branch nodes are immutable, which is why there are no methods for adding or removing children.
* DataModel classes will add this functionality, and other subclasses will implement behavior that
* mimcs changes made to data model nodes.
*
* @class
* @abstract
* @constructor
@ -44,22 +48,3 @@ ve.BranchNode.prototype.getChildren = function() {
ve.BranchNode.prototype.indexOf = function( node ) {
return ve.inArray( node, this.children );
};
/**
* Sets the root node to this and all of its descendants, recursively.
*
* @method
* @see {ve.Node.prototype.setRoot}
* @param {ve.Node} root Node to use as root
*/
ve.BranchNode.prototype.setRoot = function( root ) {
if ( root == this.root ) {
// Nothing to do, don't recurse into all descendants
return;
}
this.root = root;
for ( var i = 0; i < this.children.length; i++ ) {
this.children[i].setRoot( root );
}
};

View file

@ -61,66 +61,6 @@ ve.Node.prototype.getType = function() {
return this.type;
};
/**
* Gets a reference to this node's parent.
*
* @method
* @returns {ve.Node} Reference to this node's parent
*/
ve.Node.prototype.getParent = function() {
return this.parent;
};
/**
* Gets the root node in the tree this node is currently attached to.
*
* @method
* @returns {ve.Node} Root node
*/
ve.Node.prototype.getRoot = function() {
return this.root;
};
/**
* Sets the root node to this and all of its children.
*
* This method is overridden by nodes with children.
*
* @method
* @param {ve.Node} root Node to use as root
*/
ve.Node.prototype.setRoot = function( root ) {
// TODO events?
this.root = root;
};
/**
* Attaches this node to another as a child.
*
* @method
* @param {ve.Node} parent Node to attach to
* @emits attach (parent)
*/
ve.Node.prototype.attach = function( parent ) {
this.emit( 'beforeAttach', parent );
this.parent = parent;
this.setRoot( parent.getRoot() );
this.emit( 'afterAttach', parent );
};
/**
* Detaches this node from its parent.
*
* @method
* @emits detach
*/
ve.Node.prototype.detach = function() {
this.emit( 'beforeDetach' );
this.parent = null;
this.setRoot( this );
this.emit( 'afterDetach' );
};
/* Inheritance */
ve.extendClass( ve.Node, ve.EventEmitter );

View file

@ -1 +1,71 @@
module( 've.dm.BranchNode' );
/* Stubs */
ve.dm.BranchNodeStub = function( children ) {
// Inheritance
ve.dm.BranchNode.call( this, 'stub', children );
};
ve.extendClass( ve.dm.BranchNodeStub, ve.dm.BranchNode );
/* Tests */
test( 'prototype.setRoot', 3, function() {
var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub( [node1] ),
node3 = new ve.dm.BranchNodeStub( [node2] ),
node4 = new ve.dm.BranchNodeStub();
node3.setRoot( node4 );
strictEqual( node3.getRoot(), node4 );
strictEqual( node2.getRoot(), node4 );
strictEqual( node1.getRoot(), node4 );
} );
test( 'prototype.push', 2, function() {
var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub( [node1] );
strictEqual( node3.push( node2 ), 2 );
deepEqual( node3.getChildren(), [node1, node2] );
} );
test( 'prototype.pop', 2, function() {
var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub( [node1, node2] );
strictEqual( node3.pop(), node2 );
deepEqual( node3.getChildren(), [node1] );
} );
test( 'prototype.unshift', 2, function() {
var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub( [node1] );
strictEqual( node3.unshift( node2 ), 2 );
deepEqual( node3.getChildren(), [node2, node1] );
} );
test( 'prototype.shift', 2, function() {
var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub( [node1, node2] );
strictEqual( node3.shift(), node1 );
deepEqual( node3.getChildren(), [node2] );
} );
test( 'prototype.splice', 6, function() {
var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub(),
node4 = new ve.dm.BranchNodeStub( [node1, node2] );
// Insert
deepEqual( node4.splice( 1, 0, node3 ), [] );
deepEqual( node4.getChildren(), [node1, node3, node2] );
// Remove
deepEqual( node4.splice( 1, 1 ), [node3] );
deepEqual( node4.getChildren(), [node1, node2] );
// Remove and insert
deepEqual( node4.splice( 1, 1, node3 ), [node2] );
deepEqual( node4.getChildren(), [node1, node3] );
} );

View file

@ -1 +1,87 @@
module( 've.dm.Node' );
/* Stubs */
ve.dm.NodeStub = function( length, attributes ) {
// Inheritance
ve.dm.Node.call( this, 'stub', length, attributes );
};
ve.extendClass( ve.dm.NodeStub, ve.dm.Node );
/* Tests */
test( 'prototype.createView', 1, function() {
raises( function() {
var node = new ve.dm.NodeStub();
// Abstract method, must be overridden, throws exception when called
node.createView();
}, 'throws exception when called' );
} );
test( 'prototype.getLength', 2, function() {
var node1 = new ve.dm.NodeStub(),
node2 = new ve.dm.NodeStub( 1234 );
strictEqual( node1.getLength(), 0 );
strictEqual( node2.getLength(), 1234 );
} );
test( 'prototype.getOuterLength', 2, function() {
var node1 = new ve.dm.NodeStub(),
node2 = new ve.dm.NodeStub( 1234 );
strictEqual( node1.getOuterLength(), 0 );
strictEqual( node2.getOuterLength(), 1234 );
} );
test( 'prototype.setLength', 1, function() {
var node = new ve.dm.NodeStub();
node.setLength( 1234 );
strictEqual( node.getLength(), 1234 );
} );
test( 'prototype.adjustLength', 1, function() {
var node = new ve.dm.NodeStub( 1234 );
node.adjustLength( 5678 );
strictEqual( node.getLength(), 6912 );
} );
test( 'prototype.getAttribute', 2, function() {
var node = new ve.dm.NodeStub( 0, { 'a': 1, 'b': 2 } );
strictEqual( node.getAttribute( 'a' ), 1 );
strictEqual( node.getAttribute( 'b' ), 2 );
} );
test( 'prototype.getParent', 1, function() {
var node = new ve.dm.NodeStub();
strictEqual( node.getParent(), null );
} );
test( 'prototype.getRoot', 1, function() {
var node = new ve.dm.NodeStub();
strictEqual( node.getRoot(), node );
} );
test( 'prototype.setRoot', 1, function() {
var node1 = new ve.dm.NodeStub(),
node2 = new ve.dm.NodeStub();
node1.setRoot( node2 );
strictEqual( node1.getRoot(), node2 );
} );
test( 'prototype.attach', 2, function() {
var node1 = new ve.dm.NodeStub(),
node2 = new ve.dm.NodeStub();
node1.attach( node2 );
strictEqual( node1.getParent(), node2 );
strictEqual( node1.getRoot(), node2 );
} );
test( 'prototype.detach', 2, function() {
var node1 = new ve.dm.NodeStub(),
node2 = new ve.dm.NodeStub();
node1.attach( node2 );
node1.detach();
strictEqual( node1.getParent(), null );
strictEqual( node1.getRoot(), node1 );
} );

View file

@ -2,9 +2,9 @@ module( 've.BranchNode' );
/* Stubs */
ve.BranchNodeStub = function() {
ve.BranchNodeStub = function( children ) {
// Inheritance
ve.BranchNode.call( this, 'stub' );
ve.BranchNode.call( this, children );
};
ve.extendClass( ve.BranchNodeStub, ve.BranchNode );
@ -20,3 +20,21 @@ test( 'prototype.canHaveGrandchildren', 1, function() {
var node = new ve.BranchNodeStub();
strictEqual( node.canHaveGrandchildren(), true );
} );
test( 'prototype.getChildren', 2, function() {
var node1 = new ve.BranchNodeStub(),
node2 = new ve.BranchNodeStub( [node1] );
deepEqual( node1.getChildren(), [] );
deepEqual( node2.getChildren(), [node1] );
} );
test( 'prototype.indexOf', 4, function() {
var node1 = new ve.BranchNodeStub(),
node2 = new ve.BranchNodeStub(),
node3 = new ve.BranchNodeStub(),
node4 = new ve.BranchNodeStub( [node1, node2, node3] );
strictEqual( node4.indexOf( null ), -1 );
strictEqual( node4.indexOf( node1 ), 0 );
strictEqual( node4.indexOf( node2 ), 1 );
strictEqual( node4.indexOf( node3 ), 2 );
} );

View file

@ -4,7 +4,7 @@ module( 've.LeafNode' );
ve.LeafNodeStub = function() {
// Inheritance
ve.LeafNode.call( this, 'stub' );
ve.LeafNode.call( this );
};
ve.extendClass( ve.LeafNodeStub, ve.LeafNode );

View file

@ -31,38 +31,3 @@ test( 'prototype.getType', 1, function() {
var node = new ve.NodeStub();
strictEqual( node.getType(), 'stub' );
} );
test( 'prototype.getParent', 1, function() {
var node = new ve.NodeStub();
strictEqual( node.getParent(), null );
} );
test( 'prototype.getRoot', 1, function() {
var node = new ve.NodeStub();
strictEqual( node.getRoot(), node );
} );
test( 'prototype.setRoot', 1, function() {
var node1 = new ve.NodeStub(),
node2 = new ve.NodeStub();
node1.setRoot( node2 );
strictEqual( node1.getRoot(), node2 );
} );
test( 'prototype.attach', 2, function() {
var node1 = new ve.NodeStub(),
node2 = new ve.NodeStub();
node1.attach( node2 );
strictEqual( node1.getParent(), node2 );
strictEqual( node1.getRoot(), node2 );
} );
test( 'prototype.detach', 2, function() {
var node1 = new ve.NodeStub(),
node2 = new ve.NodeStub();
node1.attach( node2 );
node1.detach();
strictEqual( node1.getParent(), null );
strictEqual( node1.getRoot(), node1 );
} );

View file

@ -4,7 +4,7 @@ module( 've.TwigNode' );
ve.TwigNodeStub = function() {
// Inheritance
ve.TwigNode.call( this, 'stub' );
ve.TwigNode.call( this );
};
ve.extendClass( ve.TwigNodeStub, ve.TwigNode );