The resurrection

By removing the transaction listeners from surface fragments we
no longer have to make sure they are always manually destroyed.

In order to retain the functionality of having fragments update
with transactions elsewhere we keep a pointer to a place in the
new complete history stack in the surface. The complete history
stack records all transactions, even undone ones.

Whenever getRange is called we replay all transactions in the
complete history (in the correct order) since the fragment was
last updated.

Also in this commit:
* Updated Format/IndentationAction to test undo(). This increases
  coverage of surface fragment behaviour.
* .range is always accessed by .getRange now, although as an
  optimisation we can use the noCopy mode when we a sure the
  returned range will not be modified.
* Added undo test to .update (previously .onTransact)

Bug: 47343
Change-Id: I9e9818da1baa8319a3002f6d74fd1aad6732a8f5
This commit is contained in:
Ed Sanders 2013-04-22 12:20:26 +01:00
parent 1d27c3cd26
commit 8b09dd7650
16 changed files with 296 additions and 227 deletions

View file

@ -42,7 +42,7 @@ ve.AnnotationAction.static.methods = ['set', 'clear', 'toggle', 'clearAll'];
* @param {Object} [data] Additional annotation data
*/
ve.AnnotationAction.prototype.set = function ( name, data ) {
this.surface.getModel().getFragment().annotateContent( 'set', name, data ).destroy();
this.surface.getModel().getFragment().annotateContent( 'set', name, data );
};
/**
@ -53,7 +53,7 @@ ve.AnnotationAction.prototype.set = function ( name, data ) {
* @param {Object} [data] Additional annotation data
*/
ve.AnnotationAction.prototype.clear = function ( name, data ) {
this.surface.getModel().getFragment().annotateContent( 'clear', name, data ).destroy();
this.surface.getModel().getFragment().annotateContent( 'clear', name, data );
};
/**
@ -68,11 +68,9 @@ ve.AnnotationAction.prototype.clear = function ( name, data ) {
*/
ve.AnnotationAction.prototype.toggle = function ( name, data ) {
var fragment = this.surface.getModel().getFragment();
fragment
.annotateContent(
fragment.getAnnotations().hasAnnotationWithName( name ) ? 'clear' : 'set', name, data
)
.destroy();
fragment.annotateContent(
fragment.getAnnotations().hasAnnotationWithName( name ) ? 'clear' : 'set', name, data
);
};
/**
@ -91,7 +89,6 @@ ve.AnnotationAction.prototype.clearAll = function () {
for ( i = 0, len = arr.length; i < len; i++ ) {
fragment.annotateContent( 'clear', arr[i].name, arr[i].data );
}
fragment.destroy();
};
/* Registration */

View file

@ -42,7 +42,7 @@ ve.ContentAction.static.methods = ['insert', 'remove', 'select'];
* @param {boolean} annotate Content should be automatically annotated to match surrounding content
*/
ve.ContentAction.prototype.insert = function ( content, annotate ) {
this.surface.getModel().getFragment().insertContent( content, annotate ).destroy();
this.surface.getModel().getFragment().insertContent( content, annotate );
};
/**
@ -51,7 +51,7 @@ ve.ContentAction.prototype.insert = function ( content, annotate ) {
* @method
*/
ve.ContentAction.prototype.remove = function () {
this.surface.getModel().getFragment().removeContent().destroy();
this.surface.getModel().getFragment().removeContent();
};
/**

View file

@ -65,10 +65,9 @@ ve.FormatAction.prototype.convert = function ( type, attributes ) {
}
for ( i = 0, length = fragments.length; i < length; i++ ) {
fragments[i].isolateAndUnwrap( type ).destroy();
fragments[i].isolateAndUnwrap( type );
}
selection = fragmentForSelection.getRange();
fragmentForSelection.destroy();
txs = ve.dm.Transaction.newFromContentBranchConversion( doc, selection, type, attributes );
surfaceModel.change( txs, selection );

View file

@ -65,10 +65,9 @@ ve.IndentationAction.prototype.increase = function () {
this.indentListItem(
documentModel.getNodeFromOffset( fragments[i].getRange().start + 1 )
);
fragments[i].destroy();
}
selected.select().destroy();
selected.select();
return increased;
};
@ -104,10 +103,9 @@ ve.IndentationAction.prototype.decrease = function () {
this.unindentListItem(
documentModel.getNodeFromOffset( fragments[i].getRange().start + 1 )
);
fragments[i].destroy();
}
selected.select().destroy();
selected.select();
return decreased;
};
@ -301,7 +299,6 @@ ve.IndentationAction.prototype.unindentListItem = function ( listItem ) {
);
surfaceModel.change( tx );
}
fragment.destroy();
};
/* Registration */

View file

@ -94,7 +94,7 @@ ve.ce.ImageNode.prototype.onClick = function ( e ) {
[ selectionRange, nodeRange ], selectionRange.from > nodeRange.from
) :
nodeRange
).select().destroy();
).select();
};
/* Registration */

View file

@ -324,12 +324,11 @@ ve.ce.Surface.prototype.onDocumentDrop = function ( e ) {
nodeData = originFragment.getData();
// Remove node from old location (auto-updates targetFragment's range)
originFragment.removeContent().destroy();
originFragment.removeContent();
// Re-insert node at new location and re-select it
targetFragment.insertContent( nodeData );
targetFragment.adjustRange( -nodeData.length, 0 ).select().destroy();
targetFragment.destroy();
targetFragment.adjustRange( -nodeData.length, 0 ).select();
}, this ) );
}

View file

@ -24,6 +24,7 @@ ve.dm.Surface = function VeDmSurface( doc ) {
this.selectedNodes = {};
this.smallStack = [];
this.bigStack = [];
this.completeHistory = [];
this.undoIndex = 0;
this.historyTrackingInterval = null;
this.insertionAnnotations = new ve.dm.AnnotationSet( this.documentModel.getStore() );
@ -286,6 +287,10 @@ ve.dm.Surface.prototype.change = function ( transactions, selection ) {
this.bigStack = this.bigStack.slice( 0, this.bigStack.length - this.undoIndex );
this.undoIndex = 0;
this.smallStack.push( transactions[i] );
this.completeHistory.push( {
'undo': false,
'transaction': transactions[i]
} );
this.documentModel.commit( transactions[i] );
}
}
@ -411,7 +416,11 @@ ve.dm.Surface.prototype.undo = function () {
for ( i = item.stack.length - 1; i >= 0; i-- ) {
transaction = item.stack[i];
selection = transaction.translateRange( selection, true );
this.documentModel.rollback( item.stack[i] );
this.completeHistory.push( {
'undo': true,
'transaction': transaction
} );
this.documentModel.rollback( transaction );
}
this.emit( 'unlock' );
this.emit( 'history' );
@ -420,6 +429,23 @@ ve.dm.Surface.prototype.undo = function () {
return null;
};
/**
* Get the length of the complete history stack. This is also the current pointer.
* @returns {number} Length of the complete history stack
*/
ve.dm.Surface.prototype.getCompleteHistoryLength = function () {
return this.completeHistory.length;
};
/**
* Get all the items in the complete history stack since a specified pointer.
* @param {number} pointer Pointer from where to start the slice
* @returns {Array} Array of transaction objects with undo flag
*/
ve.dm.Surface.prototype.getCompleteHistorySince = function ( pointer ) {
return this.completeHistory.slice( pointer );
};
/**
* Step forwards in history.
*
@ -434,7 +460,7 @@ ve.dm.Surface.prototype.redo = function () {
if ( !this.enabled ) {
return;
}
var selection, item, i;
var item, i, transaction, selection;
this.breakpoint();
if ( this.undoIndex > 0 && this.bigStack[this.bigStack.length - this.undoIndex] ) {
@ -442,7 +468,12 @@ ve.dm.Surface.prototype.redo = function () {
item = this.bigStack[this.bigStack.length - this.undoIndex];
selection = item.selection;
for ( i = 0; i < item.stack.length; i++ ) {
this.documentModel.commit( item.stack[i] );
transaction = item.stack[i];
this.completeHistory.push( {
'undo': false,
'transaction': transaction
} );
this.documentModel.commit( transaction );
}
this.undoIndex--;
this.emit( 'unlock' );

View file

@ -21,26 +21,23 @@ ve.dm.SurfaceFragment = function VeDmSurfaceFragment( surface, range, noAutoSele
}
// Properties
this.range = range && range instanceof ve.Range ? range : surface.getSelection();
this.surface = surface;
this.setRange( range && range instanceof ve.Range ? range : surface.getSelection() );
// Short-circuit for invalid range null fragment
if ( !this.range ) {
return this;
}
this.surface = surface;
this.document = surface.getDocument();
this.noAutoSelect = !!noAutoSelect;
this.onTransactHandler = ve.bind( this.onTransact, this );
// Events
surface.on( 'transact', this.onTransactHandler );
// Initialization
var length = this.document.data.getLength();
this.range = new ve.Range(
this.setRange( new ve.Range(
// Clamp range to valid document offsets
Math.min( Math.max( this.range.from, 0 ), length ),
Math.min( Math.max( this.range.to, 0 ), length )
);
) );
};
/* Static Properties */
@ -55,16 +52,19 @@ ve.dm.SurfaceFragment.static = {};
/* Methods */
/**
* Handle transactions being processed on the document.
*
* This keeps the range of the fragment valid, even while other transactions are being processed
* Update range based on un-applied transactions in the surface.
*
* @method
* @param {ve.dm.Transaction[]} txs Transactions that have just been processed
*/
ve.dm.SurfaceFragment.prototype.onTransact = function ( txs ) {
for ( var i = 0; i < txs.length; i++ ) {
this.range = txs[i].translateRange( this.range );
ve.dm.SurfaceFragment.prototype.update = function () {
var i, length, txs;
// Small optimisation: check history pointer is in the past
if ( this.historyPointer < this.getSurface().getCompleteHistoryLength() ) {
txs = this.getSurface().getCompleteHistorySince( this.historyPointer );
for ( i = 0, length = txs.length; i < length; i++ ) {
this.range = txs[i].transaction.translateRange( this.range, txs[i].undo );
this.historyPointer++;
}
}
};
@ -91,11 +91,26 @@ ve.dm.SurfaceFragment.prototype.getDocument = function () {
/**
* Get the range of the fragment within the surface.
*
* This method also calls update to make sure the range returned is current.
*
* @method
* @param {boolean} noCopy Return the range by reference, not a copy
* @returns {ve.Range} Surface range
*/
ve.dm.SurfaceFragment.prototype.getRange = function () {
return this.range.clone();
ve.dm.SurfaceFragment.prototype.getRange = function ( noCopy ) {
this.update();
return noCopy ? this.range : this.range.clone();
};
/**
* Update the range of the transaction and reset the history pointer.
*
* @method
* @param {ve.Range} range New range
*/
ve.dm.SurfaceFragment.prototype.setRange = function ( range ) {
this.historyPointer = this.getSurface().getCompleteHistoryLength();
this.range = range;
};
/**
@ -108,21 +123,6 @@ ve.dm.SurfaceFragment.prototype.isNull = function () {
return this.surface === undefined;
};
/**
* Destroys fragment, removing event handlers and object references, leaving the fragment null.
*
* Call this whenever you are done using a fragment.
*
* @method
*/
ve.dm.SurfaceFragment.prototype.destroy = function () {
if ( this.surface ) {
this.surface.removeListener( 'transact', this.onTransactHandler );
this.surface = null;
this.document = null;
}
};
/**
* Get a new fragment with an adjusted position
*
@ -138,7 +138,7 @@ ve.dm.SurfaceFragment.prototype.adjustRange = function ( start, end ) {
}
return new ve.dm.SurfaceFragment(
this.surface,
new ve.Range( this.range.start + ( start || 0 ), this.range.end + ( end || 0 ) ),
new ve.Range( this.getRange( true ).start + ( start || 0 ), this.getRange( true ).end + ( end || 0 ) ),
this.noAutoSelect
);
};
@ -157,7 +157,7 @@ ve.dm.SurfaceFragment.prototype.truncateRange = function ( limit ) {
}
return new ve.dm.SurfaceFragment(
this.surface,
this.range.truncate( limit ),
this.getRange().truncate( limit ),
this.noAutoSelect
);
};
@ -174,7 +174,7 @@ ve.dm.SurfaceFragment.prototype.collapseRange = function () {
return this;
}
return new ve.dm.SurfaceFragment(
this.surface, new ve.Range( this.range.start ), this.noAutoSelect
this.surface, new ve.Range( this.getRange( true ).start ), this.noAutoSelect
);
};
@ -190,14 +190,14 @@ ve.dm.SurfaceFragment.prototype.trimRange = function () {
return this;
}
// If range is only whitespace
if ( this.document.getText( this.range ).trim().length === 0 ) {
if ( this.document.getText( this.getRange() ).trim().length === 0 ) {
// Collapse range
return new ve.dm.SurfaceFragment(
this.surface, new ve.Range( this.range.start ), this.noAutoSelect
this.surface, new ve.Range( this.getRange( true ).start ), this.noAutoSelect
);
}
return new ve.dm.SurfaceFragment(
this.surface, this.document.data.trimOuterSpaceFromRange( this.range ), this.noAutoSelect
this.surface, this.document.data.trimOuterSpaceFromRange( this.getRange() ), this.noAutoSelect
);
};
@ -223,30 +223,30 @@ ve.dm.SurfaceFragment.prototype.expandRange = function ( scope, type ) {
var range, node, nodes, parent;
switch ( scope || 'parent' ) {
case 'word':
if ( this.range.getLength() > 0 ) {
if ( this.getRange( true ).getLength() > 0 ) {
range = ve.Range.newCoveringRange( [
this.document.data.getNearestWordRange( this.range.start ),
this.document.data.getNearestWordRange( this.range.end )
this.document.data.getNearestWordRange( this.getRange( true ).start ),
this.document.data.getNearestWordRange( this.getRange( true ).end )
] );
if ( this.range.isBackwards() ) {
if ( this.getRange( true ).isBackwards() ) {
range = range.flip();
}
} else {
// optimisation for zero-length ranges
range = this.document.data.getNearestWordRange( this.range.start );
range = this.document.data.getNearestWordRange( this.getRange( true ).start );
}
break;
case 'annotation':
range = this.document.data.getAnnotatedRangeFromSelection( this.range, type );
range = this.document.data.getAnnotatedRangeFromSelection( this.getRange(), type );
// Adjust selection if it does not contain the annotated range
if ( this.range.start > range.start || this.range.end < range.end ) {
if ( this.getRange( true ).start > range.start || this.getRange( true ).end < range.end ) {
// Maintain range direction
if ( this.range.from > this.range.to ) {
if ( this.getRange( true ).from > this.getRange( true ).to ) {
range = range.flip();
}
} else {
// Otherwise just keep the range as is
range = this.range.clone();
range = this.getRange();
}
break;
case 'root':
@ -254,7 +254,7 @@ ve.dm.SurfaceFragment.prototype.expandRange = function ( scope, type ) {
break;
case 'siblings':
// Grow range to cover all siblings
nodes = this.document.selectNodes( this.range, 'siblings' );
nodes = this.document.selectNodes( this.getRange(), 'siblings' );
if ( nodes.length === 1 ) {
range = nodes[0].node.getOuterRange();
} else {
@ -266,7 +266,7 @@ ve.dm.SurfaceFragment.prototype.expandRange = function ( scope, type ) {
break;
case 'closest':
// Grow range to cover closest common ancestor node of given type
node = this.document.selectNodes( this.range, 'siblings' )[0].node;
node = this.document.selectNodes( this.getRange(), 'siblings' )[0].node;
parent = node.getParent();
while ( parent && parent.getType() !== type ) {
node = parent;
@ -279,7 +279,7 @@ ve.dm.SurfaceFragment.prototype.expandRange = function ( scope, type ) {
break;
case 'parent':
// Grow range to cover the closest common parent node
node = this.document.selectNodes( this.range, 'siblings' )[0].node;
node = this.document.selectNodes( this.getRange(), 'siblings' )[0].node;
parent = node.getParent();
if ( !parent ) {
return new ve.dm.SurfaceFragment( null );
@ -314,7 +314,7 @@ ve.dm.SurfaceFragment.prototype.getData = function ( deep ) {
if ( !this.surface ) {
return [];
}
return this.document.getData( this.range, deep );
return this.document.getData( this.getRange(), deep );
};
/**
@ -330,7 +330,7 @@ ve.dm.SurfaceFragment.prototype.getText = function () {
}
var i, length,
text = '',
data = this.document.getData( this.range );
data = this.document.getData( this.getRange() );
for ( i = 0, length = data.length; i < length; i++ ) {
if ( data[i].type === undefined ) {
// Annotated characters have a string at index 0, plain characters are 1-char strings
@ -355,8 +355,8 @@ ve.dm.SurfaceFragment.prototype.getAnnotations = function ( all ) {
if ( !this.surface ) {
return new ve.dm.AnnotationSet( this.getDocument().getStore() );
}
if ( this.range.getLength() ) {
return this.getDocument().data.getAnnotationsFromRange( this.range, all );
if ( this.getRange( true ).getLength() ) {
return this.getDocument().data.getAnnotationsFromRange( this.getRange(), all );
} else {
return this.surface.getInsertionAnnotations();
}
@ -375,7 +375,7 @@ ve.dm.SurfaceFragment.prototype.getLeafNodes = function () {
if ( !this.surface ) {
return [];
}
return this.document.selectNodes( this.range, 'leaves' );
return this.document.selectNodes( this.getRange(), 'leaves' );
};
/**
@ -395,7 +395,7 @@ ve.dm.SurfaceFragment.prototype.getCoveredNodes = function () {
if ( !this.surface ) {
return [];
}
return this.document.selectNodes( this.range, 'coveredNodes' );
return this.document.selectNodes( this.getRange(), 'coveredNodes' );
};
/**
@ -413,7 +413,7 @@ ve.dm.SurfaceFragment.prototype.getSiblingNodes = function () {
if ( !this.surface ) {
return [];
}
return this.document.selectNodes( this.range, 'siblings' );
return this.document.selectNodes( this.getRange(), 'siblings' );
};
/**
@ -439,7 +439,7 @@ ve.dm.SurfaceFragment.prototype.select = function () {
if ( !this.surface ) {
return this;
}
this.surface.change( null, this.range );
this.surface.change( null, this.getRange() );
return this;
};
@ -469,10 +469,10 @@ ve.dm.SurfaceFragment.prototype.annotateContent = function ( method, nameOrAnnot
} else {
annotation = ve.dm.annotationFactory.create( nameOrAnnotation, data );
}
if ( this.range.getLength() ) {
if ( this.getRange( true ).getLength() ) {
// Apply to selection
tx = ve.dm.Transaction.newFromAnnotation( this.document, this.range, method, annotation );
this.surface.change( tx, !this.noAutoSelect && tx.translateRange( this.range ) );
tx = ve.dm.Transaction.newFromAnnotation( this.document, this.getRange(), method, annotation );
this.surface.change( tx, !this.noAutoSelect && tx.translateRange( this.getRange() ) );
} else {
// Apply annotation to stack
if ( method === 'set' ) {
@ -500,7 +500,7 @@ ve.dm.SurfaceFragment.prototype.insertContent = function ( content, annotate ) {
return this;
}
var tx, annotations;
if ( this.range.getLength() ) {
if ( this.getRange( true ).getLength() ) {
this.removeContent();
}
// Auto-convert content to array of plain text characters
@ -509,13 +509,13 @@ ve.dm.SurfaceFragment.prototype.insertContent = function ( content, annotate ) {
}
if ( content.length ) {
if ( annotate ) {
annotations = this.document.data.getAnnotationsFromOffset( this.range.start - 1 );
annotations = this.document.data.getAnnotationsFromOffset( this.getRange( true ).start - 1 );
if ( annotations.getLength() > 0 ) {
ve.dm.Document.addAnnotationsToData( content, annotations );
}
}
tx = ve.dm.Transaction.newFromInsertion( this.document, this.range.start, content );
this.surface.change( tx, !this.noAutoSelect && tx.translateRange( this.range ) );
tx = ve.dm.Transaction.newFromInsertion( this.document, this.getRange( true ).start, content );
this.surface.change( tx, !this.noAutoSelect && tx.translateRange( this.getRange() ) );
}
return this;
};
@ -532,19 +532,18 @@ ve.dm.SurfaceFragment.prototype.removeContent = function () {
return this;
}
var tx;
if ( this.range.getLength() ) {
tx = ve.dm.Transaction.newFromRemoval( this.document, this.range );
// this.range will be translated via the onTransact event handler
this.surface.change( tx, !this.noAutoSelect && tx.translateRange( this.range ) );
if ( this.getRange( true ).getLength() ) {
tx = ve.dm.Transaction.newFromRemoval( this.document, this.getRange() );
this.surface.change( tx, !this.noAutoSelect && tx.translateRange( this.getRange() ) );
// Check if the range didn't get collapsed automatically - this will occur when removing
// content across un-mergable nodes because the delete only strips out content leaving
// structure at the beginning and end of the range in place
if ( this.range.getLength() ) {
if ( this.getRange( true ).getLength() ) {
// Collapse the range manually
this.range = new ve.Range( this.range.start );
this.range = new ve.Range( this.getRange( true ).start );
if ( !this.noAutoSelect ) {
// Update the surface selection
this.surface.change( null, this.range );
this.surface.change( null, this.getRange() );
}
}
}
@ -565,8 +564,8 @@ ve.dm.SurfaceFragment.prototype.convertNodes = function ( type, attr ) {
return this;
}
var tx =
ve.dm.Transaction.newFromContentBranchConversion( this.document, this.range, type, attr );
this.surface.change( tx, !this.noAutoSelect && tx.translateRange( this.range ) );
ve.dm.Transaction.newFromContentBranchConversion( this.document, this.getRange(), type, attr );
this.surface.change( tx, !this.noAutoSelect && tx.translateRange( this.getRange() ) );
return this;
};
@ -597,10 +596,10 @@ ve.dm.SurfaceFragment.prototype.wrapNodes = function ( wrapper ) {
if ( !ve.isArray( wrapper ) ) {
wrapper = [wrapper];
}
var tx = ve.dm.Transaction.newFromWrap( this.document, this.range, [], [], [], wrapper ),
newRange = new ve.Range( this.range.start, this.range.end + tx.getLengthDifference() );
var tx = ve.dm.Transaction.newFromWrap( this.document, this.getRange(), [], [], [], wrapper ),
newRange = new ve.Range( this.getRange( true ).start, this.getRange( true ).end + tx.getLengthDifference() );
this.surface.change( tx, !this.noAutoSelect && newRange );
this.range = newRange;
this.setRange( newRange );
return this;
};
@ -624,22 +623,22 @@ ve.dm.SurfaceFragment.prototype.unwrapNodes = function ( outerDepth, innerDepth
}
var i, tx, newRange, innerUnwrapper = [], outerUnwrapper = [];
if ( this.range.end - this.range.start < innerDepth * 2 ) {
if ( this.getRange( true ).end - this.getRange( true ).start < innerDepth * 2 ) {
throw new Error( 'cannot unwrap by greater depth than maximum theoretical depth of selection' );
}
for ( i = 0; i < innerDepth; i++ ) {
innerUnwrapper.push( this.surface.getDocument().data.getData( this.range.start + i ) );
innerUnwrapper.push( this.surface.getDocument().data.getData( this.getRange( true ).start + i ) );
}
for ( i = outerDepth; i > 0; i-- ) {
outerUnwrapper.push( this.surface.getDocument().data.getData( this.range.start - i ) );
outerUnwrapper.push( this.surface.getDocument().data.getData( this.getRange( true ).start - i ) );
}
tx = ve.dm.Transaction.newFromWrap( this.document, this.range, outerUnwrapper, [], innerUnwrapper, [] );
newRange = new ve.Range( this.range.start - outerDepth, this.range.end + outerDepth + tx.getLengthDifference() );
tx = ve.dm.Transaction.newFromWrap( this.document, this.getRange(), outerUnwrapper, [], innerUnwrapper, [] );
newRange = new ve.Range( this.getRange( true ).start - outerDepth, this.getRange( true ).end + outerDepth + tx.getLengthDifference() );
this.surface.change( tx, !this.noAutoSelect && newRange );
this.range = newRange;
this.setRange( newRange );
return this;
};
@ -676,19 +675,19 @@ ve.dm.SurfaceFragment.prototype.rewrapNodes = function ( depth, wrapper ) {
wrapper = [wrapper];
}
if ( this.range.end - this.range.start < depth * 2 ) {
if ( this.getRange( true ).end - this.getRange( true ).start < depth * 2 ) {
throw new Error( 'cannot unwrap by greater depth than maximum theoretical depth of selection' );
}
for ( i = 0; i < depth; i++ ) {
unwrapper.push( this.surface.getDocument().data.getData( this.range.start + i ) );
unwrapper.push( this.surface.getDocument().data.getData( this.getRange( true ).start + i ) );
}
tx = ve.dm.Transaction.newFromWrap( this.document, this.range, [], [], unwrapper, wrapper );
newRange = new ve.Range( this.range.start, this.range.end + tx.getLengthDifference() );
tx = ve.dm.Transaction.newFromWrap( this.document, this.getRange(), [], [], unwrapper, wrapper );
newRange = new ve.Range( this.getRange( true ).start, this.getRange( true ).end + tx.getLengthDifference() );
this.surface.change( tx, !this.noAutoSelect && newRange );
this.range = newRange;
this.setRange( newRange );
return this;
};
@ -724,11 +723,11 @@ ve.dm.SurfaceFragment.prototype.wrapAllNodes = function ( wrapper ) {
wrapper = [wrapper];
}
tx = ve.dm.Transaction.newFromWrap( this.document, this.range, [], wrapper, [], [] );
newRange = new ve.Range( this.range.start, this.range.end + tx.getLengthDifference() );
tx = ve.dm.Transaction.newFromWrap( this.document, this.getRange(), [], wrapper, [], [] );
newRange = new ve.Range( this.getRange( true ).start, this.getRange( true ).end + tx.getLengthDifference() );
this.surface.change( tx, !this.noAutoSelect && newRange );
this.range = newRange;
this.setRange( newRange );
return this;
};
@ -757,25 +756,25 @@ ve.dm.SurfaceFragment.prototype.rewrapAllNodes = function ( depth, wrapper ) {
return this;
}
var i, tx, newRange, unwrapper = [],
innerRange = new ve.Range( this.range.start + depth, this.range.end - depth );
innerRange = new ve.Range( this.getRange( true ).start + depth, this.getRange( true ).end - depth );
if ( !ve.isArray( wrapper ) ) {
wrapper = [wrapper];
}
if ( this.range.end - this.range.start < depth * 2 ) {
if ( this.getRange( true ).end - this.getRange( true ).start < depth * 2 ) {
throw new Error( 'cannot unwrap by greater depth than maximum theoretical depth of selection' );
}
for ( i = 0; i < depth; i++ ) {
unwrapper.push( this.surface.getDocument().data.getData( this.range.start + i ) );
unwrapper.push( this.surface.getDocument().data.getData( this.getRange( true ).start + i ) );
}
tx = ve.dm.Transaction.newFromWrap( this.document, innerRange, unwrapper, wrapper, [], [] );
newRange = new ve.Range( this.range.start, this.range.end + tx.getLengthDifference() );
newRange = new ve.Range( this.getRange( true ).start, this.getRange( true ).end + tx.getLengthDifference() );
this.surface.change( tx, !this.noAutoSelect && newRange );
this.range = newRange;
this.setRange( newRange );
return this;
};
@ -807,7 +806,7 @@ ve.dm.SurfaceFragment.prototype.isolateAndUnwrap = function ( isolateForType ) {
startSplitNodes = [],
endSplitNodes = [],
fragment = this,
newRange = new ve.Range( this.range.start, this.range.end );
newRange = new ve.Range( this.getRange( true ).start, this.getRange( true ).end );
function createSplits( splitNodes, insertBefore ) {
var i, length,
@ -830,7 +829,7 @@ ve.dm.SurfaceFragment.prototype.isolateAndUnwrap = function ( isolateForType ) {
endOffset += endOffsetChange;
}
nodes = this.getDocument().selectNodes( this.range, 'siblings' );
nodes = this.getDocument().selectNodes( this.getRange(), 'siblings' );
// Find start split point, if required
startSplitNode = nodes[0].node;

View file

@ -9,67 +9,117 @@ QUnit.module( 've.FormatAction' );
/* Tests */
function runConverterTest( assert, range, type, attributes, expectedSelection, expectedData, label ) {
var dom = ve.createDocumentFromHTML( ve.dm.example.isolationHTML ),
function runFormatConverterTest( assert, range, type, attributes, expectedSelection, expectedData, msg ) {
var selection,
dom = ve.createDocumentFromHTML( ve.dm.example.isolationHTML ),
surface = new ve.Surface( new ve.init.Target( $( '<div>' ) ), dom ),
formatAction = new ve.FormatAction( surface ),
data = ve.copyArray( surface.getModel().getDocument().getFullData() );
data = ve.copyArray( surface.getModel().getDocument().getFullData() ),
originalData = ve.copyArray( data );
expectedData( data );
surface.getModel().change( null, range );
formatAction.convert( type, attributes );
expectedData( data );
assert.deepEqual( surface.getModel().getDocument().getFullData(), data, msg + ': data models match' );
assert.deepEqual( surface.getModel().getSelection(), expectedSelection, msg + ': selections match' );
assert.deepEqual( surface.getModel().getDocument().getFullData(), data, label + ': data models match' );
assert.deepEqual( surface.getModel().getSelection(), expectedSelection, label + ': selections match' );
selection = surface.getModel().undo();
assert.deepEqual( surface.getModel().getDocument().getFullData(), originalData, msg + ' (undo): data models match' );
assert.deepEqual( selection, range, msg + ' (undo): selections match' );
surface.destroy();
}
QUnit.test( 'convert', 12, function ( assert ) {
var rebuilt = { 'changed': { 'rebuilt': 1 } },
QUnit.test( 'convert', function ( assert ) {
var i,
rebuilt = { 'changed': { 'rebuilt': 1 } },
created = { 'changed': { 'created': 1 } },
createdAndRebuilt = { 'changed': { 'created': 1, 'rebuilt': 1 } };
runConverterTest( assert, new ve.Range( 14, 16 ), 'MWheading', { level: 2 }, new ve.Range( 14, 16 ), function( data ) {
data[0].internal = rebuilt;
data.splice( 11, 2, { 'type': '/list' }, { 'type': 'MWheading', 'attributes': { 'level': 2 }, 'internal': created } );
data.splice( 19, 2, { 'type': '/MWheading' }, { 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': createdAndRebuilt } );
}, 'converting partial selection of list item "Item 2" to level 2 MWheading' );
runConverterTest( assert, new ve.Range( 15, 50 ), 'MWheading', { level: 3 }, new ve.Range( 15, 44 ), function( data ) {
data[0].internal = rebuilt;
data.splice( 11, 2, { 'type': '/list' }, { 'type': 'MWheading', 'attributes': { 'level': 3 }, 'internal': created } );
data.splice( 19, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 3 }, 'internal': created } );
data.splice( 27, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 3 }, 'internal': created } );
data.splice( 38, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 3 }, 'internal': created } );
data.splice( 46, 2, { 'type': '/MWheading' }, { 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': created } );
}, 'converting partial selection across two lists surrounding a paragraph' );
runConverterTest( assert, new ve.Range( 4, 28 ), 'MWheading', { level: 1 }, new ve.Range( 2, 22 ), function( data ) {
data[0].internal = rebuilt;
data.splice( 0, 3, { 'type': 'MWheading', 'attributes': { 'level': 1 }, 'internal': created } );
data.splice( 7, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 1 }, 'internal': created } );
data.splice( 15, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 1 }, 'internal': created } );
data.splice( 23, 3, { 'type': '/MWheading' } );
}, 'converting partial selection of all list items to level 1 MWheadings' );
runConverterTest( assert, new ve.Range( 5, 26 ), 'MWpreformatted', undefined, new ve.Range( 3, 20 ), function( data ) {
data[0].internal = rebuilt;
data.splice( 0, 3, { 'type': 'MWpreformatted', 'internal': created } );
data.splice( 7, 4, { 'type': '/MWpreformatted' }, { 'type': 'MWpreformatted', 'internal': created } );
data.splice( 15, 4, { 'type': '/MWpreformatted' }, { 'type': 'MWpreformatted', 'internal': created } );
data.splice( 23, 3, { 'type': '/MWpreformatted' } );
}, 'converting partial selection of some list items to MWpreformatted text' );
runConverterTest( assert, new ve.Range( 146, 159 ), 'paragraph', undefined, new ve.Range( 146, 159 ), function( data ) {
data.splice( 145, 1, { 'type': 'paragraph', 'internal': created } );
data.splice( 159, 1, { 'type': '/paragraph' } );
}, 'converting heading in list item to paragraph' );
runConverterTest( assert, new ve.Range( 165, 180 ), 'paragraph', undefined, new ve.Range( 165, 180 ), function( data ) {
data.splice( 162, 1, { 'type': 'paragraph', 'internal': created } );
data.splice( 183, 1, { 'type': '/paragraph' } );
}, 'converting MWpreformatted in list item to paragraph' );
createdAndRebuilt = { 'changed': { 'created': 1, 'rebuilt': 1 } },
cases = [
{
'range': new ve.Range( 14, 16 ),
'type': 'MWheading',
'attributes': { level: 2 },
'expectedSelection': new ve.Range( 14, 16 ),
'expectedData': function( data ) {
data[0].internal = rebuilt;
data.splice( 11, 2, { 'type': '/list' }, { 'type': 'MWheading', 'attributes': { 'level': 2 }, 'internal': created } );
data.splice( 19, 2, { 'type': '/MWheading' }, { 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': createdAndRebuilt } );
},
'msg': 'converting partial selection of list item "Item 2" to level 2 MWheading'
},
{
'range': new ve.Range( 15, 50 ),
'type': 'MWheading',
'attributes': { level: 3 },
'expectedSelection': new ve.Range( 15, 44 ),
'expectedData': function( data ) {
data[0].internal = rebuilt;
data.splice( 11, 2, { 'type': '/list' }, { 'type': 'MWheading', 'attributes': { 'level': 3 }, 'internal': created } );
data.splice( 19, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 3 }, 'internal': created } );
data.splice( 27, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 3 }, 'internal': created } );
data.splice( 38, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 3 }, 'internal': created } );
data.splice( 46, 2, { 'type': '/MWheading' }, { 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': created } );
},
'msg': 'converting partial selection across two lists surrounding a paragraph'
},
{
'range': new ve.Range( 4, 28 ),
'type': 'MWheading',
'attributes': { level: 1 },
'expectedSelection': new ve.Range( 2, 22 ),
'expectedData': function( data ) {
data[0].internal = rebuilt;
data.splice( 0, 3, { 'type': 'MWheading', 'attributes': { 'level': 1 }, 'internal': created } );
data.splice( 7, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 1 }, 'internal': created } );
data.splice( 15, 4, { 'type': '/MWheading' }, { 'type': 'MWheading', 'attributes': { 'level': 1 }, 'internal': created } );
data.splice( 23, 3, { 'type': '/MWheading' } );
},
'msg': 'converting partial selection of all list items to level 1 MWheadings'
},
{
'range': new ve.Range( 5, 26 ),
'type': 'MWpreformatted',
'attributes': undefined,
'expectedSelection': new ve.Range( 3, 20 ),
'expectedData': function( data ) {
data[0].internal = rebuilt;
data.splice( 0, 3, { 'type': 'MWpreformatted', 'internal': created } );
data.splice( 7, 4, { 'type': '/MWpreformatted' }, { 'type': 'MWpreformatted', 'internal': created } );
data.splice( 15, 4, { 'type': '/MWpreformatted' }, { 'type': 'MWpreformatted', 'internal': created } );
data.splice( 23, 3, { 'type': '/MWpreformatted' } );
},
'msg': 'converting partial selection of some list items to MWpreformatted text'
},
{
'range': new ve.Range( 146, 159 ),
'type': 'paragraph',
'attributes': undefined,
'expectedSelection': new ve.Range( 146, 159 ),
'expectedData': function( data ) {
data.splice( 145, 1, { 'type': 'paragraph', 'internal': created } );
data.splice( 159, 1, { 'type': '/paragraph' } );
},
'msg': 'converting heading in list item to paragraph'
},
{
'range': new ve.Range( 165, 180 ),
'type': 'paragraph',
'attributes': undefined,
'expectedSelection': new ve.Range( 165, 180 ),
'expectedData': function( data ) {
data.splice( 162, 1, { 'type': 'paragraph', 'internal': created } );
data.splice( 183, 1, { 'type': '/paragraph' } );
},
'msg': 'converting MWpreformatted in list item to paragraph'
}
];
QUnit.expect( cases.length * 4 );
for ( i = 0; i < cases.length; i++ ) {
runFormatConverterTest( assert, cases[i].range, cases[i].type, cases[i].attributes, cases[i].expectedSelection, cases[i].expectedData, cases[i].msg );
}
} );

View file

@ -9,30 +9,57 @@ QUnit.module( 've.IndentationAction' );
/* Tests */
function runIndentationTest( assert, range, method, expectedSelection, expectedData, label ) {
var dom = ve.createDocumentFromHTML( ve.dm.example.isolationHTML ),
function runIndentationChangeTest( assert, range, method, expectedSelection, expectedData, expectedOriginalData, msg ) {
var selection,
dom = ve.createDocumentFromHTML( ve.dm.example.isolationHTML ),
surface = new ve.Surface( new ve.init.Target( $( '<div>' ) ), dom ),
indentationAction = new ve.IndentationAction( surface ),
data = ve.copyArray( surface.getModel().getDocument().getFullData() );
data = ve.copyArray( surface.getModel().getDocument().getFullData() ),
originalData = ve.copyArray( data );
expectedData( data );
if ( expectedOriginalData ) {
expectedOriginalData( originalData );
}
surface.getModel().change( null, range );
indentationAction[method]();
expectedData( data );
assert.deepEqual( surface.getModel().getDocument().getFullData(), data, msg + ': data models match' );
assert.deepEqual( surface.getModel().getSelection(), expectedSelection, msg + ': selections match' );
assert.deepEqual( surface.getModel().getDocument().getFullData(), data, label + ': data models match' );
assert.deepEqual( surface.getModel().getSelection(), expectedSelection, label + ': selections match' );
selection = surface.getModel().undo();
assert.deepEqual( surface.getModel().getDocument().getFullData(), originalData, msg + ' (undo): data models match' );
assert.deepEqual( selection, range, msg + ' (undo): selections match' );
surface.destroy();
}
QUnit.test( 'decrease', 2, function ( assert ) {
var rebuilt = { 'changed': { 'rebuilt': 1 } },
createdAndRebuilt = { 'changed': { 'created': 2, 'rebuilt': 1 } };
var i,
rebuilt = { 'changed': { 'rebuilt': 1 } },
createdAndRebuilt = { 'changed': { 'created': 2, 'rebuilt': 1 } },
cases = [
{
'range': new ve.Range( 14, 16 ),
'method': 'decrease',
'expectedSelection': new ve.Range( 14, 16 ),
'expectedData': function( data ) {
data[0].internal = rebuilt;
data.splice( 11, 2, { 'type': '/list' }, { 'type': 'paragraph' } );
data.splice( 19, 2, { 'type': '/paragraph' }, { 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': createdAndRebuilt } );
},
'expectedOriginalData': function( data ) {
// generated: 'wrapper' is removed by the action and not restored by undo
delete data[12].internal;
},
'msg': 'decrease indentation on partial selection of list item "Item 2"'
}
];
runIndentationTest( assert, new ve.Range( 14, 16 ), 'decrease', new ve.Range( 14, 16 ), function( data ) {
data[0].internal = rebuilt;
data.splice( 11, 2, { 'type': '/list' }, { 'type': 'paragraph' } );
data.splice( 19, 2, { 'type': '/paragraph' }, { 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': createdAndRebuilt } );
}, 'decrease indentation on partial selection of list item "Item 2"' );
QUnit.expect( cases.length * 4 );
for ( i = 0; i < cases.length; i++ ) {
runIndentationChangeTest( assert, cases[i].range, cases[i].method, cases[i].expectedSelection, cases[i].expectedData, cases[i].expectedOriginalData, cases[i].msg );
}
} );

View file

@ -24,23 +24,26 @@ QUnit.test( 'constructor', 8, function ( assert ) {
assert.equal( fragment.getRange().from, 0, 'range is clamped between 0 and document length' );
assert.equal( fragment.getRange().to, 61, 'range is clamped between 0 and document length' );
assert.strictEqual( fragment.willAutoSelect(), false, 'noAutoSelect values are boolean' );
fragment.destroy();
} );
QUnit.test( 'onTransact', 1, function ( assert ) {
QUnit.test( 'update', 2, function ( assert ) {
var doc = ve.dm.example.createExampleDocument(),
surface = new ve.dm.Surface( doc ),
fragment1 = new ve.dm.SurfaceFragment( surface, new ve.Range( 1, 56 ) ),
fragment2 = new ve.dm.SurfaceFragment( surface, new ve.Range( 2, 4 ) );
fragment2 = new ve.dm.SurfaceFragment( surface, new ve.Range( 2, 4 ) ),
fragment3 = new ve.dm.SurfaceFragment( surface, new ve.Range( 2, 4 ) );
fragment1.removeContent();
assert.deepEqual(
fragment2.getRange(),
new ve.Range( 1, 1 ),
'fragment ranges are auto-translated when transactions are processed'
'fragment range collapses after removeContent'
);
surface.undo();
assert.deepEqual(
fragment3.getRange(),
new ve.Range( 4, 4 ),
'fragment range moved after undo'
);
fragment1.destroy();
fragment2.destroy();
} );
QUnit.test( 'adjustRange', 3, function ( assert ) {
@ -51,7 +54,6 @@ QUnit.test( 'adjustRange', 3, function ( assert ) {
assert.ok( fragment !== adjustedFragment, 'adjustRange produces a new fragment' );
assert.deepEqual( fragment.getRange(), new ve.Range( 20, 21 ), 'old fragment is not changed' );
assert.deepEqual( adjustedFragment.getRange(), new ve.Range( 1, 56 ), 'new range is used' );
fragment.destroy();
} );
QUnit.test( 'collapseRange', 3, function ( assert ) {
@ -62,8 +64,6 @@ QUnit.test( 'collapseRange', 3, function ( assert ) {
assert.ok( fragment !== collapsedFragment, 'collapseRange produces a new fragment' );
assert.deepEqual( fragment.getRange(), new ve.Range( 20, 21 ), 'old fragment is not changed' );
assert.deepEqual( collapsedFragment.getRange(), new ve.Range( 20, 20 ), 'new range is used' );
collapsedFragment.destroy();
fragment.destroy();
} );
QUnit.test( 'expandRange (closest)', 1, function ( assert ) {
@ -76,8 +76,6 @@ QUnit.test( 'expandRange (closest)', 1, function ( assert ) {
true,
'closest with invalid type results in null fragment'
);
exapandedFragment.destroy();
fragment.destroy();
} );
QUnit.test( 'expandRange (word)', 1, function ( assert ) {
@ -111,8 +109,6 @@ QUnit.test( 'expandRange (word)', 1, function ( assert ) {
word = cases[i].phrase.substring( range.start, range.end );
assert.strictEqual( word, cases[i].expected, cases[i].msg + ': text' );
assert.strictEqual( cases[i].range.isBackwards(), range.isBackwards(), cases[i].msg + ': range direction' );
fragment.destroy();
newFragment.destroy();
}
} );
@ -135,7 +131,6 @@ QUnit.test( 'removeContent', 2, function ( assert ) {
new ve.Range( 1, 1 ),
'removing content results in a zero-length fragment'
);
fragment.destroy();
} );
QUnit.test( 'insertContent', 3, function ( assert ) {
@ -159,7 +154,6 @@ QUnit.test( 'insertContent', 3, function ( assert ) {
['3', '2', '1'],
'strings get converted into data when inserting content'
);
fragment.destroy();
} );
QUnit.test( 'wrapNodes/unwrapNodes', 10, function ( assert ) {
@ -205,7 +199,6 @@ QUnit.test( 'wrapNodes/unwrapNodes', 10, function ( assert ) {
fragment.unwrapNodes( 0, 2 );
assert.deepEqual( doc.getData(), originalDoc.getData(), 'unwrapping 2 levels restores document to original state' );
assert.deepEqual( fragment.getRange(), new ve.Range( 55, 61 ), 'range after unwrapping is same as original range' );
fragment.destroy();
// Make a 1 paragraph into 1 list with 1 item
fragment = new ve.dm.SurfaceFragment( surface, new ve.Range( 9, 12 ) );
@ -234,13 +227,11 @@ QUnit.test( 'wrapNodes/unwrapNodes', 10, function ( assert ) {
fragment.unwrapNodes( 0, 2 );
assert.deepEqual( doc.getData(), originalDoc.getData(), 'unwrapping 2 levels restores document to original state' );
assert.deepEqual( fragment.getRange(), new ve.Range( 9, 12 ), 'range after unwrapping is same as original range' );
fragment.destroy();
fragment = new ve.dm.SurfaceFragment( surface, new ve.Range( 8, 34 ) );
fragment.unwrapNodes( 3, 1 );
assert.deepEqual( fragment.getData(), doc.getData( new ve.Range( 5, 29 ) ), 'unwrapping multiple outer nodes and an inner node' );
assert.deepEqual( fragment.getRange(), new ve.Range( 5, 29 ), 'new range contains inner elements' );
fragment.destroy();
} );
QUnit.test( 'rewrapNodes', 4, function ( assert ) {
@ -284,7 +275,6 @@ QUnit.test( 'rewrapNodes', 4, function ( assert ) {
// The intermediate stage (plain text attached to the document) would be invalid
// if performed as an unwrap and a wrap
expectedData = ve.copyArray( doc.getData() );
fragment.destroy();
fragment = new ve.dm.SurfaceFragment( surface, new ve.Range( 59, 65 ) );
fragment.rewrapNodes( 1, [ { 'type': 'heading', 'attributes': { 'level': 1 } } ] );
@ -296,7 +286,6 @@ QUnit.test( 'rewrapNodes', 4, function ( assert ) {
assert.deepEqual( doc.getData(), expectedData, 'rewrapping paragraphs as headings' );
assert.deepEqual( fragment.getRange(), new ve.Range( 59, 65 ), 'new range contains rewrapping elements' );
fragment.destroy();
} );
QUnit.test( 'wrapAllNodes', 10, function ( assert ) {
@ -335,7 +324,6 @@ QUnit.test( 'wrapAllNodes', 10, function ( assert ) {
fragment.unwrapNodes( 0, 2 );
assert.deepEqual( doc.getData(), originalDoc.getData(), 'unwrapping 2 levels restores document to original state' );
assert.deepEqual( fragment.getRange(), new ve.Range( 55, 61 ), 'range after unwrapping is same as original range' );
fragment.destroy();
// Make a 1 paragraph into 1 list with 1 item
fragment = new ve.dm.SurfaceFragment( surface, new ve.Range( 9, 12 ) );
@ -364,7 +352,6 @@ QUnit.test( 'wrapAllNodes', 10, function ( assert ) {
fragment.unwrapNodes( 0, 2 );
assert.deepEqual( doc.getData(), originalDoc.getData(), 'unwrapping 2 levels restores document to original state' );
assert.deepEqual( fragment.getRange(), new ve.Range( 9, 12 ), 'range after unwrapping is same as original range' );
fragment.destroy();
fragment = new ve.dm.SurfaceFragment( surface, new ve.Range( 5, 37 ) );
@ -380,7 +367,6 @@ QUnit.test( 'wrapAllNodes', 10, function ( assert ) {
expectedData,
'unwrapping 4 levels (table, tableSection, tableRow and tableCell)'
);
fragment.destroy();
} );
QUnit.test( 'rewrapAllNodes', 6, function ( assert ) {
@ -410,7 +396,6 @@ QUnit.test( 'rewrapAllNodes', 6, function ( assert ) {
'rewrapping multiple nodes via a valid intermediate state produces the same document as unwrapping then wrapping'
);
assert.deepEqual( fragment.getRange(), expectedFragment.getRange(), 'new range contains rewrapping elements' );
expectedFragment.destroy();
// Reverse of first test
fragment.rewrapAllNodes(
@ -434,7 +419,6 @@ QUnit.test( 'rewrapAllNodes', 6, function ( assert ) {
'rewrapping multiple nodes via a valid intermediate state produces the same document as unwrapping then wrapping'
);
assert.deepEqual( fragment.getRange(), new ve.Range( 5, 37 ), 'new range contains rewrapping elements' );
fragment.destroy();
// Rewrap a heading as a paragraph
// The intermediate stage (plain text attached to the document) would be invalid
@ -447,7 +431,6 @@ QUnit.test( 'rewrapAllNodes', 6, function ( assert ) {
assert.deepEqual( doc.getData(), expectedData, 'rewrapping a heading as a paragraph' );
assert.deepEqual( fragment.getRange(), new ve.Range( 0, 5 ), 'new range contains rewrapping elements' );
fragment.destroy();
} );
function runIsolateTest( assert, type, range, expected, label ) {
@ -461,7 +444,6 @@ function runIsolateTest( assert, type, range, expected, label ) {
expected( data );
assert.deepEqual( doc.getFullData(), data, label );
fragment.destroy();
}
QUnit.test( 'isolateAndUnwrap', 4, function ( assert ) {

View file

@ -2288,7 +2288,7 @@ ve.dm.example.isolationData = [
{ 'type': '/tableSection' },
{ 'type': '/table' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'N', 'o', 't', ' ', 'a', 'l', 'l', 'o', 'w', 'e', 'd', ' ', 'b', 'y', ' ', 'd', 'm', ': ',
'N', 'o', 't', ' ', 'a', 'l', 'l', 'o', 'w', 'e', 'd', ' ', 'b', 'y', ' ', 'd', 'm', ':',
{ 'type': '/paragraph' },
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },

View file

@ -81,18 +81,15 @@ ve.ui.LinkInspector.prototype.onSetup = function () {
if ( fragment.getRange().isCollapsed() ) {
// Expand to nearest word
expandedFragment = fragment.expandRange( 'word' );
fragment.destroy();
fragment = expandedFragment;
} else {
// Trim whitespace
trimmedFragment = fragment.trimRange();
fragment.destroy();
fragment = trimmedFragment;
}
if ( !fragment.getRange().isCollapsed() ) {
// Create annotation from selection
truncatedFragment = fragment.truncateRange( 255 );
fragment.destroy();
fragment = truncatedFragment;
annotation = this.getAnnotationFromTarget( fragment.getText() );
fragment.annotateContent( 'set', annotation );
@ -101,12 +98,11 @@ ve.ui.LinkInspector.prototype.onSetup = function () {
} else {
// Expand range to cover annotation
expandedFragment = fragment.expandRange( 'annotation', annotation );
fragment.destroy();
fragment = expandedFragment;
}
// Update selection
fragment.select().destroy();
fragment.select();
};
/**
@ -128,8 +124,6 @@ ve.ui.LinkInspector.prototype.onOpen = function () {
this.targetInput.setAnnotation( annotation );
this.targetInput.$input.focus().select();
}, this ), 200 );
fragment.destroy();
};
/**
@ -174,7 +168,6 @@ ve.ui.LinkInspector.prototype.onClose = function ( action ) {
// Insert default text and select it
fragment.insertContent( target, false );
adjustedFragment = fragment.adjustRange( -target.length, 0 );
fragment.destroy();
fragment = adjustedFragment;
// Move cursor to the end of the inserted content
@ -204,8 +197,6 @@ ve.ui.LinkInspector.prototype.onClose = function ( action ) {
);
// Reset state
this.isNewAnnotation = false;
fragment.destroy();
};
/**

View file

@ -238,8 +238,6 @@ ve.ui.Context.prototype.update = function () {
// Remember selection for next time
this.selection = selection.clone();
fragment.destroy();
return this;
};

View file

@ -79,7 +79,6 @@ ve.ui.Toolbar.prototype.onContextChange = function () {
}
}
this.emit( 'updateState', nodes, fragment.getAnnotations(), fragment.getAnnotations( true ) );
fragment.destroy();
};
/**

View file

@ -170,7 +170,7 @@ ve.Surface.prototype.isEnabled = function () {
* @method
*/
ve.Surface.prototype.resetSelection = function () {
this.model.getFragment().select().destroy();
this.model.getFragment().select();
this.view.surfaceObserver.poll();
};