Index: trunk/parsers/wikidom/tests/hype/es.ModelNode.test.js |
— | — | @@ -1,6 +1,6 @@ |
2 | 2 | module( 'Bases' ); |
3 | 3 | |
4 | | -test( 'es.ModelNode', 17, function() { |
| 4 | +test( 'es.ModelNode',20, function() { |
5 | 5 | // Example data (integers) is used for simplicity of testing |
6 | 6 | var modelNode1 = new es.ModelNode(), |
7 | 7 | modelNode2 = new es.ModelNode(), |
— | — | @@ -16,53 +16,74 @@ |
17 | 17 | modelNode2.on( 'afterAttach', function() { |
18 | 18 | attaches++; |
19 | 19 | } ); |
| 20 | + modelNode3.on( 'afterAttach', function() { |
| 21 | + attaches++; |
| 22 | + } ); |
| 23 | + modelNode4.on( 'afterAttach', function() { |
| 24 | + attaches++; |
| 25 | + } ); |
20 | 26 | var detaches = 0; |
21 | 27 | modelNode2.on( 'afterDetach', function() { |
22 | 28 | detaches++; |
23 | 29 | } ); |
| 30 | + modelNode3.on( 'afterDetach', function() { |
| 31 | + detaches++; |
| 32 | + } ); |
| 33 | + modelNode4.on( 'afterDetach', function() { |
| 34 | + detaches++; |
| 35 | + } ); |
24 | 36 | |
25 | 37 | /** @covers es.ModelNode.push */ |
26 | 38 | modelNode1.push( modelNode2 ); |
27 | | - equal( updates, 1, 'es.ModelNode emits update events on push' ); |
28 | | - equal( modelNode1[0], modelNode2, 'es.ModelNode appends node on push' ); |
| 39 | + equal( updates, 1, 'push emits update events' ); |
| 40 | + equal( modelNode1[0], modelNode2, 'push appends a node' ); |
29 | 41 | |
30 | 42 | /** @covers es.ModelNode.attach */ |
31 | | - equal( attaches, 1, 'es.ModelNode emits afterAttach events when added to another node' ); |
| 43 | + equal( attaches, 1, 'push attaches added node' ); |
32 | 44 | |
33 | 45 | /** @covers es.ModelNode.unshift */ |
34 | 46 | modelNode1.unshift( modelNode3 ); |
35 | | - equal( updates, 2, 'es.ModelNode emits update events on unshift' ); |
36 | | - equal( modelNode1[0], modelNode3, 'es.ModelNode prepends node on unshift' ); |
| 47 | + equal( updates, 2, 'unshift emits update events' ); |
| 48 | + equal( modelNode1[0], modelNode3, 'unshift prepends a node' ); |
37 | 49 | |
| 50 | + /** @covers es.ModelNode.attach */ |
| 51 | + equal( attaches, 2, 'unshift attaches added node' ); |
| 52 | + |
38 | 53 | /** @covers es.ModelNode.splice */ |
39 | 54 | modelNode1.splice( 1, 0, modelNode4 ); |
40 | | - equal( updates, 3, 'es.ModelNode emits update events on splice' ); |
41 | | - equal( modelNode1[1], modelNode4, 'es.ModelNode inserts node on splice' ); |
| 55 | + equal( updates, 3, 'splice emits update events' ); |
| 56 | + equal( modelNode1[1], modelNode4, 'splice inserts nodes' ); |
42 | 57 | |
| 58 | + /** @covers es.ModelNode.attach */ |
| 59 | + equal( attaches, 3, 'splice attaches added nodes' ); |
| 60 | + |
43 | 61 | /** @covers es.ModelNode.reverse */ |
44 | 62 | modelNode1.reverse(); |
45 | | - equal( updates, 4, 'es.ModelNode emits update events on reverse' ); |
| 63 | + equal( updates, 4, 'reverse emits update events' ); |
46 | 64 | |
47 | 65 | /** @covers es.ModelNode.sort */ |
48 | 66 | modelNode1.sort( function( a, b ) { |
49 | 67 | return a.length < b.length ? -1 : 1; |
50 | 68 | } ); |
51 | | - equal( updates, 5, 'es.ModelNode emits update events on sort' ); |
| 69 | + equal( updates, 5, 'sort emits update events' ); |
52 | 70 | deepEqual( |
53 | 71 | modelNode1.slice( 0 ), |
54 | 72 | [modelNode2, modelNode3, modelNode4], |
55 | | - 'es.ModelNode properly orders nodes on sort' |
| 73 | + 'sort reorderes nodes correctly' |
56 | 74 | ); |
57 | 75 | |
58 | 76 | /** @covers es.ModelNode.pop */ |
59 | 77 | modelNode1.pop(); |
60 | | - equal( updates, 6, 'es.ModelNode emits update events on pop' ); |
| 78 | + equal( updates, 6, 'pop emits update events' ); |
61 | 79 | deepEqual( |
62 | 80 | modelNode1.slice( 0 ), |
63 | 81 | [modelNode2, modelNode3], |
64 | | - 'es.ModelNode removes last node on pop' |
| 82 | + 'pop removes the last child node' |
65 | 83 | ); |
66 | 84 | |
| 85 | + /** @covers es.ModelNode.detach */ |
| 86 | + equal( detaches, 1, 'pop detaches a node' ); |
| 87 | + |
67 | 88 | /** @covers es.ModelNode.shift */ |
68 | 89 | modelNode1.shift(); |
69 | 90 | equal( updates, 7, 'es.ModelNode emits update events on shift' ); |
— | — | @@ -73,14 +94,14 @@ |
74 | 95 | ); |
75 | 96 | |
76 | 97 | /** @covers es.ModelNode.detach */ |
77 | | - equal( detaches, 1, 'es.ModelNode emits afterDetach events when removed from another node' ); |
| 98 | + equal( detaches, 2, 'shift detaches a node' ); |
78 | 99 | |
79 | 100 | /** @covers es.ModelNode.getParent */ |
80 | | - strictEqual( modelNode3.getParent(), modelNode1, 'Child nodes have correct parent reference' ); |
| 101 | + strictEqual( modelNode3.getParent(), modelNode1, 'getParent returns the correct reference' ); |
81 | 102 | |
82 | 103 | try { |
83 | 104 | var view = modelNode3.createView(); |
84 | 105 | } catch ( err ){ |
85 | | - ok( true, 'Calling createView on a plain ModelNode throws an exception' ); |
| 106 | + ok( true, 'createView throws an exception when not overridden' ); |
86 | 107 | } |
87 | 108 | } ); |
Index: trunk/parsers/wikidom/tests/hype/index.html |
— | — | @@ -14,6 +14,7 @@ |
15 | 15 | <script src="../../lib/qunit.js"></script> |
16 | 16 | <script src="../../lib/hype/es.js"></script> |
17 | 17 | <script src="../../lib/hype/es.Range.js"></script> |
| 18 | + <script src="../../lib/hype/es.Transaction.js"></script> |
18 | 19 | <script src="../../lib/hype/bases/es.EventEmitter.js"></script> |
19 | 20 | <script src="../../lib/hype/bases/es.ModelNode.js"></script> |
20 | 21 | <script src="../../lib/hype/bases/es.ViewNode.js"></script> |
Index: trunk/parsers/wikidom/tests/hype/es.DocumentModel.test.js |
— | — | @@ -201,12 +201,12 @@ |
202 | 202 | new es.ParagraphModel( data[25], 1 ) |
203 | 203 | ]; |
204 | 204 | |
205 | | -test( 'es.DocumentModel', 11, function() { |
| 205 | +test( 'es.DocumentModel', 15, function() { |
206 | 206 | var documentModel = es.DocumentModel.newFromPlainObject( obj ); |
207 | 207 | |
208 | 208 | deepEqual( documentModel.getData(), data, 'Flattening plain objects results in correct data' ); |
209 | 209 | |
210 | | - deepEqual( documentModel.slice( 0 ), tree, 'Nodes contain correct lengths' ); |
| 210 | + deepEqual( documentModel.slice( 0 ), tree, 'Nodes in the model tree contain correct lengths' ); |
211 | 211 | |
212 | 212 | deepEqual( |
213 | 213 | documentModel[0].getContent( new es.Range( 1, 3 ) ), |
— | — | @@ -214,42 +214,31 @@ |
215 | 215 | ['b', { 'type': 'bold', 'hash': '#bold' }], |
216 | 216 | ['c', { 'type': 'italic', 'hash': '#italic' }] |
217 | 217 | ], |
218 | | - 'When getting content for a node, ranges can trim left' |
| 218 | + 'getContent can return an ending portion of the content' |
219 | 219 | ); |
220 | 220 | |
221 | 221 | deepEqual( |
222 | 222 | documentModel[0].getContent( new es.Range( 0, 2 ) ), |
223 | | - [ |
224 | | - 'a', |
225 | | - ['b', { 'type': 'bold', 'hash': '#bold' }], |
226 | | - ], |
227 | | - 'When getting content for a node, ranges can trim right' |
| 223 | + ['a', ['b', { 'type': 'bold', 'hash': '#bold' }]], |
| 224 | + 'getContent can return a beginning portion of the content' |
228 | 225 | ); |
229 | 226 | |
230 | 227 | deepEqual( |
231 | 228 | documentModel[0].getContent( new es.Range( 1, 2 ) ), |
232 | | - [ |
233 | | - ['b', { 'type': 'bold', 'hash': '#bold' }], |
234 | | - ], |
235 | | - 'When getting content for a node, ranges can trim left and right' |
| 229 | + [['b', { 'type': 'bold', 'hash': '#bold' }]], |
| 230 | + 'getContent can return a middle portion of the content' |
236 | 231 | ); |
237 | 232 | |
238 | 233 | try { |
239 | 234 | documentModel[0].getContent( new es.Range( -1, 3 ) ); |
240 | 235 | } catch ( err ) { |
241 | | - ok( |
242 | | - true, |
243 | | - 'Exceptions are thrown when getting node content within a range starting before 0' |
244 | | - ); |
| 236 | + ok( true, 'getContent throws exceptions when given a range with start < 0' ); |
245 | 237 | } |
246 | 238 | |
247 | 239 | try { |
248 | 240 | documentModel[0].getContent( new es.Range( 0, 4 ) ); |
249 | 241 | } catch ( err ) { |
250 | | - ok( |
251 | | - true, |
252 | | - 'Exceptions are thrown when getting node content within a range ending after length' |
253 | | - ); |
| 242 | + ok( true, 'getContent throws exceptions when given a range with end > length' ); |
254 | 243 | } |
255 | 244 | |
256 | 245 | deepEqual( documentModel[2].getContent(), ['a'], 'Content can be extracted from nodes' ); |
— | — | @@ -276,4 +265,43 @@ |
277 | 266 | -1, |
278 | 267 | 'getIndexOfAnnotation returns -1 if the annotation was not found' |
279 | 268 | ); |
| 269 | + |
| 270 | + deepEqual( |
| 271 | + documentModel.prepareElementAttributeChange( 0, 'set', 'test', 1234 ), |
| 272 | + [ |
| 273 | + { 'type': 'attribute', 'method': 'set', 'key': 'test', 'value': 1234 }, |
| 274 | + { 'type': 'retain', 'length': 28 } |
| 275 | + ], |
| 276 | + 'prepareElementAttributeChange retains data after attribute change for first element' |
| 277 | + ); |
| 278 | + |
| 279 | + deepEqual( |
| 280 | + documentModel.prepareElementAttributeChange( 5, 'set', 'test', 1234 ), |
| 281 | + [ |
| 282 | + { 'type': 'retain', 'length': 5 }, |
| 283 | + { 'type': 'attribute', 'method': 'set', 'key': 'test', 'value': 1234 }, |
| 284 | + { 'type': 'retain', 'length': 23 } |
| 285 | + ], |
| 286 | + 'prepareElementAttributeChange retains data before and after attribute change' |
| 287 | + ); |
| 288 | + |
| 289 | + try { |
| 290 | + documentModel.prepareElementAttributeChange( 1, 'set', 'test', 1234 ) |
| 291 | + } catch ( err ) { |
| 292 | + ok( |
| 293 | + true, |
| 294 | + 'prepareElementAttributeChange throws an exception when offset is not an element' |
| 295 | + ); |
| 296 | + } |
| 297 | + |
| 298 | + try { |
| 299 | + documentModel.prepareElementAttributeChange( 4, 'set', 'test', 1234 ) |
| 300 | + } catch ( err ) { |
| 301 | + ok( |
| 302 | + true, |
| 303 | + 'prepareElementAttributeChange throws an exception when offset is a closing element' |
| 304 | + ); |
| 305 | + } |
| 306 | + |
280 | 307 | } ); |
| 308 | + |
Index: trunk/parsers/wikidom/lib/hype/models/es.DocumentModel.js |
— | — | @@ -545,6 +545,7 @@ |
546 | 546 | * @returns {es.Transaction} |
547 | 547 | */ |
548 | 548 | es.DocumentModel.prototype.prepareInsertion = function( offset, data ) { |
| 549 | + var tx = new es.Transaction(); |
549 | 550 | /* |
550 | 551 | * There are 2 basic types of locations the insertion point can be: |
551 | 552 | * Structural locations |
— | — | @@ -570,6 +571,7 @@ |
571 | 572 | * } |
572 | 573 | * } |
573 | 574 | */ |
| 575 | + return tx; |
574 | 576 | }; |
575 | 577 | |
576 | 578 | /** |
— | — | @@ -580,6 +582,7 @@ |
581 | 583 | * @returns {es.Transaction} |
582 | 584 | */ |
583 | 585 | es.DocumentModel.prototype.prepareRemoval = function( range ) { |
| 586 | + var tx = new es.Transaction(); |
584 | 587 | /* |
585 | 588 | * if ( The range spans structural elements ) { |
586 | 589 | * if ( The range partially overlaps structural elements ) { |
— | — | @@ -591,6 +594,7 @@ |
592 | 595 | * Removing only content is OK, do nothing |
593 | 596 | * } |
594 | 597 | */ |
| 598 | + return tx; |
595 | 599 | }; |
596 | 600 | |
597 | 601 | /** |
— | — | @@ -644,12 +648,17 @@ |
645 | 649 | if ( offset ) { |
646 | 650 | tx.pushRetain( offset ); |
647 | 651 | } |
648 | | - if ( this.data[offset].type !== undefined ) { |
649 | | - tx.pushChangeElementAttribute( method, key, value ); |
| 652 | + if ( this.data[offset].type === undefined ) { |
| 653 | + throw 'Invalid element offset error. Can not set attributes to non-element data.' |
650 | 654 | } |
| 655 | + if ( this.data[offset].type[0] === '/' ) { |
| 656 | + throw 'Invalid element offset error. Can not set attributes on closing element.' |
| 657 | + } |
| 658 | + tx.pushChangeElementAttribute( method, key, value ); |
651 | 659 | if ( offset < this.data.length ) { |
652 | 660 | tx.pushRetain( this.data.length - offset ); |
653 | 661 | } |
| 662 | + return tx; |
654 | 663 | }; |
655 | 664 | |
656 | 665 | /** |
Index: trunk/parsers/wikidom/lib/hype/es.Transaction.js |
— | — | @@ -11,28 +11,28 @@ |
12 | 12 | |
13 | 13 | /* Methods */ |
14 | 14 | |
15 | | -es.Transaction.prototype.pushRetain( length ) { |
| 15 | +es.Transaction.prototype.pushRetain = function( length ) { |
16 | 16 | this.push( { |
17 | 17 | 'type': 'retain', |
18 | 18 | 'length': length |
19 | 19 | } ); |
20 | 20 | }; |
21 | 21 | |
22 | | -es.Transaction.prototype.pushInsert( content ) { |
| 22 | +es.Transaction.prototype.pushInsert = function( content ) { |
23 | 23 | this.push( { |
24 | 24 | 'type': 'insert', |
25 | 25 | 'data': data |
26 | 26 | } ); |
27 | 27 | }; |
28 | 28 | |
29 | | -es.Transaction.prototype.pushRemove( data ) { |
| 29 | +es.Transaction.prototype.pushRemove = function( data ) { |
30 | 30 | this.push( { |
31 | 31 | 'type': 'remove', |
32 | 32 | 'data': data |
33 | 33 | } ); |
34 | 34 | }; |
35 | 35 | |
36 | | -es.Transaction.prototype.pushChangeElementAttribute( method, key, value ) { |
| 36 | +es.Transaction.prototype.pushChangeElementAttribute = function( method, key, value ) { |
37 | 37 | this.push( { |
38 | 38 | 'type': 'attribute', |
39 | 39 | 'method': method, |
— | — | @@ -41,7 +41,7 @@ |
42 | 42 | } ); |
43 | 43 | }; |
44 | 44 | |
45 | | -es.Transaction.prototype.pushStartAnnotating( method, annotation ) { |
| 45 | +es.Transaction.prototype.pushStartAnnotating = function( method, annotation ) { |
46 | 46 | this.push( { |
47 | 47 | 'type': 'annotate', |
48 | 48 | 'method': method, |
— | — | @@ -50,7 +50,7 @@ |
51 | 51 | } ); |
52 | 52 | }; |
53 | 53 | |
54 | | -es.Transaction.prototype.pushStopAnnotating( method, annotation ) { |
| 54 | +es.Transaction.prototype.pushStopAnnotating = function( method, annotation ) { |
55 | 55 | this.push( { |
56 | 56 | 'type': 'annotate', |
57 | 57 | 'method': method, |