Implement prepareWrap and add tests for it

This commit is contained in:
Roan Kattouw 2012-03-08 23:21:26 +00:00
parent 9b66749575
commit 5054ed320e
Notes: Roan Kattouw 2012-03-08 23:21:26 +00:00
3 changed files with 128 additions and 1 deletions

View file

@ -1145,6 +1145,87 @@ ve.dm.DocumentNode.prototype.prepareContentReplacement = function( range, data )
*
*/
ve.dm.DocumentNode.prototype.prepareWrap = function( range, unwrapOuter, wrapOuter, unwrapEach, wrapEach ) {
// Function to generate arrays of closing elements in reverse order
function closingArray( openings ) {
var closings = [], i, len = openings.length;
for ( i = 0; i < len; i++ ) {
closings[closings.length] = { 'type': '/' + openings[len - i - 1].type };
}
return closings;
}
// TODO sanity checks:
// * range.start > unwrapOuter.length
// * unwraps actually match
// * result is valid
var tx = new ve.dm.Transaction();
range.normalize();
if ( range.start > unwrapOuter.length ) {
// Retain up to the first thing we're unwrapping
// The outer unwrapping takes place *outside*
// the range, so compensate for that
tx.pushRetain ( range.start - unwrapOuter.length );
}
// Replace the opening elements for the outer unwrap&wrap
if ( wrapOuter.length > 0 || unwrapOuter.length > 0 ) {
tx.pushReplace( unwrapOuter, wrapOuter );
}
if ( wrapEach.length > 0 && unwrapEach.length > 0 ) {
var closingUnwrapEach = closingArray( unwrapEach ),
closingWrapEach = closingArray( wrapEach ),
depth = 0,
startOffset,
i;
// Visit each top-level child and wrap/unwrap it
// TODO figure out if we should use the tree/node functions here
// rather than iterating over offsets, it may or may not be faster
for ( i = range.start; i < range.end; i++ ) {
if ( this.data[offset].type === undefined ) {
// This is a content offset, skip
} else {
// This is a structural offset
if ( this.data[offset].type.charAt( 0 ) != '/' ) {
// This is an opening element
if ( depth == 0 ) {
// We are at the start of a top-level element
// Replace the opening elements
tx.pushReplace( unwrapEach, wrapEach );
// Store this offset for later
startOffset = i;
}
depth++;
} else {
// This is a closing element
depth--;
if ( depth == 0 ) {
// We are at the end of a top-level element
// Retain the contents of what we're wrapping
tx.pushRetain( i - startOffset + 1 - unwrapEach.length*2 );
// Replace the closing elements
tx.pushReplace( closingUnwrapEach, closingWrapEach );
}
}
}
}
} else {
// There is no wrapEach/unwrapEach to be done, just retain
// up to the end of the range
tx.pushRetain( range.end - range.start );
}
if ( wrapOuter.length > 0 || unwrapOuter.length > 0 ) {
tx.pushReplace( closingArray( unwrapOuter ), closingArray( wrapOuter ) );
}
// Retain up to the end of the document
if ( range.end < this.data.length ) {
tx.pushRetain( this.data.length - range.end - unwrapOuter.length );
}
return tx;
};

View file

@ -800,3 +800,19 @@ test( 've.dm.DocumentNode.prepareInsertion', 11, function() {
'prepareInsertion throws exception for malformed input'
);
} );
test( 've.dm.DocumentNode.prepareWrap', 1, function() {
var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
// Test 1
deepEqual(
documentModel.prepareWrap( new ve.Range( 1, 4 ), [ { 'type': 'paragraph' } ], [ { 'type': 'heading', 'level': 2 } ], [], [] ).getOperations(),
[
{ 'type': 'replace', 'remove': [ { 'type': 'paragraph' } ], 'replacement': [ { 'type': 'heading', 'level': 2 } ] },
{ 'type': 'retain', 'length': 3 },
{ 'type': 'replace', 'remove': [ { 'type': '/paragraph' } ], 'replacement': [ { 'type': '/heading' } ] },
{ 'type': 'retain', 'length': 29 }
],
'prepareWrap changes a paragraph to a heading'
);
} );

View file

@ -1,6 +1,6 @@
module( 've/dm' );
test( 've.dm.TransactionProcessor', 33, function() {
test( 've.dm.TransactionProcessor', 35, function() {
var documentModel = ve.dm.DocumentNode.newFromPlainObject( veTest.obj );
// FIXME: These tests shouldn't use prepareFoo() because those functions
@ -404,4 +404,34 @@ test( 've.dm.TransactionProcessor', 33, function() {
],
'rollback restores content'
);
var paragraphToHeading = documentModel.prepareWrap( new ve.Range( 1, 4 ), [ { 'type': 'paragraph' } ], [ { 'type': 'heading', 'level': 2 } ], [], [] );
// Test 33
ve.dm.TransactionProcessor.commit( documentModel, paragraphToHeading );
deepEqual(
documentModel.getData( new ve.Range( 0, 5 ) ),
[
{ 'type': 'heading', 'level': 2 },
'a',
['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
{ 'type': '/heading' }
],
'changing paragraph to heading'
);
// Test 34
ve.dm.TransactionProcessor.rollback( documentModel, paragraphToHeading );
deepEqual(
documentModel.getData( new ve.Range( 0, 5 ) ),
[
{ 'type': 'paragraph' },
'a',
['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }],
['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }],
{ 'type': '/paragraph' }
],
'rollback puts paragraph back'
);
} );