r110809 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r110808‎ | r110809 | r110810 >
Date:00:12, 7 February 2012
Author:tparscal
Status:deferred
Tags:nodeploy, visualeditor 
Comment:
Updated unit tests in response to structural changes in r110805
Modified paths:
  • /trunk/extensions/VisualEditor/tests/es (deleted) (history)
  • /trunk/extensions/VisualEditor/tests/ve (added) (history)
  • /trunk/extensions/VisualEditor/tests/ve/es.DocumentBranchNode.test.js (deleted) (history)
  • /trunk/extensions/VisualEditor/tests/ve/es.DocumentModel.test.js (deleted) (history)
  • /trunk/extensions/VisualEditor/tests/ve/es.DocumentModelBranchNode.test.js (deleted) (history)
  • /trunk/extensions/VisualEditor/tests/ve/es.DocumentNode.test.js (deleted) (history)
  • /trunk/extensions/VisualEditor/tests/ve/es.TransactionProcessor.test.js (deleted) (history)
  • /trunk/extensions/VisualEditor/tests/ve/es.test.js (deleted) (history)
  • /trunk/extensions/VisualEditor/tests/ve/es.testData.js (deleted) (history)
  • /trunk/extensions/VisualEditor/tests/ve/index.html (modified) (history)
  • /trunk/extensions/VisualEditor/tests/ve/ve.BranchNode.test.js (added) (history)
  • /trunk/extensions/VisualEditor/tests/ve/ve.Node.test.js (added) (history)
  • /trunk/extensions/VisualEditor/tests/ve/ve.dm.BranchNode.test.js (added) (history)
  • /trunk/extensions/VisualEditor/tests/ve/ve.dm.DocumentNode.test.js (added) (history)
  • /trunk/extensions/VisualEditor/tests/ve/ve.dm.TransactionProcessor.test.js (added) (history)
  • /trunk/extensions/VisualEditor/tests/ve/ve.test.js (added) (history)
  • /trunk/extensions/VisualEditor/tests/ve/ve.testData.js (added) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/tests/ve/ve.test.js
@@ -0,0 +1,14 @@
 2+module( 've' );
 3+
 4+test( 've.insertIntoArray', 1, function() {
 5+ var insert = [], i, arr = ['foo', 'bar'], expected = [];
 6+ expected[0] = 'foo';
 7+ for ( i = 0; i < 3000; i++ ) {
 8+ insert[i] = i;
 9+ expected[i + 1] = i;
 10+ }
 11+ expected[3001] = 'bar';
 12+
 13+ ve.insertIntoArray( arr, 1, insert );
 14+ deepEqual( arr, expected, 'splicing 3000 elements into the middle of a 2-element array' );
 15+} );
Property changes on: trunk/extensions/VisualEditor/tests/ve/ve.test.js
___________________________________________________________________
Added: svn:eol-style
116 + native
Index: trunk/extensions/VisualEditor/tests/ve/ve.dm.BranchNode.test.js
@@ -0,0 +1,129 @@
 2+module( 've/dm' );
 3+
 4+test( 've.dm.BranchNode', 20, function() {
 5+ // Example data (integers) is used for simplicity of testing
 6+ var node1 = new ve.dm.BranchNode( '1' ),
 7+ node2 = new ve.dm.BranchNode( '2' ),
 8+ node3 = new ve.dm.BranchNode(
 9+ '3',
 10+ null,
 11+ [new ve.dm.BranchNode( '3a' )]
 12+ ),
 13+ node4 = new ve.dm.BranchNode(
 14+ '4',
 15+ null,
 16+ [new ve.dm.BranchNode( '4a' ), new ve.dm.BranchNode( '4b' )]
 17+ );
 18+
 19+ // Event triggering is detected using a callback that increments a counter
 20+ var updates = 0;
 21+ node1.on( 'update', function() {
 22+ updates++;
 23+ } );
 24+ var attaches = 0;
 25+ node2.on( 'afterAttach', function() {
 26+ attaches++;
 27+ } );
 28+ node3.on( 'afterAttach', function() {
 29+ attaches++;
 30+ } );
 31+ node4.on( 'afterAttach', function() {
 32+ attaches++;
 33+ } );
 34+ var detaches = 0;
 35+ node2.on( 'afterDetach', function() {
 36+ detaches++;
 37+ } );
 38+ node3.on( 'afterDetach', function() {
 39+ detaches++;
 40+ } );
 41+ node4.on( 'afterDetach', function() {
 42+ detaches++;
 43+ } );
 44+ function strictArrayValueEqual( a, b, msg ) {
 45+ if ( a.length !== b.length ) {
 46+ ok( false, msg );
 47+ return;
 48+ }
 49+ for ( var i = 0; i < a.length; i++ ) {
 50+ if ( a[i] !== b[i] ) {
 51+ ok( false, msg );
 52+ return;
 53+ }
 54+ }
 55+ ok( true, msg );
 56+ }
 57+
 58+ // Test 1
 59+ node1.push( node2 );
 60+ equal( updates, 1, 'push emits update events' );
 61+ strictArrayValueEqual( node1.getChildren(), [node2], 'push appends a node' );
 62+
 63+ // Test 2
 64+ equal( attaches, 1, 'push attaches added node' );
 65+
 66+ // Test 3, 4
 67+ node1.unshift( node3 );
 68+ equal( updates, 2, 'unshift emits update events' );
 69+ strictArrayValueEqual( node1.getChildren(), [node3, node2], 'unshift prepends a node' );
 70+
 71+ // Test 5
 72+ equal( attaches, 2, 'unshift attaches added node' );
 73+
 74+ // Test 6, 7
 75+ node1.splice( 1, 0, node4 );
 76+ equal( updates, 3, 'splice emits update events' );
 77+ strictArrayValueEqual( node1.getChildren(), [node3, node4, node2], 'splice inserts nodes' );
 78+
 79+ // Test 8
 80+ equal( attaches, 3, 'splice attaches added nodes' );
 81+
 82+ // Test 9
 83+ node1.reverse();
 84+ equal( updates, 4, 'reverse emits update events' );
 85+
 86+ // Test 10, 11
 87+ node1.sort( function( a, b ) {
 88+ return a.getChildren().length < b.getChildren().length ? -1 : 1;
 89+ } );
 90+ equal( updates, 5, 'sort emits update events' );
 91+ strictArrayValueEqual(
 92+ node1.getChildren(),
 93+ [node2, node3, node4],
 94+ 'sort reorderes nodes correctly'
 95+ );
 96+
 97+ // Test 12, 13
 98+ node1.pop();
 99+ equal( updates, 6, 'pop emits update events' );
 100+ strictArrayValueEqual(
 101+ node1.getChildren(),
 102+ [node2, node3],
 103+ 'pop removes the last child node'
 104+ );
 105+
 106+ // Test 14
 107+ equal( detaches, 1, 'pop detaches a node' );
 108+
 109+ // Test 15, 16
 110+ node1.shift();
 111+ equal( updates, 7, 've.ModelNode emits update events on shift' );
 112+ strictArrayValueEqual(
 113+ node1.getChildren(),
 114+ [node3],
 115+ 've.ModelNode removes first Node on shift'
 116+ );
 117+
 118+ // Test 17
 119+ equal( detaches, 2, 'shift detaches a node' );
 120+
 121+ // Test 18
 122+ strictEqual( node3.getParent(), node1, 'getParent returns the correct reference' );
 123+
 124+ // Test 19
 125+ try {
 126+ var view = node3.createView();
 127+ } catch ( err ){
 128+ ok( true, 'createView throws an exception when not overridden' );
 129+ }
 130+} );
Property changes on: trunk/extensions/VisualEditor/tests/ve/ve.dm.BranchNode.test.js
___________________________________________________________________
Added: svn:eol-style
1131 + native
Index: trunk/extensions/VisualEditor/tests/ve/ve.BranchNode.test.js
@@ -0,0 +1,1107 @@
 2+module( 've' );
 3+
 4+/* Stubs */
 5+
 6+function BranchNodeStub( items, name, size ) {
 7+ // Inheritance
 8+ ve.BranchNode.call( this, items );
 9+
 10+ // Properties
 11+ this.name = name;
 12+ this.size = size;
 13+}
 14+
 15+BranchNodeStub.prototype.getContentLength = function() {
 16+ return this.size;
 17+};
 18+
 19+BranchNodeStub.prototype.getElementLength = function() {
 20+ // Mimic document data which has an opening and closing around the content
 21+ return this.size + 2;
 22+};
 23+
 24+ve.extendClass( BranchNodeStub, ve.BranchNode );
 25+
 26+/* Tests */
 27+
 28+test( 've.BranchNodeStub.getElementLength', 1, function() {
 29+ // Test 1
 30+ strictEqual(
 31+ ( new BranchNodeStub( [], 'a', 0 ) ).getElementLength(),
 32+ 2,
 33+ 'BranchNodeStub.getElementLength() returns initialized length plus 2 for elements'
 34+ );
 35+} );
 36+
 37+// Common stubs
 38+var a = new BranchNodeStub( [], 'a', 0 ),
 39+ b = new BranchNodeStub( [], 'b', 1 ),
 40+ c = new BranchNodeStub( [], 'c', 2 ),
 41+ d = new BranchNodeStub( [], 'd', 3 ),
 42+ e = new BranchNodeStub( [], 'e', 4 ),
 43+ root1 = new BranchNodeStub( [a, b, c, d, e], 'root1', 20 );
 44+
 45+test( 've.BranchNode.getRangeFromNode', 6, function() {
 46+ // Tests 1 .. 6
 47+ var getRangeFromNodeTests = [
 48+ { 'input': a, 'output': new ve.Range( 0, 2 ) },
 49+ { 'input': b, 'output': new ve.Range( 2, 5 ) },
 50+ { 'input': c, 'output': new ve.Range( 5, 9 ) },
 51+ { 'input': d, 'output': new ve.Range( 9, 14 ) },
 52+ { 'input': e, 'output': new ve.Range( 14, 20 ) },
 53+ { 'input': null, 'output': null }
 54+ ];
 55+ for ( var i = 0; i < getRangeFromNodeTests.length; i++ ) {
 56+ deepEqual(
 57+ root1.getRangeFromNode( getRangeFromNodeTests[i].input ),
 58+ getRangeFromNodeTests[i].output,
 59+ 'getRangeFromNode returns the correct range or null if item is not found'
 60+ );
 61+ }
 62+} );
 63+
 64+test( 've.BranchNode.getNodeFromOffset', 23, function() {
 65+ // Tests 1 .. 22
 66+ var getNodeFromOffsetTests = [
 67+ // Test 1 - |[<a></a><b> </b><c> </c><d> </d><e> </e>]
 68+ { 'input': -1, 'output': null },
 69+ // Test 2 - [|<a></a><b> </b><c> </c><d> </d><e> </e>]
 70+ { 'input': 0, 'output': root1 },
 71+ // Test 3 - [<a>|</a><b> </b><c> </c><d> </d><e> </e>]
 72+ { 'input': 1, 'output': a },
 73+ // Test 4 - [<a></a>|<b> </b><c> </c><d> </d><e> </e>]
 74+ { 'input': 2, 'output': root1 },
 75+ // Test 5 - [<a></a><b>| </b><c> </c><d> </d><e> </e>]
 76+ { 'input': 3, 'output': b },
 77+ // Test 6 - [<a></a><b> |</b><c> </c><d> </d><e> </e>]
 78+ { 'input': 4, 'output': b },
 79+ // Test 7 - [<a></a><b> </b>|<c> </c><d> </d><e> </e>]
 80+ { 'input': 5, 'output': root1 },
 81+ // Test 8 - [<a></a><b> </b><c>| </c><d> </d><e> </e>]
 82+ { 'input': 6, 'output': c },
 83+ // Test 9 - [<a></a><b> </b><c> | </c><d> </d><e> </e>]
 84+ { 'input': 7, 'output': c },
 85+ // Test 10 - [<a></a><b> </b><c> |</c><d> </d><e> </e>]
 86+ { 'input': 8, 'output': c },
 87+ // Test 11 - [<a></a><b> </b><c> </c>|<d> </d><e> </e>]
 88+ { 'input': 9, 'output': root1 },
 89+ // Test 12 - [<a></a><b> </b><c> </c><d>| </d><e> </e>]
 90+ { 'input': 10, 'output': d },
 91+ // Test 13 - [<a></a><b> </b><c> </c><d> | </d><e> </e>]
 92+ { 'input': 11, 'output': d },
 93+ // Test 14 - [<a></a><b> </b><c> </c><d> | </d><e> </e>]
 94+ { 'input': 12, 'output': d },
 95+ // Test 15 - [<a></a><b> </b><c> </c><d> |</d><e> </e>]
 96+ { 'input': 13, 'output': d },
 97+ // Test 16 - [<a></a><b> </b><c> </c><d> </d>|<e> </e>]
 98+ { 'input': 14, 'output': root1 },
 99+ // Test 17 - [<a></a><b> </b><c> </c><d> </d><e>| </e>]
 100+ { 'input': 15, 'output': e },
 101+ // Test 18 - [<a></a><b> </b><c> </c><d> </d><e> | </e>]
 102+ { 'input': 16, 'output': e },
 103+ // Test 19 - [<a></a><b> </b><c> </c><d> </d><e> | </e>]
 104+ { 'input': 17, 'output': e },
 105+ // Test 20 - [<a></a><b> </b><c> </c><d> </d><e> | </e>]
 106+ { 'input': 18, 'output': e },
 107+ // Test 21 - [<a></a><b> </b><c> </c><d> </d><e> |</e>]
 108+ { 'input': 19, 'output': e },
 109+ // Test 22 - [<a></a><b> </b><c> </c><d> </d><e> </e>|]
 110+ { 'input': 20, 'output': root1 },
 111+ // Test 22 - [<a></a><b> </b><c> </c><d> </d><e> </e>]|
 112+ { 'input': 21, 'output': null }
 113+ ];
 114+ for ( var i = 0; i < getNodeFromOffsetTests.length; i++ ) {
 115+ ok(
 116+ root1.getNodeFromOffset( getNodeFromOffsetTests[i].input ) ===
 117+ getNodeFromOffsetTests[i].output,
 118+ 'getNodeFromOffset finds the right item or returns null when out of range ' +
 119+ '(' + getNodeFromOffsetTests[i].input + ')'
 120+ );
 121+ }
 122+} );
 123+
 124+test( 've.BranchNode.getOffsetFromNode', 6, function() {
 125+ // Tests 1 .. 6
 126+ var getOffsetFromNodeTests = [
 127+ { 'input': a, 'output': 0 },
 128+ { 'input': b, 'output': 2 },
 129+ { 'input': c, 'output': 5 },
 130+ { 'input': d, 'output': 9 },
 131+ { 'input': e, 'output': 14 },
 132+ { 'input': null, 'output': -1 }
 133+ ];
 134+ for ( var i = 0; i < getOffsetFromNodeTests.length; i++ ) {
 135+ strictEqual(
 136+ root1.getOffsetFromNode( getOffsetFromNodeTests[i].input ),
 137+ getOffsetFromNodeTests[i].output,
 138+ 'getOffsetFromNode finds the right offset or returns -1 when node is not found'
 139+ );
 140+ }
 141+} );
 142+
 143+test( 've.BranchNode.selectNodes', 77, function() {
 144+
 145+ // selectNodes tests
 146+
 147+ // <f> a b c d e f g h </f> <g> a b c d e f g h </g> <h> a b c d e f g h </h>
 148+ //^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
 149+ //0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
 150+ // 0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
 151+ var f = new BranchNodeStub( [], 'f', 8 ),
 152+ g = new BranchNodeStub( [], 'g', 8 ),
 153+ h = new BranchNodeStub( [], 'h', 8 ),
 154+ root2 = new BranchNodeStub( [f, g, h], 'root2', 30 ),
 155+ big = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 156+
 157+ // Tests 1 ... 22
 158+ // Possible positions are:
 159+ // * before beginning
 160+ // * at beginning
 161+ // * middle
 162+ // * at end
 163+ // * past end
 164+ var selectNodesTests = [
 165+ // Complete set of combinations within the same node:
 166+
 167+ // Test 1
 168+ {
 169+ 'node': root2,
 170+ 'input': new ve.Range( 0, 0 ),
 171+ 'output': [{ 'node': root2, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 0, 0 ) }],
 172+ 'desc': 'Zero-length range before the beginning of a node'
 173+ },
 174+ // Test 2
 175+ {
 176+ 'node': root2,
 177+ 'input': new ve.Range( 0, 1 ),
 178+ 'output': [{ 'node': f, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 1, 1 ) }],
 179+ 'desc': 'Range starting before the beginning of a node and ending at the beginning'
 180+ },
 181+ // Test 3
 182+ {
 183+ 'node': root2,
 184+ 'input': new ve.Range( 10, 15 ),
 185+ 'output': [{ 'node': g, 'range': new ve.Range( 0, 4 ), 'globalRange': new ve.Range( 11, 15 ) }],
 186+ 'desc': 'Range starting before the beginning of a node and ending in the middle'
 187+ },
 188+ // Test 4
 189+ {
 190+ 'node': root2,
 191+ 'input': new ve.Range( 20, 29 ),
 192+ 'output': [{ 'node': h, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 21, 29 ) }],
 193+ 'desc': 'Range starting before the beginning of a node and ending at the end'
 194+ },
 195+ // Test 5
 196+ {
 197+ 'node': root2,
 198+ 'input': new ve.Range( 0, 10 ),
 199+ 'output': [{ 'node': f, 'globalRange': new ve.Range( 0, 10 ) } ],
 200+ 'desc': 'Range starting before the beginning of a node and ending past the end'
 201+ },
 202+ // Test 6
 203+ {
 204+ 'node': root2,
 205+ 'input': new ve.Range( 11, 11 ),
 206+ 'output': [{ 'node': g, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 11, 11 ) }],
 207+ 'desc': 'Zero-length range at the beginning of a node'
 208+ },
 209+ // Test 7
 210+ {
 211+ 'node': root2,
 212+ 'input': new ve.Range( 21, 26 ),
 213+ 'output': [{ 'node': h, 'range': new ve.Range( 0, 5 ), 'globalRange': new ve.Range( 21, 26 ) }],
 214+ 'desc': 'Range starting at the beginning of a node and ending in the middle'
 215+ },
 216+ // Test 8
 217+ {
 218+ 'node': root2,
 219+ 'input': new ve.Range( 1, 9 ),
 220+ 'output': [{ 'node': f, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 1, 9 ) }],
 221+ 'desc': 'Range starting at the beginning of a node and ending at the end'
 222+ },
 223+ // Test 9
 224+ {
 225+ 'node': root2,
 226+ 'input': new ve.Range( 11, 20 ),
 227+ 'output': [{ 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 11, 19 ) }],
 228+ 'desc': 'Range starting at the beginning of a node and ending past the end'
 229+ },
 230+ // Test 10
 231+ {
 232+ 'node': root2,
 233+ 'input': new ve.Range( 22, 22 ),
 234+ 'output': [{ 'node': h, 'range': new ve.Range( 1, 1 ), 'globalRange': new ve.Range( 22, 22 ) }],
 235+ 'desc': 'Zero-length range in the middle of a node'
 236+ },
 237+ // Test 11
 238+ {
 239+ 'node': root2,
 240+ 'input': new ve.Range( 2, 7 ),
 241+ 'output': [{ 'node': f, 'range': new ve.Range( 1, 6 ), 'globalRange': new ve.Range( 2, 7 ) }],
 242+ 'desc': 'Range starting and ending in the middle of the same node'
 243+ },
 244+ // Test 12
 245+ {
 246+ 'node': root2,
 247+ 'input': new ve.Range( 13, 19 ),
 248+ 'output': [{ 'node': g, 'range': new ve.Range( 2, 8 ), 'globalRange': new ve.Range( 13, 19 ) }],
 249+ 'desc': 'Range starting in the middle of a node and ending at the end'
 250+ },
 251+ // Test 13
 252+ {
 253+ 'node': root2,
 254+ 'input': new ve.Range( 24, 30 ),
 255+ 'output': [{ 'node': h, 'range': new ve.Range( 3, 8 ), 'globalRange': new ve.Range( 24, 29 ) }],
 256+ 'desc': 'Range starting in the middle of a node and ending past the end'
 257+ },
 258+ // Test 14
 259+ {
 260+ 'node': root2,
 261+ 'input': new ve.Range( 9, 9 ),
 262+ 'output': [{ 'node': f, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 9, 9 ) }],
 263+ 'desc': 'Zero-length range at the end of a node'
 264+ },
 265+ // Test 15
 266+ {
 267+ 'node': root2,
 268+ 'input': new ve.Range( 19, 20 ),
 269+ 'output': [{ 'node': g, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 19, 19 ) }],
 270+ 'desc': 'Range starting at the end of a node and ending past the end'
 271+ },
 272+ // Test 16
 273+ {
 274+ 'node': root2,
 275+ 'input': new ve.Range( 30, 30 ),
 276+ 'output': [{ 'node': root2, 'range': new ve.Range( 30, 30 ), 'globalRange': new ve.Range( 30, 30 ) }],
 277+ 'desc': 'Zero-length range past the end of a node'
 278+ },
 279+ // Test 17
 280+ {
 281+ 'node': root2,
 282+ 'input': new ve.Range( 20, 20 ),
 283+ 'output': [{ 'node': root2, 'range': new ve.Range( 20, 20 ), 'globalRange': new ve.Range( 20, 20 ) }],
 284+ 'desc': 'Zero-length range between two nodes'
 285+ },
 286+
 287+ // Complete set of combinations for cross-node selections. Generated with help of a script
 288+
 289+ // Test 18
 290+ {
 291+ 'node': root2,
 292+ 'input': new ve.Range( 0, 11 ),
 293+ 'output': [
 294+ { 'node': f, 'globalRange': new ve.Range( 0, 10 ) },
 295+ { 'node': g, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 11, 11 ) }
 296+ ],
 297+ 'desc': 'Range starting before the beginning of the first node and ending at the beginning of the second node'
 298+ },
 299+ // Test 19
 300+ {
 301+ 'node': root2,
 302+ 'input': new ve.Range( 0, 14 ),
 303+ 'output': [
 304+ { 'node': f, 'globalRange': new ve.Range( 0, 10 ) },
 305+ { 'node': g, 'range': new ve.Range( 0, 3 ), 'globalRange': new ve.Range( 11, 14 ) }
 306+ ],
 307+ 'desc': 'Range starting before the beginning of the first node and ending in the middle of the second node'
 308+ },
 309+ // Test 20
 310+ {
 311+ 'node': root2,
 312+ 'input': new ve.Range( 0, 19 ),
 313+ 'output': [
 314+ { 'node': f, 'globalRange': new ve.Range( 0, 10 ) },
 315+ { 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 11, 19 ) }
 316+ ],
 317+ 'desc': 'Range starting before the beginning of the first node and ending at the end of the second node'
 318+ },
 319+ // Test 21
 320+ {
 321+ 'node': root2,
 322+ 'input': new ve.Range( 0, 20 ),
 323+ 'output': [
 324+ { 'node': f, 'globalRange': new ve.Range( 0, 10 ) },
 325+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) }
 326+ ],
 327+ 'desc': 'Range starting before the beginning of the first node and ending between the second and the third node'
 328+ },
 329+ // Test 22
 330+ {
 331+ 'node': root2,
 332+ 'input': new ve.Range( 0, 21 ),
 333+ 'output': [
 334+ { 'node': f, 'globalRange': new ve.Range( 0, 10 ) },
 335+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 336+ { 'node': h, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 21, 21 ) }
 337+ ],
 338+ 'desc': 'Range starting before the beginning of the first node and ending at the beginning of the third node'
 339+ },
 340+ // Test 23
 341+ {
 342+ 'node': root2,
 343+ 'input': new ve.Range( 0, 27 ),
 344+ 'output': [
 345+ { 'node': f, 'globalRange': new ve.Range( 0, 10 ) },
 346+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 347+ { 'node': h, 'range': new ve.Range( 0, 6 ), 'globalRange': new ve.Range( 21, 27 ) }
 348+ ],
 349+ 'desc': 'Range starting before the beginning of the first node and ending in the middle of the third node'
 350+ },
 351+ // Test 24
 352+ {
 353+ 'node': root2,
 354+ 'input': new ve.Range( 0, 29 ),
 355+ 'output': [
 356+ { 'node': f, 'globalRange': new ve.Range( 0, 10 ) },
 357+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 358+ { 'node': h, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 21, 29 ) }
 359+ ],
 360+ 'desc': 'Range starting before the beginning of the first node and ending at the end of the third node'
 361+ },
 362+ // Test 25
 363+ {
 364+ 'node': root2,
 365+ 'input': new ve.Range( 0, 30 ),
 366+ 'output': [
 367+ { 'node': f, 'globalRange': new ve.Range( 0, 10 ) },
 368+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 369+ { 'node': h, 'globalRange': new ve.Range( 20, 30 ) }
 370+ ],
 371+ 'desc': 'Range starting before the beginning of the first node and ending past the end of the third node'
 372+ },
 373+ // Test 26
 374+ {
 375+ 'node': root2,
 376+ 'input': new ve.Range( 1, 11 ),
 377+ 'output': [
 378+ { 'node': f, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 1, 9 ) },
 379+ { 'node': g, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 11, 11 ) }
 380+ ],
 381+ 'desc': 'Range starting at the beginning of the first node and ending at the beginning of the second node'
 382+ },
 383+ // Test 27
 384+ {
 385+ 'node': root2,
 386+ 'input': new ve.Range( 1, 14 ),
 387+ 'output': [
 388+ { 'node': f, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 1, 9 ) },
 389+ { 'node': g, 'range': new ve.Range( 0, 3 ), 'globalRange': new ve.Range( 11, 14 ) }
 390+ ],
 391+ 'desc': 'Range starting at the beginning of the first node and ending in the middle of the second node'
 392+ },
 393+ // Test 28
 394+ {
 395+ 'node': root2,
 396+ 'input': new ve.Range( 1, 19 ),
 397+ 'output': [
 398+ { 'node': f, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 1, 9 ) },
 399+ { 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 11, 19 ) }
 400+ ],
 401+ 'desc': 'Range starting at the beginning of the first node and ending at the end of the second node'
 402+ },
 403+ // Test 29
 404+ {
 405+ 'node': root2,
 406+ 'input': new ve.Range( 1, 20 ),
 407+ 'output': [
 408+ { 'node': f, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 1, 9 ) },
 409+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) }
 410+ ],
 411+ 'desc': 'Range starting at the beginning of the first node and ending between the second and the third node'
 412+ },
 413+ // Test 30
 414+ {
 415+ 'node': root2,
 416+ 'input': new ve.Range( 1, 21 ),
 417+ 'output': [
 418+ { 'node': f, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 1, 9 ) },
 419+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 420+ { 'node': h, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 21, 21 ) }
 421+ ],
 422+ 'desc': 'Range starting at the beginning of the first node and ending at the beginning of the third node'
 423+ },
 424+ // Test 31
 425+ {
 426+ 'node': root2,
 427+ 'input': new ve.Range( 1, 27 ),
 428+ 'output': [
 429+ { 'node': f, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 1, 9 ) },
 430+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 431+ { 'node': h, 'range': new ve.Range( 0, 6 ), 'globalRange': new ve.Range( 21, 27 ) }
 432+ ],
 433+ 'desc': 'Range starting at the beginning of the first node and ending in the middle of the third node'
 434+ },
 435+ // Test 32
 436+ {
 437+ 'node': root2,
 438+ 'input': new ve.Range( 1, 29 ),
 439+ 'output': [
 440+ { 'node': f, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 1, 9 ) },
 441+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 442+ { 'node': h, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 21, 29 ) }
 443+ ],
 444+ 'desc': 'Range starting at the beginning of the first node and ending at the end of the third node'
 445+ },
 446+ // Test 33
 447+ {
 448+ 'node': root2,
 449+ 'input': new ve.Range( 1, 30 ),
 450+ 'output': [
 451+ { 'node': f, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 1, 9 ) },
 452+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 453+ { 'node': h, 'globalRange': new ve.Range( 20, 30 ) }
 454+ ],
 455+ 'desc': 'Range starting at the beginning of the first node and ending past the end of the third node'
 456+ },
 457+ // Test 34
 458+ {
 459+ 'node': root2,
 460+ 'input': new ve.Range( 5, 11 ),
 461+ 'output': [
 462+ { 'node': f, 'range': new ve.Range( 4, 8 ), 'globalRange': new ve.Range( 5, 9 ) },
 463+ { 'node': g, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 11, 11 ) }
 464+ ],
 465+ 'desc': 'Range starting in the middle of the first node and ending at the beginning of the second node'
 466+ },
 467+ // Test 35
 468+ {
 469+ 'node': root2,
 470+ 'input': new ve.Range( 5, 14 ),
 471+ 'output': [
 472+ { 'node': f, 'range': new ve.Range( 4, 8 ), 'globalRange': new ve.Range( 5, 9 ) },
 473+ { 'node': g, 'range': new ve.Range( 0, 3 ), 'globalRange': new ve.Range( 11, 14 ) }
 474+ ],
 475+ 'desc': 'Range starting in the middle of the first node and ending in the middle of the second node'
 476+ },
 477+ // Test 36
 478+ {
 479+ 'node': root2,
 480+ 'input': new ve.Range( 5, 19 ),
 481+ 'output': [
 482+ { 'node': f, 'range': new ve.Range( 4, 8 ), 'globalRange': new ve.Range( 5, 9 ) },
 483+ { 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 11, 19 ) }
 484+ ],
 485+ 'desc': 'Range starting in the middle of the first node and ending at the end of the second node'
 486+ },
 487+ // Test 37
 488+ {
 489+ 'node': root2,
 490+ 'input': new ve.Range( 5, 20 ),
 491+ 'output': [
 492+ { 'node': f, 'range': new ve.Range( 4, 8 ), 'globalRange': new ve.Range( 5, 9 ) },
 493+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) }
 494+ ],
 495+ 'desc': 'Range starting in the middle of the first node and ending between the second and the third node'
 496+ },
 497+ // Test 38
 498+ {
 499+ 'node': root2,
 500+ 'input': new ve.Range( 5, 21 ),
 501+ 'output': [
 502+ { 'node': f, 'range': new ve.Range( 4, 8 ), 'globalRange': new ve.Range( 5, 9 ) },
 503+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 504+ { 'node': h, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 21, 21 ) }
 505+ ],
 506+ 'desc': 'Range starting in the middle of the first node and ending at the beginning of the third node'
 507+ },
 508+ // Test 39
 509+ {
 510+ 'node': root2,
 511+ 'input': new ve.Range( 5, 27 ),
 512+ 'output': [
 513+ { 'node': f, 'range': new ve.Range( 4, 8 ), 'globalRange': new ve.Range( 5, 9 ) },
 514+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 515+ { 'node': h, 'range': new ve.Range( 0, 6 ), 'globalRange': new ve.Range( 21, 27 ) }
 516+ ],
 517+ 'desc': 'Range starting in the middle of the first node and ending in the middle of the third node'
 518+ },
 519+ // Test 40
 520+ {
 521+ 'node': root2,
 522+ 'input': new ve.Range( 5, 29 ),
 523+ 'output': [
 524+ { 'node': f, 'range': new ve.Range( 4, 8 ), 'globalRange': new ve.Range( 5, 9 ) },
 525+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 526+ { 'node': h, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 21, 29 ) }
 527+ ],
 528+ 'desc': 'Range starting in the middle of the first node and ending at the end of the third node'
 529+ },
 530+ // Test 41
 531+ {
 532+ 'node': root2,
 533+ 'input': new ve.Range( 5, 30 ),
 534+ 'output': [
 535+ { 'node': f, 'range': new ve.Range( 4, 8 ), 'globalRange': new ve.Range( 5, 9 ) },
 536+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 537+ { 'node': h, 'globalRange': new ve.Range( 20, 30 ) }
 538+ ],
 539+ 'desc': 'Range starting in the middle of the first node and ending past the end of the third node'
 540+ },
 541+ // Test 42
 542+ {
 543+ 'node': root2,
 544+ 'input': new ve.Range( 9, 11 ),
 545+ 'output': [
 546+ { 'node': f, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 9, 9 ) },
 547+ { 'node': g, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 11, 11 ) }
 548+ ],
 549+ 'desc': 'Range starting at the end of the first node and ending at the beginning of the second node'
 550+ },
 551+ // Test 43
 552+ {
 553+ 'node': root2,
 554+ 'input': new ve.Range( 9, 14 ),
 555+ 'output': [
 556+ { 'node': f, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 9, 9 ) },
 557+ { 'node': g, 'range': new ve.Range( 0, 3 ), 'globalRange': new ve.Range( 11, 14 ) }
 558+ ],
 559+ 'desc': 'Range starting at the end of the first node and ending in the middle of the second node'
 560+ },
 561+ // Test 44
 562+ {
 563+ 'node': root2,
 564+ 'input': new ve.Range( 9, 19 ),
 565+ 'output': [
 566+ { 'node': f, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 9, 9 ) },
 567+ { 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 11, 19 ) }
 568+ ],
 569+ 'desc': 'Range starting at the end of the first node and ending at the end of the second node'
 570+ },
 571+ // Test 45
 572+ {
 573+ 'node': root2,
 574+ 'input': new ve.Range( 9, 20 ),
 575+ 'output': [
 576+ { 'node': f, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 9, 9 ) },
 577+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) }
 578+ ],
 579+ 'desc': 'Range starting at the end of the first node and ending between the second and the third node'
 580+ },
 581+ // Test 46
 582+ {
 583+ 'node': root2,
 584+ 'input': new ve.Range( 9, 21 ),
 585+ 'output': [
 586+ { 'node': f, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 9, 9 ) },
 587+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 588+ { 'node': h, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 21, 21 ) }
 589+ ],
 590+ 'desc': 'Range starting at the end of the first node and ending at the beginning of the third node'
 591+ },
 592+ // Test 47
 593+ {
 594+ 'node': root2,
 595+ 'input': new ve.Range( 9, 27 ),
 596+ 'output': [
 597+ { 'node': f, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 9, 9 ) },
 598+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 599+ { 'node': h, 'range': new ve.Range( 0, 6 ), 'globalRange': new ve.Range( 21, 27 ) }
 600+ ],
 601+ 'desc': 'Range starting at the end of the first node and ending in the middle of the third node'
 602+ },
 603+ // Test 48
 604+ {
 605+ 'node': root2,
 606+ 'input': new ve.Range( 9, 29 ),
 607+ 'output': [
 608+ { 'node': f, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 9, 9 ) },
 609+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 610+ { 'node': h, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 21, 29 ) }
 611+ ],
 612+ 'desc': 'Range starting at the end of the first node and ending at the end of the third node'
 613+ },
 614+ // Test 49
 615+ {
 616+ 'node': root2,
 617+ 'input': new ve.Range( 9, 30 ),
 618+ 'output': [
 619+ { 'node': f, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 9, 9 ) },
 620+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 621+ { 'node': h, 'globalRange': new ve.Range( 20, 30 ) }
 622+ ],
 623+ 'desc': 'Range starting at the end of the first node and ending past the end of the third node'
 624+ },
 625+ // Test 50
 626+ {
 627+ 'node': root2,
 628+ 'input': new ve.Range( 10, 21 ),
 629+ 'output': [
 630+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 631+ { 'node': h, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 21, 21 ) }
 632+ ],
 633+ 'desc': 'Range starting between the first and the second node and ending at the beginning of the third node'
 634+ },
 635+ // Test 51
 636+ {
 637+ 'node': root2,
 638+ 'input': new ve.Range( 10, 27 ),
 639+ 'output': [
 640+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 641+ { 'node': h, 'range': new ve.Range( 0, 6 ), 'globalRange': new ve.Range( 21, 27 ) }
 642+ ],
 643+ 'desc': 'Range starting between the first and the second node and ending in the middle of the third node'
 644+ },
 645+ // Test 52
 646+ {
 647+ 'node': root2,
 648+ 'input': new ve.Range( 10, 29 ),
 649+ 'output': [
 650+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 651+ { 'node': h, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 21, 29 ) }
 652+ ],
 653+ 'desc': 'Range starting between the first and the second node and ending at the end of the third node'
 654+ },
 655+ // Test 53
 656+ {
 657+ 'node': root2,
 658+ 'input': new ve.Range( 10, 30 ),
 659+ 'output': [
 660+ { 'node': g, 'globalRange': new ve.Range( 10, 20 ) },
 661+ { 'node': h, 'globalRange': new ve.Range( 20, 30 ) }
 662+ ],
 663+ 'desc': 'Range starting between the first and the second node and ending past the end of the third node'
 664+ },
 665+ // Test 54
 666+ {
 667+ 'node': root2,
 668+ 'input': new ve.Range( 11, 21 ),
 669+ 'output': [
 670+ { 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 11, 19 ) },
 671+ { 'node': h, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 21, 21 ) }
 672+ ],
 673+ 'desc': 'Range starting at the beginning of the second node and ending at the beginning of the third node'
 674+ },
 675+ // Test 55
 676+ {
 677+ 'node': root2,
 678+ 'input': new ve.Range( 11, 27 ),
 679+ 'output': [
 680+ { 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 11, 19 ) },
 681+ { 'node': h, 'range': new ve.Range( 0, 6 ), 'globalRange': new ve.Range( 21, 27 ) }
 682+ ],
 683+ 'desc': 'Range starting at the beginning of the second node and ending in the middle of the third node'
 684+ },
 685+ // Test 56
 686+ {
 687+ 'node': root2,
 688+ 'input': new ve.Range( 11, 29 ),
 689+ 'output': [
 690+ { 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 11, 19 ) },
 691+ { 'node': h, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 21, 29 ) }
 692+ ],
 693+ 'desc': 'Range starting at the beginning of the second node and ending at the end of the third node'
 694+ },
 695+ // Test 57
 696+ {
 697+ 'node': root2,
 698+ 'input': new ve.Range( 11, 30 ),
 699+ 'output': [
 700+ { 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 11, 19 ) },
 701+ { 'node': h, 'globalRange': new ve.Range( 20, 30 ) }
 702+ ],
 703+ 'desc': 'Range starting at the beginning of the second node and ending past the end of the third node'
 704+ },
 705+ // Test 58
 706+ {
 707+ 'node': root2,
 708+ 'input': new ve.Range( 14, 21 ),
 709+ 'output': [
 710+ { 'node': g, 'range': new ve.Range( 3, 8 ), 'globalRange': new ve.Range( 14, 19 ) },
 711+ { 'node': h, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 21, 21 ) }
 712+ ],
 713+ 'desc': 'Range starting in the middle of the second node and ending at the beginning of the third node'
 714+ },
 715+ // Test 59
 716+ {
 717+ 'node': root2,
 718+ 'input': new ve.Range( 14, 27 ),
 719+ 'output': [
 720+ { 'node': g, 'range': new ve.Range( 3, 8 ), 'globalRange': new ve.Range( 14, 19 ) },
 721+ { 'node': h, 'range': new ve.Range( 0, 6 ), 'globalRange': new ve.Range( 21, 27 ) }
 722+ ],
 723+ 'desc': 'Range starting in the middle of the second node and ending in the middle of the third node'
 724+ },
 725+ // Test 60
 726+ {
 727+ 'node': root2,
 728+ 'input': new ve.Range( 14, 29 ),
 729+ 'output': [
 730+ { 'node': g, 'range': new ve.Range( 3, 8 ), 'globalRange': new ve.Range( 14, 19 ) },
 731+ { 'node': h, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 21, 29 ) }
 732+ ],
 733+ 'desc': 'Range starting in the middle of the second node and ending at the end of the third node'
 734+ },
 735+ // Test 61
 736+ {
 737+ 'node': root2,
 738+ 'input': new ve.Range( 14, 30 ),
 739+ 'output': [
 740+ { 'node': g, 'range': new ve.Range( 3, 8 ), 'globalRange': new ve.Range( 14, 19 ) },
 741+ { 'node': h, 'globalRange': new ve.Range( 20, 30 ) }
 742+ ],
 743+ 'desc': 'Range starting in the middle of the second node and ending past the end of the third node'
 744+ },
 745+ // Test 62
 746+ {
 747+ 'node': root2,
 748+ 'input': new ve.Range( 19, 21 ),
 749+ 'output': [
 750+ { 'node': g, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 19, 19 ) },
 751+ { 'node': h, 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 21, 21 ) }
 752+ ],
 753+ 'desc': 'Range starting at the end of the second node and ending at the beginning of the third node'
 754+ },
 755+ // Test 63
 756+ {
 757+ 'node': root2,
 758+ 'input': new ve.Range( 19, 27 ),
 759+ 'output': [
 760+ { 'node': g, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 19, 19 ) },
 761+ { 'node': h, 'range': new ve.Range( 0, 6 ), 'globalRange': new ve.Range( 21, 27 ) }
 762+ ],
 763+ 'desc': 'Range starting at the end of the second node and ending in the middle of the third node'
 764+ },
 765+ // Test 64
 766+ {
 767+ 'node': root2,
 768+ 'input': new ve.Range( 19, 29 ),
 769+ 'output': [
 770+ { 'node': g, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 19, 19 ) },
 771+ { 'node': h, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 21, 29 ) }
 772+ ],
 773+ 'desc': 'Range starting at the end of the second node and ending at the end of the third node'
 774+ },
 775+ // Test 65
 776+ {
 777+ 'node': root2,
 778+ 'input': new ve.Range( 19, 30 ),
 779+ 'output': [
 780+ { 'node': g, 'range': new ve.Range( 8, 8 ), 'globalRange': new ve.Range( 19, 19 ) },
 781+ { 'node': h, 'globalRange': new ve.Range( 20, 30 ) }
 782+ ],
 783+ 'desc': 'Range starting at the end of the second node and ending past the end of the third node'
 784+ },
 785+ // Tests for childless nodes
 786+
 787+ // Test 66
 788+ {
 789+ 'node': g,
 790+ 'input': new ve.Range( 1, 3 ),
 791+ 'output': [
 792+ { 'node': g, 'range': new ve.Range( 1, 3 ), 'globalRange': new ve.Range( 1, 3 ) }
 793+ ],
 794+ 'desc': 'Childless node given, range not out of bounds'
 795+ },
 796+ // Test 67
 797+ {
 798+ 'node': g,
 799+ 'input': new ve.Range( 0, 8 ),
 800+ 'output': [
 801+ { 'node': g, 'range': new ve.Range( 0, 8 ), 'globalRange': new ve.Range( 0, 8 ) }
 802+ ],
 803+ 'desc': 'Childless node given, range covers entire node'
 804+ },
 805+ // Tests for out-of-bounds cases
 806+
 807+ // Test 68
 808+ {
 809+ 'node': g,
 810+ 'input': new ve.Range( -1, 3 ),
 811+ 'exception': /^The start offset of the range is negative$/,
 812+ 'desc': 'Childless node given, range start out of bounds'
 813+ },
 814+ // Test 69
 815+ {
 816+ 'node': g,
 817+ 'input': new ve.Range( 1, 9 ),
 818+ 'exception': /^The end offset of the range is past the end of the node$/,
 819+ 'desc': 'Childless node given, range end out of bounds'
 820+ },
 821+ // Test 70
 822+ {
 823+ 'node': root2,
 824+ 'input': new ve.Range( 31, 35 ),
 825+ 'exception': /^The start offset of the range is past the end of the node$/,
 826+ 'desc': 'Node with children given, range start out of bounds'
 827+ },
 828+ // Test 71
 829+ {
 830+ 'node': root2,
 831+ 'input': new ve.Range( 30, 35 ),
 832+ 'exception': /^The end offset of the range is past the end of the node$/,
 833+ 'desc': 'Node with children given, range end out of bounds'
 834+ },
 835+ // Tests for recursion cases
 836+
 837+ // Test 72
 838+ {
 839+ 'node': big,
 840+ 'input': new ve.Range( 2, 10 ),
 841+ 'output': [
 842+ { 'node': big.children[0], 'range': new ve.Range( 1, 3 ), 'globalRange': new ve.Range( 2, 4 ) },
 843+ { 'node': big.children[1].children[0].children[0].children[0], 'range': new ve.Range( 0, 1 ), 'globalRange': new ve.Range( 9, 10 ) }
 844+ ],
 845+ 'desc': 'Select from before the b to after the d'
 846+ },
 847+ // Test 73
 848+ {
 849+ 'node': big,
 850+ 'input': new ve.Range( 3, 33 ),
 851+ 'output': [
 852+ { 'node': big.children[0], 'range': new ve.Range( 2, 3 ), 'globalRange': new ve.Range( 3, 4 ) },
 853+ { 'node': big.children[1], 'globalRange': new ve.Range( 5, 31 ) },
 854+ { 'node': big.children[2], 'range': new ve.Range( 0, 1 ), 'globalRange': new ve.Range( 32, 33 ) }
 855+ ],
 856+ 'desc': 'Select from before the c to after the h'
 857+ },
 858+ // Test 74
 859+ {
 860+ 'node': big,
 861+ 'input': new ve.Range( 9, 20 ),
 862+ 'output': [
 863+ { 'node': big.children[1].children[0].children[0].children[0], 'range': new ve.Range( 0, 1 ), 'globalRange': new ve.Range( 9, 10 ) },
 864+ { 'node': big.children[1].children[0].children[0].children[1].children[0], 'globalRange': new ve.Range( 12, 17 ) },
 865+ { 'node': big.children[1].children[0].children[0].children[1].children[1].children[0], 'range': new ve.Range( 0, 1 ), 'globalRange': new ve.Range( 19, 20 ) }
 866+ ],
 867+ 'desc': 'Select from before the d to after the f, with recursion'
 868+ },
 869+ // Test 75
 870+ {
 871+ 'node': big,
 872+ 'input': new ve.Range( 9, 20 ),
 873+ 'shallow': true,
 874+ 'output': [
 875+ { 'node': big.children[1], 'range': new ve.Range( 3, 14 ), 'globalRange': new ve.Range( 9, 20 ) }
 876+ ],
 877+ 'desc': 'Select from before the d to after the f, without recursion'
 878+ },
 879+ // Test 76
 880+ {
 881+ 'node': big,
 882+ 'input': new ve.Range( 3, 9 ),
 883+ 'output': [
 884+ { 'node': big.children[0], 'range': new ve.Range( 2, 3 ), 'globalRange': new ve.Range( 3, 4 ) },
 885+ { 'node': big.children[1].children[0].children[0].children[0], 'range': new ve.Range( 0, 0 ), 'globalRange': new ve.Range( 9, 9 ) }
 886+ ],
 887+ 'desc': 'Select from before the c to before the d'
 888+ },
 889+ // Test 77
 890+ {
 891+ 'node': big,
 892+ 'input': new ve.Range( 3, 9 ),
 893+ 'shallow': true,
 894+ 'output': [
 895+ { 'node': big.children[0], 'range': new ve.Range( 2, 3 ), 'globalRange': new ve.Range( 3, 4 ) },
 896+ { 'node': big.children[1], 'range': new ve.Range( 0, 3 ), 'globalRange': new ve.Range( 6, 9 ) }
 897+ ],
 898+ 'desc': 'Select from before the c to before the d, without recursion'
 899+ }
 900+ ];
 901+
 902+ function compare( a, b ) {
 903+ if ( $.isArray( a ) && $.isArray( b ) && a.length === b.length ) {
 904+ for ( var i = 0; i < a.length; i++ ) {
 905+ if (
 906+ a[i].node !== b[i].node ||
 907+ (
 908+ ( typeof a[i].range !== typeof b[i].range ) ||
 909+ (
 910+ a[i].range !== undefined &&
 911+ (
 912+ a[i].range.start !== b[i].range.start ||
 913+ a[i].range.end !== b[i].range.end
 914+ )
 915+ )
 916+ ) || (
 917+ ( typeof a[i].globalRange !== typeof b[i].globalRange ) ||
 918+ (
 919+ a[i].globalRange !== undefined &&
 920+ (
 921+ a[i].globalRange.start !== b[i].globalRange.start ||
 922+ a[i].globalRange.end !== b[i].globalRange.end
 923+ )
 924+ )
 925+ )
 926+ ) {
 927+ return false;
 928+ }
 929+ }
 930+ return true;
 931+ }
 932+ return false;
 933+ }
 934+ function select( input, shallow ) {
 935+ return function() {
 936+ selectNodesTests[i].node.selectNodes( input, shallow );
 937+ };
 938+ }
 939+
 940+ for ( var i = 0; i < selectNodesTests.length; i++ ) {
 941+ if ( 'output' in selectNodesTests[i] ) {
 942+ var result = selectNodesTests[i].node.selectNodes(
 943+ selectNodesTests[i].input, selectNodesTests[i].shallow
 944+ );
 945+ ok(
 946+ compare( result, selectNodesTests[i].output ),
 947+ selectNodesTests[i].desc +
 948+ ' (from ' + selectNodesTests[i].input.start +
 949+ ' to ' + selectNodesTests[i].input.end + ')'
 950+ );
 951+ if ( console && console.log && !compare( result, selectNodesTests[i].output ) ) {
 952+ console.log( "Test " + (i+1) + " FAILED" );
 953+ console.log( result );
 954+ console.log( selectNodesTests[i].output );
 955+ }
 956+ } else if ( 'exception' in selectNodesTests[i] ) {
 957+ raises(
 958+ function() {
 959+ selectNodesTests[i].node.selectNodes(
 960+ selectNodesTests[i].input,
 961+ selectNodesTests[i].shallow
 962+ );
 963+ },
 964+ selectNodesTests[i].exception,
 965+ selectNodesTests[i].desc
 966+ );
 967+ }
 968+ }
 969+} );
 970+
 971+test( 've.BranchNode.traverseLeafNodes', 11, function() {
 972+ var root3 = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 973+
 974+ var tests = [
 975+ // Test 1 & 2
 976+ {
 977+ 'node': root3,
 978+ 'output': [
 979+ root3.children[0],
 980+ root3.children[1].children[0].children[0].children[0],
 981+ root3.children[1].children[0].children[0].children[1].children[0].children[0],
 982+ root3.children[1].children[0].children[0].children[1].children[1].children[0],
 983+ root3.children[1].children[0].children[0].children[1].children[2].children[0],
 984+ root3.children[2]
 985+ ],
 986+ 'reverse': true,
 987+ 'desc': 'Traversing the entire document returns all leaf nodes'
 988+ },
 989+ // Test 3 & 4
 990+ {
 991+ 'node': root3,
 992+ 'output': [
 993+ root3.children[0],
 994+ root3.children[1].children[0].children[0].children[0],
 995+ root3.children[1].children[0].children[0].children[1].children[0].children[0]
 996+ ],
 997+ 'reverse': [
 998+ root3.children[2],
 999+ root3.children[1].children[0].children[0].children[1].children[2].children[0],
 1000+ root3.children[1].children[0].children[0].children[1].children[1].children[0],
 1001+ root3.children[1].children[0].children[0].children[1].children[0].children[0]
 1002+ ],
 1003+ 'callback': function( node ) {
 1004+ if ( node === root3.children[1].children[0].children[0].children[1].children[0].children[0] ) {
 1005+ return false;
 1006+ }
 1007+ },
 1008+ 'desc': 'Returning false from the callback stops the traversal'
 1009+ },
 1010+ // Test 5 & 6
 1011+ {
 1012+ 'node': root3,
 1013+ 'output': [
 1014+ root3.children[1].children[0].children[0].children[1].children[1].children[0],
 1015+ root3.children[1].children[0].children[0].children[1].children[2].children[0],
 1016+ root3.children[2]
 1017+ ],
 1018+ 'reverse': [
 1019+ root3.children[1].children[0].children[0].children[1].children[1].children[0],
 1020+ root3.children[1].children[0].children[0].children[1].children[0].children[0],
 1021+ root3.children[1].children[0].children[0].children[0],
 1022+ root3.children[0]
 1023+ ],
 1024+ 'from': root3.children[1].children[0].children[0].children[1].children[1].children[0],
 1025+ 'desc': 'Starting at a leaf node returns that leaf node and everything after it',
 1026+ 'reverseDesc': 'Starting at a leaf node returns that leaf node and everything before it (in reverse)'
 1027+ },
 1028+ // Test 7 & 8
 1029+ {
 1030+ 'node': root3,
 1031+ 'output': [
 1032+ root3.children[1].children[0].children[0].children[0],
 1033+ root3.children[1].children[0].children[0].children[1].children[0].children[0],
 1034+ root3.children[1].children[0].children[0].children[1].children[1].children[0],
 1035+ root3.children[1].children[0].children[0].children[1].children[2].children[0],
 1036+ root3.children[2]
 1037+ ],
 1038+ 'reverse': [
 1039+ root3.children[0]
 1040+ ],
 1041+ 'from': root3.children[1],
 1042+ 'desc': 'Starting at a non-leaf node returns all leaf nodes inside and after it',
 1043+ 'reverseDesc': 'Starting at a non-leaf node returns all leaf nodes before it and none inside (in reverse)'
 1044+ },
 1045+ // Test 9 & 10
 1046+ {
 1047+ 'node': root3.children[1],
 1048+ 'output': [
 1049+ root3.children[1].children[0].children[0].children[0],
 1050+ root3.children[1].children[0].children[0].children[1].children[0].children[0],
 1051+ root3.children[1].children[0].children[0].children[1].children[1].children[0],
 1052+ root3.children[1].children[0].children[0].children[1].children[2].children[0]
 1053+ ],
 1054+ 'reverse': true,
 1055+ 'desc': 'Calling traverseLeafNodes() on a non-root node only returns leaf nodes inside that node'
 1056+ },
 1057+ // Test 11
 1058+ {
 1059+ 'node': root3.children[1],
 1060+ 'from': root3.children[2],
 1061+ 'exception': /^from parameter passed to traverseLeafNodes\(\) must be a descendant$/,
 1062+ 'desc': 'Passing a sibling for from results in an exception'
 1063+ }
 1064+ ];
 1065+
 1066+ for ( var i = 0; i < tests.length; i++ ) {
 1067+ executeTest( tests[i] );
 1068+ if ( tests[i].reverse !== undefined ) {
 1069+ var reversed = {
 1070+ 'node': tests[i].node,
 1071+ 'from': tests[i].from,
 1072+ 'callback': tests[i].callback,
 1073+ 'exception': tests[i].exception,
 1074+ 'isReversed': true,
 1075+ 'desc': tests[i].reverseDesc || tests[i].desc + ' (in reverse)'
 1076+ };
 1077+ if ( tests[i].output !== undefined && tests[i].reverse === true ) {
 1078+ reversed.output = tests[i].output.reverse();
 1079+ } else {
 1080+ reversed.output = tests[i].reverse;
 1081+ }
 1082+ executeTest( reversed );
 1083+ }
 1084+ }
 1085+
 1086+ function executeTest( test ) {
 1087+ var realLeaves = [],
 1088+ callback = function( node ) {
 1089+ var retval;
 1090+ realLeaves.push( node );
 1091+ if ( test.callback ) {
 1092+ retval = test.callback( node );
 1093+ if ( retval !== undefined ) {
 1094+ return retval;
 1095+ }
 1096+ }
 1097+ },
 1098+ f = function() {
 1099+ test.node.traverseLeafNodes( callback, test.from, test.isReversed );
 1100+ };
 1101+ if ( test.exception ) {
 1102+ raises( f, test.exception, test.desc );
 1103+ } else {
 1104+ f();
 1105+ ok( ve.compareArrays( realLeaves, test.output ), test.desc );
 1106+ }
 1107+ }
 1108+} );
\ No newline at end of file
Property changes on: trunk/extensions/VisualEditor/tests/ve/ve.BranchNode.test.js
___________________________________________________________________
Added: svn:eol-style
11109 + native
Index: trunk/extensions/VisualEditor/tests/ve/ve.dm.DocumentNode.test.js
@@ -0,0 +1,802 @@
 2+module( 've/dm' );
 3+
 4+test( 've.dm.DocumentNode.getData', 1, function() {
 5+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 6+
 7+ // Test 1
 8+ deepEqual( documentModel.getData(), veTest.data, 'Flattening plain objects results in correct data' );
 9+} );
 10+
 11+test( 've.dm.DocumentNode.getChildren', 1, function() {
 12+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 13+
 14+ function equalLengths( a, b ) {
 15+ if ( a.length !== b.length ) {
 16+ return false;
 17+ }
 18+ for ( var i = 0; i < a.length; i++ ) {
 19+ if ( a[i].getContentLength() !== b[i].getContentLength() ) {
 20+ console.log( 'mismatched content lengths', a[i], b[i] );
 21+ return false;
 22+ }
 23+ var aIsBranch = typeof a[i].getChildren === 'function';
 24+ var bIsBranch = typeof b[i].getChildren === 'function';
 25+ if ( aIsBranch !== bIsBranch ) {
 26+ return false;
 27+ }
 28+ if ( aIsBranch && !equalLengths( a[i].getChildren(), b[i].getChildren() ) ) {
 29+ return false;
 30+ }
 31+ }
 32+ return true;
 33+ }
 34+
 35+ // Test 1
 36+ ok(
 37+ equalLengths( documentModel.getChildren(), veTest.tree ),
 38+ 'Nodes in the model tree contain correct lengths'
 39+ );
 40+} );
 41+
 42+test( 've.dm.DocumentNode.getRelativeContentOffset', 7, function() {
 43+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 44+
 45+ // Test 1
 46+ equal(
 47+ documentModel.getRelativeContentOffset( 1, 1 ),
 48+ 2,
 49+ 'getRelativeContentOffset advances forwards through the inside of elements'
 50+ );
 51+ // Test 2
 52+ equal(
 53+ documentModel.getRelativeContentOffset( 2, -1 ),
 54+ 1,
 55+ 'getRelativeContentOffset advances backwards through the inside of elements'
 56+ );
 57+ // Test 3
 58+ equal(
 59+ documentModel.getRelativeContentOffset( 3, 1 ),
 60+ 4,
 61+ 'getRelativeContentOffset uses the offset after the last character in an element'
 62+ );
 63+ // Test 4
 64+ equal(
 65+ documentModel.getRelativeContentOffset( 1, -1 ),
 66+ 1,
 67+ 'getRelativeContentOffset does not allow moving before the content of the first node'
 68+ );
 69+ // Test 5
 70+ equal(
 71+ documentModel.getRelativeContentOffset( 33, 1 ),
 72+ 33,
 73+ 'getRelativeContentOffset does not allow moving after the content of the last node'
 74+ );
 75+ // Test 6
 76+ equal(
 77+ documentModel.getRelativeContentOffset( 4, 1 ),
 78+ 9,
 79+ 'getRelativeContentOffset advances forwards between elements'
 80+ );
 81+ // Test 7
 82+ equal(
 83+ documentModel.getRelativeContentOffset( 32, -1 ),
 84+ 25,
 85+ 'getRelativeContentOffset advances backwards between elements'
 86+ );
 87+} );
 88+
 89+test( 've.dm.DocumentNode.getContentData', 6, function() {
 90+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj ),
 91+ childNodes = documentModel.getChildren();
 92+
 93+ // Test 1
 94+ deepEqual(
 95+ childNodes[0].getContentData( new ve.Range( 1, 3 ) ),
 96+ [
 97+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 98+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]
 99+ ],
 100+ 'getContentData can return an ending portion of the content'
 101+ );
 102+
 103+ // Test 2
 104+ deepEqual(
 105+ childNodes[0].getContentData( new ve.Range( 0, 2 ) ),
 106+ ['a', ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }]],
 107+ 'getContentData can return a beginning portion of the content'
 108+ );
 109+
 110+ // Test 3
 111+ deepEqual(
 112+ childNodes[0].getContentData( new ve.Range( 1, 2 ) ),
 113+ [['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }]],
 114+ 'getContentData can return a middle portion of the content'
 115+ );
 116+
 117+ // Test 4
 118+ try {
 119+ childNodes[0].getContentData( new ve.Range( -1, 3 ) );
 120+ } catch ( negativeIndexError ) {
 121+ ok( true, 'getContentData throws exceptions when given a range with start < 0' );
 122+ }
 123+
 124+ // Test 5
 125+ try {
 126+ childNodes[0].getContentData( new ve.Range( 0, 4 ) );
 127+ } catch ( outOfRangeError ) {
 128+ ok( true, 'getContentData throws exceptions when given a range with end > length' );
 129+ }
 130+
 131+ // Test 6
 132+ deepEqual( childNodes[2].getContentData(), ['h'], 'Content can be extracted from nodes' );
 133+} );
 134+
 135+test( 've.dm.DocumentNode.getIndexOfAnnotation', 3, function() {
 136+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 137+
 138+ var bold = { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' },
 139+ italic = { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' },
 140+ nothing = { 'type': 'nothing', 'hash': '{"type":"nothing"}' },
 141+ character = ['a', bold, italic];
 142+
 143+ // Test 1
 144+ equal(
 145+ ve.dm.DocumentNode.getIndexOfAnnotation( character, bold ),
 146+ 1,
 147+ 'getIndexOfAnnotation get the correct index'
 148+ );
 149+
 150+ // Test 2
 151+ equal(
 152+ ve.dm.DocumentNode.getIndexOfAnnotation( character, italic ),
 153+ 2,
 154+ 'getIndexOfAnnotation get the correct index'
 155+ );
 156+
 157+ // Test 3
 158+ equal(
 159+ ve.dm.DocumentNode.getIndexOfAnnotation( character, nothing ),
 160+ -1,
 161+ 'getIndexOfAnnotation returns -1 if the annotation was not found'
 162+ );
 163+} );
 164+
 165+test( 've.dm.DocumentNode.getWordBoundaries', 2, function() {
 166+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 167+ deepEqual(
 168+ documentModel.getWordBoundaries( 2 ),
 169+ new ve.Range( 1, 4 ),
 170+ 'getWordBoundaries returns range around nearest whole word'
 171+ );
 172+ strictEqual(
 173+ documentModel.getWordBoundaries( 5 ),
 174+ null,
 175+ 'getWordBoundaries returns null when given non-content offset'
 176+ );
 177+} );
 178+
 179+test( 've.dm.DocumentNode.getAnnotationBoundaries', 2, function() {
 180+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 181+ deepEqual(
 182+ documentModel.getAnnotationBoundaries( 2, { 'type': 'textStyle/bold' } ),
 183+ new ve.Range( 2, 3 ),
 184+ 'getWordBoundaries returns range around content covered by annotation'
 185+ );
 186+ strictEqual(
 187+ documentModel.getAnnotationBoundaries( 1, { 'type': 'textStyle/bold' } ),
 188+ null,
 189+ 'getWordBoundaries returns null if offset is not covered by annotation'
 190+ );
 191+} );
 192+
 193+test( 've.dm.DocumentNode.getAnnotationsFromOffset', 4, function() {
 194+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 195+ deepEqual(
 196+ documentModel.getAnnotationsFromOffset( 1 ),
 197+ [],
 198+ 'getAnnotationsFromOffset returns empty array for non-annotated content'
 199+ );
 200+ deepEqual(
 201+ documentModel.getAnnotationsFromOffset( 2 ),
 202+ [{ 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 203+ 'getAnnotationsFromOffset returns annotations of annotated content correctly'
 204+ );
 205+ deepEqual(
 206+ documentModel.getAnnotationsFromOffset( 3 ),
 207+ [{ 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 208+ 'getAnnotationsFromOffset returns annotations of annotated content correctly'
 209+ );
 210+ deepEqual(
 211+ documentModel.getAnnotationsFromOffset( 0 ),
 212+ [],
 213+ 'getAnnotationsFromOffset returns empty array when given a non-content offset'
 214+ );
 215+} );
 216+
 217+test( 've.dm.DocumentNode.prepareElementAttributeChange', 4, function() {
 218+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 219+
 220+ // Test 1
 221+ deepEqual(
 222+ documentModel.prepareElementAttributeChange( 0, 'set', 'test', 1234 ).getOperations(),
 223+ [
 224+ { 'type': 'attribute', 'method': 'set', 'key': 'test', 'value': 1234 },
 225+ { 'type': 'retain', 'length': 34 }
 226+ ],
 227+ 'prepareElementAttributeChange retains data after attribute change for first element'
 228+ );
 229+
 230+ // Test 2
 231+ deepEqual(
 232+ documentModel.prepareElementAttributeChange( 5, 'set', 'test', 1234 ).getOperations(),
 233+ [
 234+ { 'type': 'retain', 'length': 5 },
 235+ { 'type': 'attribute', 'method': 'set', 'key': 'test', 'value': 1234 },
 236+ { 'type': 'retain', 'length': 29 }
 237+ ],
 238+ 'prepareElementAttributeChange retains data before and after attribute change'
 239+ );
 240+
 241+ // Test 3
 242+ try {
 243+ documentModel.prepareElementAttributeChange( 1, 'set', 'test', 1234 );
 244+ } catch ( invalidOffsetError ) {
 245+ ok(
 246+ true,
 247+ 'prepareElementAttributeChange throws an exception when offset is not an element'
 248+ );
 249+ }
 250+
 251+ // Test 4
 252+ try {
 253+ documentModel.prepareElementAttributeChange( 4, 'set', 'test', 1234 );
 254+ } catch ( closingElementError ) {
 255+ ok(
 256+ true,
 257+ 'prepareElementAttributeChange throws an exception when offset is a closing element'
 258+ );
 259+ }
 260+} );
 261+
 262+test( 've.dm.DocumentNode.prepareContentAnnotation', 3, function() {
 263+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 264+
 265+ // Test 1
 266+ deepEqual(
 267+ documentModel.prepareContentAnnotation(
 268+ new ve.Range( 1, 4 ), 'set', { 'type': 'textStyle/bold' }
 269+ ).getOperations(),
 270+ [
 271+ { 'type': 'retain', 'length': 1 },
 272+ {
 273+ 'type': 'annotate',
 274+ 'method': 'set',
 275+ 'bias': 'start',
 276+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 277+ },
 278+ { 'type': 'retain', 'length': 1 },
 279+ {
 280+ 'type': 'annotate',
 281+ 'method': 'set',
 282+ 'bias': 'stop',
 283+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 284+ },
 285+ { 'type': 'retain', 'length': 1 },
 286+ {
 287+ 'type': 'annotate',
 288+ 'method': 'set',
 289+ 'bias': 'start',
 290+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 291+ },
 292+ { 'type': 'retain', 'length': 1 },
 293+ {
 294+ 'type': 'annotate',
 295+ 'method': 'set',
 296+ 'bias': 'stop',
 297+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 298+ },
 299+ { 'type': 'retain', 'length': 30 }
 300+ ],
 301+ 'prepareContentAnnotation skips over content that is already set or cleared'
 302+ );
 303+
 304+ // Test 2
 305+ deepEqual(
 306+ documentModel.prepareContentAnnotation(
 307+ new ve.Range( 3, 10 ), 'set', { 'type': 'textStyle/bold' }
 308+ ).getOperations(),
 309+ [
 310+ { 'type': 'retain', 'length': 3 },
 311+ {
 312+ 'type': 'annotate',
 313+ 'method': 'set',
 314+ 'bias': 'start',
 315+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 316+ },
 317+ { 'type': 'retain', 'length': 1 },
 318+ {
 319+ 'type': 'annotate',
 320+ 'method': 'set',
 321+ 'bias': 'stop',
 322+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 323+ },
 324+ { 'type': 'retain', 'length': 5 },
 325+ {
 326+ 'type': 'annotate',
 327+ 'method': 'set',
 328+ 'bias': 'start',
 329+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 330+ },
 331+ { 'type': 'retain', 'length': 1 },
 332+ {
 333+ 'type': 'annotate',
 334+ 'method': 'set',
 335+ 'bias': 'stop',
 336+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 337+ },
 338+ { 'type': 'retain', 'length': 24 }
 339+ ],
 340+ 'prepareContentAnnotation works across element boundaries'
 341+ );
 342+
 343+ // Test 3
 344+ deepEqual(
 345+ documentModel.prepareContentAnnotation(
 346+ new ve.Range( 4, 11 ), 'set', { 'type': 'textStyle/bold' }
 347+ ).getOperations(),
 348+ [
 349+ { 'type': 'retain', 'length': 9 },
 350+ {
 351+ 'type': 'annotate',
 352+ 'method': 'set',
 353+ 'bias': 'start',
 354+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 355+ },
 356+ { 'type': 'retain', 'length': 1 },
 357+ {
 358+ 'type': 'annotate',
 359+ 'method': 'set',
 360+ 'bias': 'stop',
 361+ 'annotation': { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 362+ },
 363+ { 'type': 'retain', 'length': 24 }
 364+ ],
 365+ 'prepareContentAnnotation works when given structural offsets'
 366+ );
 367+} );
 368+
 369+test( 've.dm.DocumentNode.prepareRemoval', 11, function() {
 370+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 371+
 372+ // Test 1
 373+ deepEqual(
 374+ documentModel.prepareRemoval( new ve.Range( 1, 4 ) ).getOperations(),
 375+ [
 376+ { 'type': 'retain', 'length': 1 },
 377+ {
 378+ 'type': 'remove',
 379+ 'data': [
 380+ 'a',
 381+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 382+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]
 383+ ]
 384+ },
 385+ { 'type': 'retain', 'length': 30 }
 386+ ],
 387+ 'prepareRemoval includes the content being removed'
 388+ );
 389+
 390+ // Test 2
 391+ deepEqual(
 392+ documentModel.prepareRemoval( new ve.Range( 17, 22 ) ).getOperations(),
 393+ [
 394+ { 'type': 'retain', 'length': 17 },
 395+ {
 396+ 'type': 'remove',
 397+ 'data': [
 398+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 399+ { 'type': 'paragraph' },
 400+ 'f',
 401+ { 'type': '/paragraph' },
 402+ { 'type': '/listItem' }
 403+ ]
 404+ },
 405+ { 'type': 'retain', 'length': 12 }
 406+ ],
 407+ 'prepareRemoval removes entire elements'
 408+ );
 409+
 410+ // Test 3
 411+ deepEqual(
 412+ documentModel.prepareRemoval( new ve.Range( 3, 9 ) ).getOperations(),
 413+ [
 414+ { 'type': 'retain', 'length': 3 },
 415+ {
 416+ 'type': 'remove',
 417+ 'data': [
 418+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]
 419+ ]
 420+ },
 421+ { 'type': 'retain', 'length': 30 }
 422+ ],
 423+ 'prepareRemoval works across structural nodes'
 424+ );
 425+
 426+ // Test 4
 427+ deepEqual(
 428+ documentModel.prepareRemoval( new ve.Range( 3, 24 ) ).getOperations(),
 429+ [
 430+ { 'type': 'retain', 'length': 3 },
 431+ {
 432+ 'type': 'remove',
 433+ 'data': [['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]]
 434+ },
 435+ { 'type': 'retain', 'length': 4 },
 436+ {
 437+ 'type': 'remove',
 438+ 'data': [{ 'type': 'paragraph' }, 'd', { 'type': '/paragraph' }]
 439+ },
 440+ { 'type': 'retain', 'length': 1 },
 441+ {
 442+ 'type': 'remove',
 443+ 'data': [
 444+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } },
 445+ { 'type': 'paragraph' },
 446+ 'e',
 447+ { 'type': '/paragraph' },
 448+ { 'type': '/listItem' },
 449+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 450+ { 'type': 'paragraph' },
 451+ 'f',
 452+ { 'type': '/paragraph' },
 453+ { 'type': '/listItem' }
 454+ ]
 455+ },
 456+ { 'type': 'retain', 'length': 12 }
 457+ ],
 458+ 'prepareRemoval strips and drops correctly when working across structural nodes'
 459+ );
 460+
 461+ // Test 5
 462+ deepEqual(
 463+ documentModel.prepareRemoval( new ve.Range( 3, 25 ) ).getOperations(),
 464+ [
 465+ { 'type': 'retain', 'length': 3 },
 466+ {
 467+ 'type': 'remove',
 468+ 'data': [['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]]
 469+ },
 470+ { 'type': 'retain', 'length': 4 },
 471+ {
 472+ 'type': 'remove',
 473+ 'data': [{ 'type': 'paragraph' }, 'd', { 'type': '/paragraph' }]
 474+ },
 475+ { 'type': 'retain', 'length': 1 },
 476+ {
 477+ 'type': 'remove',
 478+ 'data': [
 479+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } },
 480+ { 'type': 'paragraph' },
 481+ 'e',
 482+ { 'type': '/paragraph' },
 483+ { 'type': '/listItem' },
 484+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 485+ { 'type': 'paragraph' },
 486+ 'f',
 487+ { 'type': '/paragraph' },
 488+ { 'type': '/listItem' }
 489+ ]
 490+ },
 491+ { 'type': 'retain', 'length': 2 },
 492+ {
 493+ 'type': 'remove',
 494+ 'data': [ 'g' ]
 495+ },
 496+ { 'type': 'retain', 'length': 9 }
 497+ ],
 498+ 'prepareRemoval strips and drops correctly when working across structural nodes (2)'
 499+ );
 500+
 501+ // Test 6
 502+ deepEqual(
 503+ documentModel.prepareRemoval( new ve.Range( 9, 17 ) ).getOperations(),
 504+ [
 505+ { 'type': 'retain', 'length': 9 },
 506+ {
 507+ 'type': 'remove',
 508+ 'data': [ 'd' ]
 509+ },
 510+ { 'type': 'retain', 'length': 2 },
 511+ {
 512+ 'type': 'remove',
 513+ 'data': [
 514+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } },
 515+ { 'type': 'paragraph' },
 516+ 'e',
 517+ { 'type': '/paragraph' },
 518+ { 'type': '/listItem' }
 519+ ]
 520+ },
 521+ { 'type': 'retain', 'length': 17 }
 522+ ],
 523+ 'prepareRemoval will not merge items of unequal types'
 524+ );
 525+
 526+ // Test 7
 527+ deepEqual(
 528+ documentModel.prepareRemoval( new ve.Range( 9, 27 ) ).getOperations(),
 529+ [
 530+ { 'type': 'retain', 'length': 9 },
 531+ {
 532+ 'type': 'remove',
 533+ 'data': [ 'd' ]
 534+ },
 535+ { 'type': 'retain', 'length': 2 },
 536+ {
 537+ 'type': 'remove',
 538+ 'data': [
 539+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } },
 540+ { 'type': 'paragraph' },
 541+ 'e',
 542+ { 'type': '/paragraph' },
 543+ { 'type': '/listItem' },
 544+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 545+ { 'type': 'paragraph' },
 546+ 'f',
 547+ { 'type': '/paragraph' },
 548+ { 'type': '/listItem' },
 549+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } },
 550+ { 'type': 'paragraph' },
 551+ 'g',
 552+ { 'type': '/paragraph' },
 553+ { 'type': '/listItem' }
 554+ ]
 555+ },
 556+ { 'type': 'retain', 'length': 7 }
 557+ ],
 558+ 'prepareRemoval blanks a paragraph and a list'
 559+ );
 560+
 561+ // Test 8
 562+ deepEqual(
 563+ documentModel.prepareRemoval( new ve.Range( 21, 23 ) ).getOperations(),
 564+ [
 565+ { 'type': 'retain', 'length': 21 },
 566+ {
 567+ 'type': 'remove',
 568+ 'data': [
 569+ { 'type': '/listItem' },
 570+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } }
 571+ ]
 572+ },
 573+ { 'type': 'retain', 'length': 11 }
 574+ ],
 575+ 'prepareRemoval merges two list items'
 576+ );
 577+
 578+ // Test 9
 579+ deepEqual(
 580+ documentModel.prepareRemoval( new ve.Range( 20, 24 ) ).getOperations(),
 581+ [
 582+ { 'type': 'retain', 'length': 20 },
 583+ {
 584+ 'type': 'remove',
 585+ 'data': [
 586+ { 'type': '/paragraph' },
 587+ { 'type': '/listItem' },
 588+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } },
 589+ { 'type': 'paragraph' }
 590+ ]
 591+ },
 592+ { 'type': 'retain', 'length': 10 }
 593+ ],
 594+ 'prepareRemoval merges two list items and the paragraphs inside them'
 595+ );
 596+
 597+ // Test 10
 598+ deepEqual(
 599+ documentModel.prepareRemoval( new ve.Range( 20, 23 ) ).getOperations(),
 600+ [
 601+ { 'type': 'retain', 'length': 34 }
 602+ ],
 603+ 'prepareRemoval returns a null transaction when attempting an unbalanced merge'
 604+ );
 605+
 606+ // Test 11
 607+ deepEqual(
 608+ documentModel.prepareRemoval( new ve.Range( 15, 24 ) ).getOperations(),
 609+ [
 610+ { 'type': 'retain', 'length': 15 },
 611+ {
 612+ 'type': 'remove',
 613+ 'data': [
 614+ { 'type': '/paragraph' },
 615+ { 'type': '/listItem' },
 616+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 617+ { 'type': 'paragraph' },
 618+ 'f',
 619+ { 'type': '/paragraph' },
 620+ { 'type': '/listItem' },
 621+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } },
 622+ { 'type': 'paragraph' }
 623+ ]
 624+ },
 625+ { 'type': 'retain', 'length': 10 }
 626+ ],
 627+ 'prepareRemoval merges two list items and the paragraphs inside them'
 628+ );
 629+
 630+} );
 631+
 632+test( 've.dm.DocumentNode.prepareInsertion', 11, function() {
 633+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 634+
 635+ // Test 1
 636+ deepEqual(
 637+ documentModel.prepareInsertion( 1, ['d', 'e', 'f'] ).getOperations(),
 638+ [
 639+ { 'type': 'retain', 'length': 1 },
 640+ { 'type': 'insert', 'data': ['d', 'e', 'f'] },
 641+ { 'type': 'retain', 'length': 33 }
 642+ ],
 643+ 'prepareInsertion retains data up to the offset and includes the content being inserted'
 644+ );
 645+
 646+ // Test 2
 647+ deepEqual(
 648+ documentModel.prepareInsertion(
 649+ 5, [{ 'type': 'paragraph' }, 'd', 'e', 'f', { 'type': '/paragraph' }]
 650+ ).getOperations(),
 651+ [
 652+ { 'type': 'retain', 'length': 5 },
 653+ {
 654+ 'type': 'insert',
 655+ 'data': [{ 'type': 'paragraph' }, 'd', 'e', 'f', { 'type': '/paragraph' }]
 656+ },
 657+ { 'type': 'retain', 'length': 29 }
 658+ ],
 659+ 'prepareInsertion inserts a paragraph between two structural elements'
 660+ );
 661+
 662+ // Test 3
 663+ deepEqual(
 664+ documentModel.prepareInsertion( 5, ['d', 'e', 'f'] ).getOperations(),
 665+ [
 666+ { 'type': 'retain', 'length': 5 },
 667+ {
 668+ 'type': 'insert',
 669+ 'data': [{ 'type': 'paragraph' }, 'd', 'e', 'f', { 'type': '/paragraph' }]
 670+ },
 671+ { 'type': 'retain', 'length': 29 }
 672+ ],
 673+ 'prepareInsertion wraps unstructured content inserted between elements in a paragraph'
 674+ );
 675+
 676+ // Test 4
 677+ deepEqual(
 678+ documentModel.prepareInsertion(
 679+ 5, [{ 'type': 'paragraph' }, 'd', 'e', 'f']
 680+ ).getOperations(),
 681+ [
 682+ { 'type': 'retain', 'length': 5 },
 683+ {
 684+ 'type': 'insert',
 685+ 'data': [{ 'type': 'paragraph' }, 'd', 'e', 'f', { 'type': '/paragraph' }]
 686+ },
 687+ { 'type': 'retain', 'length': 29 }
 688+ ],
 689+ 'prepareInsertion completes opening elements in inserted content'
 690+ );
 691+
 692+ // Test 5
 693+ deepEqual(
 694+ documentModel.prepareInsertion(
 695+ 2, [ { 'type': 'table' }, { 'type': '/table' } ]
 696+ ).getOperations(),
 697+ [
 698+ { 'type': 'retain', 'length': 2 },
 699+ {
 700+ 'type': 'insert',
 701+ 'data': [
 702+ { 'type': '/paragraph' },
 703+ { 'type': 'table' },
 704+ { 'type': '/table' },
 705+ { 'type': 'paragraph' }
 706+ ]
 707+ },
 708+ { 'type': 'retain', 'length': 32 }
 709+ ],
 710+ 'prepareInsertion splits up paragraph when inserting a table in the middle'
 711+ );
 712+
 713+ // Test 6
 714+ deepEqual(
 715+ documentModel.prepareInsertion(
 716+ 2, [ 'f', 'o', 'o', { 'type': '/paragraph' }, { 'type': 'paragraph' }, 'b', 'a', 'r' ]
 717+ ).getOperations(),
 718+ [
 719+ { 'type': 'retain', 'length': 2 },
 720+ {
 721+ 'type': 'insert',
 722+ 'data': [
 723+ 'f',
 724+ 'o',
 725+ 'o',
 726+ { 'type': '/paragraph' },
 727+ { 'type': 'paragraph' },
 728+ 'b',
 729+ 'a',
 730+ 'r'
 731+ ]
 732+ },
 733+ { 'type': 'retain', 'length': 32 }
 734+ ],
 735+ 'prepareInsertion splits paragraph when inserting a paragraph closing and opening inside it'
 736+ );
 737+
 738+ // Test 7
 739+ deepEqual(
 740+ documentModel.prepareInsertion(
 741+ 0, [ { 'type': 'paragraph' }, 'f', 'o', 'o', { 'type': '/paragraph' } ]
 742+ ).getOperations(),
 743+ [
 744+ {
 745+ 'type': 'insert',
 746+ 'data': [ { 'type': 'paragraph' }, 'f', 'o', 'o', { 'type': '/paragraph' } ]
 747+ },
 748+ { 'type': 'retain', 'length': 34 }
 749+ ],
 750+ 'prepareInsertion inserts at the beginning, then retains up to the end'
 751+ );
 752+
 753+ // Test 8
 754+ deepEqual(
 755+ documentModel.prepareInsertion(
 756+ 34, [ { 'type': 'paragraph' }, 'f', 'o', 'o', { 'type': '/paragraph' } ]
 757+ ).getOperations(),
 758+ [
 759+ { 'type': 'retain', 'length': 34 },
 760+ {
 761+ 'type': 'insert',
 762+ 'data': [ { 'type': 'paragraph' }, 'f', 'o', 'o', { 'type': '/paragraph' } ]
 763+ }
 764+ ],
 765+ 'prepareInsertion inserts at the end'
 766+ );
 767+
 768+ // Test 9
 769+ raises(
 770+ function() {
 771+ documentModel.prepareInsertion(
 772+ -1,
 773+ [ { 'type': 'paragraph' }, 'f', 'o', 'o', { 'type': '/paragraph' } ]
 774+ );
 775+ },
 776+ /^Offset -1 out of bounds/,
 777+ 'prepareInsertion throws exception for negative offset'
 778+ );
 779+
 780+ // Test 10
 781+ raises(
 782+ function() {
 783+ documentModel.prepareInsertion(
 784+ 35,
 785+ [ { 'type': 'paragraph' }, 'f', 'o', 'o', { 'type': '/paragraph' } ]
 786+ );
 787+ },
 788+ /^Offset 35 out of bounds/,
 789+ 'prepareInsertion throws exception for offset past the end'
 790+ );
 791+
 792+ // Test 11
 793+ raises(
 794+ function() {
 795+ documentModel.prepareInsertion(
 796+ 5,
 797+ [{ 'type': 'paragraph' }, 'a', { 'type': 'listItem' }, { 'type': '/paragraph' }]
 798+ );
 799+ },
 800+ /^Input is malformed: expected \/listItem but got \/paragraph at index 3$/,
 801+ 'prepareInsertion throws exception for malformed input'
 802+ );
 803+} );
Property changes on: trunk/extensions/VisualEditor/tests/ve/ve.dm.DocumentNode.test.js
___________________________________________________________________
Added: svn:eol-style
1804 + native
Index: trunk/extensions/VisualEditor/tests/ve/ve.dm.TransactionProcessor.test.js
@@ -0,0 +1,380 @@
 2+module( 've/dm' );
 3+
 4+test( 've.dm.TransactionProcessor', 31, function() {
 5+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
 6+
 7+ // FIXME: These tests shouldn't use prepareFoo() because those functions
 8+ // normalize the transactions they create and are tested separately.
 9+ // We should be creating transactions directly and feeding those into
 10+ // commit()/rollback() --Roan
 11+ var elementAttributeChange = documentModel.prepareElementAttributeChange(
 12+ 0, 'set', 'test', 1
 13+ );
 14+
 15+ // Test 1
 16+ ve.dm.TransactionProcessor.commit( documentModel, elementAttributeChange );
 17+ deepEqual(
 18+ documentModel.getData( new ve.Range( 0, 5 ) ),
 19+ [
 20+ { 'type': 'paragraph', 'attributes': { 'test': 1 } },
 21+ 'a',
 22+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 23+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 24+ { 'type': '/paragraph' }
 25+ ],
 26+ 'commit applies an element attribute change transaction to the content'
 27+ );
 28+
 29+ // Test 2
 30+ ve.dm.TransactionProcessor.rollback( documentModel, elementAttributeChange );
 31+ deepEqual(
 32+ documentModel.getData( new ve.Range( 0, 5 ) ),
 33+ [
 34+ { 'type': 'paragraph' },
 35+ 'a',
 36+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 37+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 38+ { 'type': '/paragraph' }
 39+ ],
 40+ 'rollback reverses the effect of an element attribute change transaction on the content'
 41+ );
 42+
 43+ var contentAnnotation = documentModel.prepareContentAnnotation(
 44+ new ve.Range( 1, 4 ), 'set', { 'type': 'textStyle/bold' }
 45+ );
 46+
 47+ // Test 3
 48+ ve.dm.TransactionProcessor.commit( documentModel, contentAnnotation );
 49+ deepEqual(
 50+ documentModel.getData( new ve.Range( 0, 5 ) ),
 51+ [
 52+ { 'type': 'paragraph' },
 53+ ['a', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 54+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 55+ [
 56+ 'c',
 57+ { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' },
 58+ { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }
 59+ ],
 60+ { 'type': '/paragraph' }
 61+ ],
 62+ 'commit applies a content annotation transaction to the content'
 63+ );
 64+
 65+ // Test 4
 66+ ve.dm.TransactionProcessor.rollback( documentModel, contentAnnotation );
 67+ deepEqual(
 68+ documentModel.getData( new ve.Range( 0, 5 ) ),
 69+ [
 70+ { 'type': 'paragraph' },
 71+ 'a',
 72+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 73+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 74+ { 'type': '/paragraph' }
 75+ ],
 76+ 'rollback reverses the effect of a content annotation transaction on the content'
 77+ );
 78+
 79+ var insertion = documentModel.prepareInsertion( 3, ['d'] );
 80+
 81+ // Test 5
 82+ ve.dm.TransactionProcessor.commit( documentModel, insertion );
 83+ deepEqual(
 84+ documentModel.getData( new ve.Range( 0, 6 ) ),
 85+ [
 86+ { 'type': 'paragraph' },
 87+ 'a',
 88+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 89+ 'd',
 90+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 91+ { 'type': '/paragraph' }
 92+ ],
 93+ 'commit applies an insertion transaction to the content'
 94+ );
 95+
 96+ // Test 6
 97+ deepEqual(
 98+ documentModel.getChildren()[0].getContentData(),
 99+ [
 100+ 'a',
 101+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 102+ 'd',
 103+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]
 104+ ],
 105+ 'commit keeps model tree up to date with insertions'
 106+ );
 107+
 108+ // Test 7
 109+ ve.dm.TransactionProcessor.rollback( documentModel, insertion );
 110+ deepEqual(
 111+ documentModel.getData( new ve.Range( 0, 5 ) ),
 112+ [
 113+ { 'type': 'paragraph' },
 114+ 'a',
 115+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 116+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 117+ { 'type': '/paragraph' }
 118+ ],
 119+ 'rollback reverses the effect of an insertion transaction on the content'
 120+ );
 121+
 122+ // Test 8
 123+ deepEqual(
 124+ documentModel.getChildren()[0].getContentData(),
 125+ [
 126+ 'a',
 127+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 128+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]
 129+ ],
 130+ 'rollback keeps model tree up to date with insertions'
 131+ );
 132+
 133+ var removal = documentModel.prepareRemoval( new ve.Range( 2, 4 ) );
 134+
 135+ // Test 9
 136+ ve.dm.TransactionProcessor.commit( documentModel, removal );
 137+ deepEqual(
 138+ documentModel.getData( new ve.Range( 0, 3 ) ),
 139+ [
 140+ { 'type': 'paragraph' },
 141+ 'a',
 142+ { 'type': '/paragraph' }
 143+ ],
 144+ 'commit applies a removal transaction to the content'
 145+ );
 146+
 147+ // Test 10
 148+ deepEqual(
 149+ documentModel.getChildren()[0].getContentData(),
 150+ ['a'],
 151+ 'commit keeps model tree up to date with removals'
 152+ );
 153+
 154+ // Test 11
 155+ ve.dm.TransactionProcessor.rollback( documentModel, removal );
 156+ deepEqual(
 157+ documentModel.getData( new ve.Range( 0, 5 ) ),
 158+ [
 159+ { 'type': 'paragraph' },
 160+ 'a',
 161+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 162+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 163+ { 'type': '/paragraph' }
 164+ ],
 165+ 'rollback reverses the effect of a removal transaction on the content'
 166+ );
 167+
 168+ // Test 12
 169+ deepEqual(
 170+ documentModel.getChildren()[0].getContentData(),
 171+ [
 172+ 'a',
 173+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 174+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]
 175+ ],
 176+ 'rollback keeps model tree up to date with removals'
 177+ );
 178+
 179+ var paragraphBreak = documentModel.prepareInsertion(
 180+ 2, [{ 'type': '/paragraph' }, { 'type': 'paragraph' }]
 181+ );
 182+
 183+ // Test 13
 184+ ve.dm.TransactionProcessor.commit( documentModel, paragraphBreak );
 185+ deepEqual(
 186+ documentModel.getData( new ve.Range( 0, 7 ) ),
 187+ [
 188+ { 'type': 'paragraph' },
 189+ 'a',
 190+ { 'type': '/paragraph' },
 191+ { 'type': 'paragraph' },
 192+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 193+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 194+ { 'type': '/paragraph' }
 195+ ],
 196+ 'commit applies an insertion transaction that splits the paragraph'
 197+ );
 198+
 199+ // Test 14
 200+ deepEqual(
 201+ documentModel.getChildren()[0].getContentData(),
 202+ ['a'],
 203+ 'commit keeps model tree up to date with paragraph split (paragraph 1)'
 204+ );
 205+
 206+ // Test 15
 207+ deepEqual(
 208+ documentModel.getChildren()[1].getContentData(),
 209+ [
 210+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 211+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]
 212+ ],
 213+ 'commit keeps model tree up to date with paragraph split (paragraph 2)'
 214+ );
 215+
 216+ // Test 16
 217+ ve.dm.TransactionProcessor.rollback( documentModel, paragraphBreak );
 218+ deepEqual(
 219+ documentModel.getData( new ve.Range( 0, 5 ) ),
 220+ [
 221+ { 'type': 'paragraph' },
 222+ 'a',
 223+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 224+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 225+ { 'type': '/paragraph' }
 226+ ],
 227+ 'rollback reverses the effect of a paragraph split on the content'
 228+ );
 229+
 230+ // Test 17
 231+ deepEqual(
 232+ documentModel.getChildren()[0].getContentData(),
 233+ [
 234+ 'a',
 235+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 236+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }]
 237+ ],
 238+ 'rollback keeps model tree up to date with paragraph split (paragraphs are merged back)'
 239+ );
 240+
 241+ // Test 18
 242+ deepEqual(
 243+ documentModel.getChildren()[1].getElementType(),
 244+ 'table',
 245+ 'rollback keeps model tree up to date with paragraph split (table follows the paragraph)'
 246+ );
 247+
 248+ var listItemMerge = documentModel.prepareRemoval( new ve.Range( 14, 19 ) );
 249+
 250+ // Test 19
 251+ ve.dm.TransactionProcessor.commit( documentModel, listItemMerge );
 252+ deepEqual(
 253+ documentModel.getData( new ve.Range( 12, 22 ) ),
 254+ [
 255+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } },
 256+ { 'type': 'paragraph' },
 257+ 'f',
 258+ { 'type': '/paragraph' },
 259+ { 'type': '/listItem' },
 260+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } },
 261+ { 'type': 'paragraph' },
 262+ 'g',
 263+ { 'type': '/paragraph' },
 264+ { 'type': '/listItem' }
 265+ ],
 266+ 'removal merges two list items with paragraphs'
 267+ );
 268+
 269+ // Test 20
 270+ deepEqual( documentModel.children[1].children[0].children[0].children[1].children.length, 2,
 271+ 'removal keeps model tree up to date with list item merge (number of children)'
 272+ );
 273+
 274+ // Test 21
 275+ deepEqual(
 276+ documentModel.children[1].children[0].children[0].children[1].children[0].children[0].getContentData(),
 277+ [ 'f' ],
 278+ 'removal keeps model tree up to date with list item merge (first list item)'
 279+ );
 280+
 281+ // Test 22
 282+ deepEqual(
 283+ documentModel.children[1].children[0].children[0].children[1].children[1].children[0].getContentData(),
 284+ [ 'g' ],
 285+ 'removal keeps model tree up to date with list item merge (second list item)'
 286+ );
 287+
 288+ // Test 23
 289+ deepEqual(
 290+ documentModel.children[2].getContentData(),
 291+ [ 'h' ],
 292+ 'rollback keeps model tree up to date with list item split (final paragraph)'
 293+ );
 294+
 295+ // Test 24
 296+ ve.dm.TransactionProcessor.rollback( documentModel, listItemMerge );
 297+ deepEqual(
 298+ documentModel.getData( new ve.Range( 12, 27 ) ),
 299+ [
 300+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } },
 301+ { 'type': 'paragraph' },
 302+ 'e',
 303+ { 'type': '/paragraph' },
 304+ { 'type': '/listItem' },
 305+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 306+ { 'type': 'paragraph' },
 307+ 'f',
 308+ { 'type': '/paragraph' },
 309+ { 'type': '/listItem' },
 310+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } },
 311+ { 'type': 'paragraph' },
 312+ 'g',
 313+ { 'type': '/paragraph' },
 314+ { 'type': '/listItem' }
 315+ ],
 316+ 'rollback reverses list item merge (splits the list items)'
 317+ );
 318+
 319+ // Test 25
 320+ deepEqual( documentModel.children[1].children[0].children[0].children[1].children.length, 3,
 321+ 'rollback keeps model tree up to date with list item split (number of children)'
 322+ );
 323+
 324+ // Test 26
 325+ deepEqual(
 326+ documentModel.children[1].children[0].children[0].children[1].children[0].children[0].getContentData(),
 327+ [ 'e' ],
 328+ 'rollback keeps model tree up to date with list item split (first list item)'
 329+ );
 330+
 331+ // Test 27
 332+ deepEqual(
 333+ documentModel.children[1].children[0].children[0].children[1].children[1].children[0].getContentData(),
 334+ [ 'f' ],
 335+ 'rollback keeps model tree up to date with list item split (second list item)'
 336+ );
 337+
 338+ // Test 28
 339+ deepEqual(
 340+ documentModel.children[1].children[0].children[0].children[1].children[2].children[0].getContentData(),
 341+ [ 'g' ],
 342+ 'rollback keeps model tree up to date with list item split (third list item)'
 343+ );
 344+
 345+ // Test 29
 346+ deepEqual(
 347+ documentModel.children[2].getContentData(),
 348+ [ 'h' ],
 349+ 'rollback keeps model tree up to date with list item split (final paragraph)'
 350+ );
 351+
 352+ var listSplit = documentModel.prepareInsertion( 17, [{ 'type': '/list' }, { 'type': 'list' }] );
 353+
 354+ // Test 30
 355+ ve.dm.TransactionProcessor.commit( documentModel, listSplit );
 356+ deepEqual(
 357+ documentModel.getData( new ve.Range( 15, 21 ) ),
 358+ [
 359+ { 'type': '/paragraph' },
 360+ { 'type': '/listItem' },
 361+ { 'type': '/list' },
 362+ { 'type': 'list' },
 363+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 364+ { 'type': 'paragraph' }
 365+ ],
 366+ 'commit splits list into two lists'
 367+ );
 368+
 369+ // Test 31
 370+ ve.dm.TransactionProcessor.rollback( documentModel, listSplit );
 371+ deepEqual(
 372+ documentModel.getData( new ve.Range( 15, 19 ) ),
 373+ [
 374+ { 'type': '/paragraph' },
 375+ { 'type': '/listItem' },
 376+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 377+ { 'type': 'paragraph' }
 378+ ],
 379+ 'rollback reverses list split'
 380+ );
 381+} );
Property changes on: trunk/extensions/VisualEditor/tests/ve/ve.dm.TransactionProcessor.test.js
___________________________________________________________________
Added: svn:eol-style
1382 + native
Index: trunk/extensions/VisualEditor/tests/ve/ve.Node.test.js
@@ -0,0 +1,46 @@
 2+module( 've' );
 3+
 4+test( 've.Node.getCommonAncestorPaths', 14, function() {
 5+ var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj ), result;
 6+
 7+ var list = documentModel.children[1].children[0].children[0].children[1];
 8+ result = ve.Node.getCommonAncestorPaths( list, list );
 9+ // Test 1
 10+ ok( result.commonAncestor == list, 'same nodes (commonAncestor)' );
 11+ // Test 2
 12+ ok( ve.compareArrays( result.node1Path, [] ), 'adjacent list items (node1Path)' );
 13+ // Test 3
 14+ ok( ve.compareArrays( result.node2Path, [] ), 'adjacent list items (node2Path)' );
 15+
 16+ result = ve.Node.getCommonAncestorPaths( list.children[0], list.children[1] );
 17+ // Test 4
 18+ ok( result.commonAncestor == list, 'adjacent list items (commonAncestor)' );
 19+ // Test 5
 20+ ok( ve.compareArrays( result.node1Path, [ list.children[0] ] ), 'adjacent list items (node1Path)' );
 21+ // Test 6
 22+ ok( ve.compareArrays( result.node2Path, [ list.children[1] ] ), 'adjacent list items (node2Path)' );
 23+
 24+ result = ve.Node.getCommonAncestorPaths( list.children[0], list.children[2] );
 25+ // Test 7
 26+ ok( result.commonAncestor == list, 'non-adjacent sibling list items (commonAncestor)' );
 27+ // Test 8
 28+ ok( ve.compareArrays( result.node1Path, [ list.children[0] ] ), 'non-adjacent sibling list items (node1Path)' );
 29+ // Test 9
 30+ ok( ve.compareArrays( result.node2Path, [ list.children[2] ] ), 'non-adjacent sibling list items (node2Path)' );
 31+
 32+ result = ve.Node.getCommonAncestorPaths( list.children[0].children[0], list.children[2].children[0] );
 33+ // Test 10
 34+ ok( result.commonAncestor == list, 'paragraphs inside list items (commonAncestor)' );
 35+ // Test 11
 36+ ok( ve.compareArrays( result.node1Path, [ list.children[0].children[0], list.children[0] ] ), 'paragraphs inside list items (node1Path)' );
 37+ // Test 12
 38+ ok( ve.compareArrays( result.node2Path, [ list.children[2].children[0], list.children[2] ] ), 'paragraphs inside list items (node2Path)' );
 39+
 40+ result = ve.Node.getCommonAncestorPaths( list.children[0].children[0], list.children[2] );
 41+ // Test 13
 42+ equal( result, false, 'nodes of unequal depth' );
 43+
 44+ result = ve.Node.getCommonAncestorPaths( list, ve.dm.DocumentNode.newFromPlainObject( veTest.obj ).children[1] );
 45+ // Test 14
 46+ equal( result, false, 'nodes in different trees' );
 47+} );
\ No newline at end of file
Property changes on: trunk/extensions/VisualEditor/tests/ve/ve.Node.test.js
___________________________________________________________________
Added: svn:eol-style
148 + native
Index: trunk/extensions/VisualEditor/tests/ve/index.html
@@ -0,0 +1,61 @@
 2+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 3+ "http://www.w3.org/TR/html4/loose.dtd">
 4+<html>
 5+ <head>
 6+ <title>EditSurface Tests</title>
 7+ <link rel="stylesheet" href="../../modules/qunit/qunit.css" type="text/css" />
 8+ </head>
 9+ <body>
 10+ <h1 id="qunit-header">EditSurface Tests</h1>
 11+ <h2 id="qunit-banner"></h2>
 12+ <div id="qunit-testrunner-toolbar"></div>
 13+ <h2 id="qunit-userAgent"></h2>
 14+ <ol id="qunit-tests"></ol>
 15+ <div id="qunit-fixture">test markup</div>
 16+
 17+ <script src="../../modules/qunit/qunit.js"></script>
 18+
 19+ <!-- ve -->
 20+ <script src="../../modules/jquery/jquery.js"></script>
 21+ <script src="../../modules/ve/ve.js"></script>
 22+ <script src="../../modules/ve/ve.Position.js"></script>
 23+ <script src="../../modules/ve/ve.Range.js"></script>
 24+ <script src="../../modules/ve/ve.EventEmitter.js"></script>
 25+ <script src="../../modules/ve/ve.Node.js"></script>
 26+ <script src="../../modules/ve/ve.BranchNode.js"></script>
 27+ <script src="../../modules/ve/ve.LeafNode.js"></script>
 28+
 29+ <!-- dm -->
 30+ <script src="../../modules/ve/dm/ve.dm.js"></script>
 31+ <script src="../../modules/ve/dm/ve.dm.Node.js"></script>
 32+ <script src="../../modules/ve/dm/ve.dm.BranchNode.js"></script>
 33+ <script src="../../modules/ve/dm/ve.dm.LeafNode.js"></script>
 34+ <script src="../../modules/ve/dm/ve.dm.TransactionProcessor.js"></script>
 35+ <script src="../../modules/ve/dm/ve.dm.Transaction.js"></script>
 36+ <script src="../../modules/ve/dm/ve.dm.Surface.js"></script>
 37+
 38+ <script src="../../modules/ve/dm/nodes/ve.dm.DocumentNode.js"></script>
 39+ <script src="../../modules/ve/dm/nodes/ve.dm.HeadingNode.js"></script>
 40+ <script src="../../modules/ve/dm/nodes/ve.dm.ParagraphNode.js"></script>
 41+ <script src="../../modules/ve/dm/nodes/ve.dm.PreNode.js"></script>
 42+ <script src="../../modules/ve/dm/nodes/ve.dm.ListItemNode.js"></script>
 43+ <script src="../../modules/ve/dm/nodes/ve.dm.ListNode.js"></script>
 44+ <script src="../../modules/ve/dm/nodes/ve.dm.TableCellNode.js"></script>
 45+ <script src="../../modules/ve/dm/nodes/ve.dm.TableNode.js"></script>
 46+ <script src="../../modules/ve/dm/nodes/ve.dm.TableRowNode.js"></script>
 47+
 48+ <script src="../../modules/ve/dm/serializers/ve.dm.AnnotationSerializer.js"></script>
 49+ <script src="../../modules/ve/dm/serializers/ve.dm.HtmlSerializer.js"></script>
 50+ <script src="../../modules/ve/dm/serializers/ve.dm.JsonSerializer.js"></script>
 51+ <script src="../../modules/ve/dm/serializers/ve.dm.WikitextSerializer.js"></script>
 52+
 53+ <!-- Tests -->
 54+ <script src="ve.testData.js"></script>
 55+ <script src="ve.test.js"></script>
 56+ <script src="ve.Node.test.js"></script>
 57+ <script src="ve.BranchNode.test.js"></script>
 58+ <script src="ve.dm.TransactionProcessor.test.js"></script>
 59+ <script src="ve.dm.BranchNode.test.js"></script>
 60+ <script src="ve.dm.DocumentNode.test.js"></script>
 61+ </body>
 62+</html>
Property changes on: trunk/extensions/VisualEditor/tests/ve/index.html
___________________________________________________________________
Added: svn:eol-style
163 + native
Index: trunk/extensions/VisualEditor/tests/ve/ve.testData.js
@@ -0,0 +1,239 @@
 2+veTest = {};
 3+
 4+/*
 5+ * Sample plain object (WikiDom).
 6+ *
 7+ * There are two kinds of nodes in WikiDom:
 8+ *
 9+ * {Object} ElementNode
 10+ * type: {String} Symbolic node type name
 11+ * [attributes]: {Object} List of symbolic attribute name and literal value pairs
 12+ * [content]: {Object} Content node (not defined if node has children)
 13+ * [children]: {Object[]} Child nodes (not defined if node has content)
 14+ *
 15+ * {Object} ContentNode
 16+ * text: {String} Plain text data of content
 17+ * [annotations]: {Object[]} List of annotation objects that can be used to render text
 18+ * type: {String} Symbolic name of annotation type
 19+ * start: {Integer} Offset within text to begin annotation
 20+ * end: {Integer} Offset within text to end annotation
 21+ * [data]: {Object} Additional information, only used by more complex annotations
 22+ */
 23+veTest.obj = {
 24+ 'type': 'document',
 25+ 'children': [
 26+ {
 27+ 'type': 'paragraph',
 28+ 'content': {
 29+ 'text': 'abc',
 30+ 'annotations': [
 31+ {
 32+ 'type': 'textStyle/bold',
 33+ 'range': {
 34+ 'start': 1,
 35+ 'end': 2
 36+ }
 37+ },
 38+ {
 39+ 'type': 'textStyle/italic',
 40+ 'range': {
 41+ 'start': 2,
 42+ 'end': 3
 43+ }
 44+ }
 45+ ]
 46+ }
 47+ },
 48+ {
 49+ 'type': 'table',
 50+ 'children': [
 51+ {
 52+ 'type': 'tableRow',
 53+ 'children': [
 54+ {
 55+ 'type': 'tableCell',
 56+ 'children': [
 57+ {
 58+ 'type': 'paragraph',
 59+ 'content': {
 60+ 'text': 'd'
 61+ }
 62+ },
 63+ {
 64+ 'type': 'list',
 65+ 'children': [
 66+ {
 67+ 'type': 'listItem',
 68+ 'attributes': {
 69+ 'styles': ['bullet']
 70+ },
 71+ 'children': [
 72+ {
 73+ 'type': 'paragraph',
 74+ 'content': {
 75+ 'text': 'e'
 76+ }
 77+ }
 78+ ]
 79+ },
 80+ {
 81+ 'type': 'listItem',
 82+ 'attributes': {
 83+ 'styles': ['bullet', 'bullet']
 84+ },
 85+ 'children': [
 86+ {
 87+ 'type': 'paragraph',
 88+ 'content': {
 89+ 'text': 'f'
 90+ }
 91+ }
 92+ ]
 93+ },
 94+ {
 95+ 'type': 'listItem',
 96+ 'attributes': {
 97+ 'styles': ['number']
 98+ },
 99+ 'children': [
 100+ {
 101+ 'type': 'paragraph',
 102+ 'content': {
 103+ 'text': 'g'
 104+ }
 105+ }
 106+ ]
 107+ }
 108+ ]
 109+ }
 110+ ]
 111+ }
 112+ ]
 113+ }
 114+ ]
 115+ },
 116+ {
 117+ 'type': 'paragraph',
 118+ 'content': {
 119+ 'text': 'h'
 120+ }
 121+ }
 122+ ]
 123+};
 124+
 125+/*
 126+ * Sample content data.
 127+ *
 128+ * There are three types of components in content data:
 129+ *
 130+ * {String} Plain text character
 131+ *
 132+ * {Array} Annotated character
 133+ * {String} Character
 134+ * {String} Hash
 135+ * {Object}... List of annotation object references
 136+ *
 137+ * {Object} Opening or closing structural element
 138+ * type: {String} Symbolic node type name, if closing element first character will be "/"
 139+ * node: {Object} Reference to model tree node
 140+ * [attributes]: {Object} List of symbolic attribute name and literal value pairs
 141+ */
 142+veTest.data = [
 143+ // 0 - Beginning of paragraph
 144+ { 'type': 'paragraph' },
 145+ // 1 - Plain content
 146+ 'a',
 147+ // 2 - Annotated content
 148+ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
 149+ // 3 - Annotated content
 150+ ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
 151+ // 4 - End of paragraph
 152+ { 'type': '/paragraph' },
 153+ // 5 - Beginning of table
 154+ { 'type': 'table' },
 155+ // 6 - Beginning of row
 156+ { 'type': 'tableRow' },
 157+ // 7 - Beginning of cell
 158+ { 'type': 'tableCell' },
 159+ // 8 - Beginning of paragraph
 160+ { 'type': 'paragraph' },
 161+ // 9 - Plain content
 162+ 'd',
 163+ // 10 - End of paragraph
 164+ { 'type': '/paragraph' },
 165+ // 11 - Beginning of list
 166+ { 'type': 'list' },
 167+ // 12 - Beginning of bullet list item
 168+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } },
 169+ // 13 - Beginning of paragraph
 170+ { 'type': 'paragraph' },
 171+ // 14 - Plain content
 172+ 'e',
 173+ // 15 - End of paragraph
 174+ { 'type': '/paragraph' },
 175+ // 16 - End of item
 176+ { 'type': '/listItem' },
 177+ // 17 - Beginning of nested bullet list item
 178+ { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } },
 179+ // 18 - Beginning of paragraph
 180+ { 'type': 'paragraph' },
 181+ // 19 - Plain content
 182+ 'f',
 183+ // 20 - End of paragraph
 184+ { 'type': '/paragraph' },
 185+ // 21 - End of item
 186+ { 'type': '/listItem' },
 187+ // 22 - Beginning of numbered list item
 188+ { 'type': 'listItem', 'attributes': { 'styles': ['number'] } },
 189+ // 23 - Beginning of paragraph
 190+ { 'type': 'paragraph' },
 191+ // 24 - Plain content
 192+ 'g',
 193+ // 25 - End of paragraph
 194+ { 'type': '/paragraph' },
 195+ // 26 - End of item
 196+ { 'type': '/listItem' },
 197+ // 27 - End of list
 198+ { 'type': '/list' },
 199+ // 28 - End of cell
 200+ { 'type': '/tableCell' },
 201+ // 29 - End of row
 202+ { 'type': '/tableRow' },
 203+ // 30 - End of table
 204+ { 'type': '/table' },
 205+ // 31 - Beginning of paragraph
 206+ { 'type': 'paragraph' },
 207+ // 32 - Plain content
 208+ 'h',
 209+ // 33 - End of paragraph
 210+ { 'type': '/paragraph' }
 211+];
 212+
 213+/**
 214+ * Sample content data index.
 215+ *
 216+ * This is a node tree that describes each partition within the document's content data. This is
 217+ * what is automatically built by the ve.DocumentNode constructor.
 218+ */
 219+veTest.tree = [
 220+ new ve.dm.ParagraphNode( veTest.data[0], 3 ),
 221+ new ve.dm.TableNode( veTest.data[5], [
 222+ new ve.dm.TableRowNode( veTest.data[6], [
 223+ new ve.dm.TableCellNode( veTest.data[7], [
 224+ new ve.dm.ParagraphNode( veTest.data[8], 1 ),
 225+ new ve.dm.ListNode( veTest.data[11], [
 226+ new ve.dm.ListItemNode( veTest.data[12], [
 227+ new ve.dm.ParagraphNode( veTest.data[13], 1 )
 228+ ] ),
 229+ new ve.dm.ListItemNode( veTest.data[17], [
 230+ new ve.dm.ParagraphNode( veTest.data[18], 1 )
 231+ ] ),
 232+ new ve.dm.ListItemNode( veTest.data[22], [
 233+ new ve.dm.ParagraphNode( veTest.data[23], 1 )
 234+ ] )
 235+ ] )
 236+ ] )
 237+ ] )
 238+ ] ),
 239+ new ve.dm.ParagraphNode( veTest.data[31], 1 )
 240+];
Property changes on: trunk/extensions/VisualEditor/tests/ve/ve.testData.js
___________________________________________________________________
Added: svn:eol-style
1241 + native

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r110805Migrated es.* to new ve.* namespace which is more structured, and will make i...tparscal23:50, 6 February 2012

Status & tagging log