diff --git a/modules/ve2/dm/ve.dm.BranchNode.js b/modules/ve2/dm/ve.dm.BranchNode.js index e74542b2b2..7b9b99ec13 100644 --- a/modules/ve2/dm/ve.dm.BranchNode.js +++ b/modules/ve2/dm/ve.dm.BranchNode.js @@ -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; diff --git a/modules/ve2/dm/ve.dm.Node.js b/modules/ve2/dm/ve.dm.Node.js index 4a3ad6fe9a..f02ebd9a97 100644 --- a/modules/ve2/dm/ve.dm.Node.js +++ b/modules/ve2/dm/ve.dm.Node.js @@ -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 ); diff --git a/modules/ve2/dm/ve.dm.TwigNode.js b/modules/ve2/dm/ve.dm.TwigNode.js index 1d5a1550bf..f4f776874f 100644 --- a/modules/ve2/dm/ve.dm.TwigNode.js +++ b/modules/ve2/dm/ve.dm.TwigNode.js @@ -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 ) { diff --git a/modules/ve2/ve.BranchNode.js b/modules/ve2/ve.BranchNode.js index 2490b9d01d..84310d5acd 100644 --- a/modules/ve2/ve.BranchNode.js +++ b/modules/ve2/ve.BranchNode.js @@ -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 ); - } -}; diff --git a/modules/ve2/ve.Node.js b/modules/ve2/ve.Node.js index 4326cc076e..c2ae35ebb7 100644 --- a/modules/ve2/ve.Node.js +++ b/modules/ve2/ve.Node.js @@ -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 ); diff --git a/tests/ve2/dm/ve.dm.BranchNode.test.js b/tests/ve2/dm/ve.dm.BranchNode.test.js index 4911fedb68..3aed5974d9 100644 --- a/tests/ve2/dm/ve.dm.BranchNode.test.js +++ b/tests/ve2/dm/ve.dm.BranchNode.test.js @@ -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] ); +} ); diff --git a/tests/ve2/dm/ve.dm.Node.test.js b/tests/ve2/dm/ve.dm.Node.test.js index e5a0b1a4e2..9ef67486b4 100644 --- a/tests/ve2/dm/ve.dm.Node.test.js +++ b/tests/ve2/dm/ve.dm.Node.test.js @@ -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 ); +} ); + diff --git a/tests/ve2/ve.BranchNode.test.js b/tests/ve2/ve.BranchNode.test.js index e0b1767f0b..6fed33fbcd 100644 --- a/tests/ve2/ve.BranchNode.test.js +++ b/tests/ve2/ve.BranchNode.test.js @@ -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 ); +} ); diff --git a/tests/ve2/ve.LeafNode.test.js b/tests/ve2/ve.LeafNode.test.js index 4790dd3312..26c086c971 100644 --- a/tests/ve2/ve.LeafNode.test.js +++ b/tests/ve2/ve.LeafNode.test.js @@ -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 ); diff --git a/tests/ve2/ve.Node.test.js b/tests/ve2/ve.Node.test.js index 1d0e098cff..3b2c83597f 100644 --- a/tests/ve2/ve.Node.test.js +++ b/tests/ve2/ve.Node.test.js @@ -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 ); -} ); - diff --git a/tests/ve2/ve.TwigNode.test.js b/tests/ve2/ve.TwigNode.test.js index 09cb7c0a9d..52204f8ce6 100644 --- a/tests/ve2/ve.TwigNode.test.js +++ b/tests/ve2/ve.TwigNode.test.js @@ -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 );