Esanders has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/304970

Change subject: [BREAKING CHANGE] Move originalDomElements to the IV store
......................................................................

[BREAKING CHANGE] Move originalDomElements to the IV store

Doing so ensures that the linear model contains only data that
can be quickly serialized with JSON.stringify.

Bug: T142943

Change-Id: I8a71c1a40ec35108d0a9a388da6f75632f8dc53c
---
M src/ce/nodes/ve.ce.AlienNode.js
M src/ce/ve.ce.ContentBranchNode.js
M src/ce/ve.ce.Surface.js
M src/ce/ve.ce.TextState.js
M src/ce/ve.ce.View.js
M src/dm/lineardata/ve.dm.ElementLinearData.js
M src/dm/metaitems/ve.dm.AlienMetaItem.js
M src/dm/nodes/ve.dm.AlienNode.js
M src/dm/ve.dm.Annotation.js
M src/dm/ve.dm.Converter.js
M src/dm/ve.dm.IndexValueStore.js
M src/dm/ve.dm.Model.js
M src/dm/ve.dm.ModelFactory.js
M src/dm/ve.dm.Node.js
M tests/ce/ve.ce.Surface.test.js
M tests/ce/ve.ce.TextState.test.js
M tests/dm/lineardata/ve.dm.ElementLinearData.test.js
M tests/dm/ve.dm.Annotation.test.js
M tests/dm/ve.dm.Document.test.js
M tests/dm/ve.dm.Transaction.test.js
M tests/dm/ve.dm.TransactionProcessor.test.js
M tests/dm/ve.dm.example.js
M tests/ve.qunit.js
M tests/ve.test.utils.js
24 files changed, 228 insertions(+), 173 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor 
refs/changes/70/304970/1

diff --git a/src/ce/nodes/ve.ce.AlienNode.js b/src/ce/nodes/ve.ce.AlienNode.js
index cd7ec3d..ddb0079 100644
--- a/src/ce/nodes/ve.ce.AlienNode.js
+++ b/src/ce/nodes/ve.ce.AlienNode.js
@@ -22,7 +22,7 @@
        ve.ce.AlienNode.super.apply( this, arguments );
 
        // DOM changes
-       this.$element = $( ve.copyDomElements( 
this.model.getOriginalDomElements(), document ) );
+       this.$element = $( ve.copyDomElements( 
this.model.getOriginalDomElements( this.model.getDocument().getStore() ), 
document ) );
 
        // Mixin constructors
        ve.ce.FocusableNode.call( this, this.$element, {
diff --git a/src/ce/ve.ce.ContentBranchNode.js 
b/src/ce/ve.ce.ContentBranchNode.js
index bb52fc1..cb9bdbf 100644
--- a/src/ce/ve.ce.ContentBranchNode.js
+++ b/src/ce/ve.ce.ContentBranchNode.js
@@ -206,6 +206,7 @@
                        buffer = '';
                }
                // Create a new DOM node and descend into it
+               annotation.doc = node.model.doc;
                ann = ve.ce.annotationFactory.create( annotation.getType(), 
annotation, node );
                ann.appendTo( current );
                annotationStack.push( ann );
diff --git a/src/ce/ve.ce.Surface.js b/src/ce/ve.ce.Surface.js
index 3b960ca..d727c10 100644
--- a/src/ce/ve.ce.Surface.js
+++ b/src/ce/ve.ce.Surface.js
@@ -1807,7 +1807,7 @@
                contextElement = node.getClonedElement();
                // Make sure that context doesn't have any attributes that 
might confuse
                // the importantElement check in afterPaste.
-               $( contextElement.originalDomElements ).removeAttr( 'id typeof 
rel' );
+               $( documentModel.getStore().value( 
contextElement.originalDomElementsIndex ) ).removeAttr( 'id typeof rel' );
                context = [ contextElement ];
                // If there is content to the left of the cursor, put a 
placeholder
                // character to the left of the cursor
diff --git a/src/ce/ve.ce.TextState.js b/src/ce/ve.ce.TextState.js
index 5aa464b..5651d4f 100644
--- a/src/ce/ve.ce.TextState.js
+++ b/src/ce/ve.ce.TextState.js
@@ -398,13 +398,13 @@
                                        modelClass = ve.dm.modelRegistry.lookup(
                                                
ve.dm.modelRegistry.matchElement( element )
                                        );
-                                       ann = 
ve.dm.annotationFactory.createFromElement(
-                                               
ve.dm.converter.createDataElements( modelClass, [ element ] )[ 0 ]
-                                       );
-                                       if ( !( ann instanceof ve.dm.Annotation 
) ) {
+                                       if ( !( modelClass.prototype instanceof 
ve.dm.Annotation ) ) {
                                                // Erroneous element; nothing 
we can do with it
                                                continue;
                                        }
+                                       ann = 
ve.dm.annotationFactory.createFromElement(
+                                               
modelClass.static.toDataElement( [ element ], ve.dm.converter )
+                                       );
                                        oldAnn = oldAnnotations.getComparable( 
ann );
                                        if ( oldAnn ) {
                                                ann = oldAnn;
diff --git a/src/ce/ve.ce.View.js b/src/ce/ve.ce.View.js
index 0d7eaa9..6df7007 100644
--- a/src/ce/ve.ce.View.js
+++ b/src/ce/ve.ce.View.js
@@ -36,10 +36,10 @@
                teardown: 'onTeardown'
        } );
 
-       if ( this.model.element ) {
+       if ( this.model.element && this.model.element.originalDomElementsIndex 
!== undefined ) {
                // Render attributes from original DOM elements
                ve.dm.Converter.static.renderHtmlAttributeList(
-                       this.model.getOriginalDomElements(),
+                       this.model.getOriginalDomElements( this.model.doc.store 
),
                        this.$element,
                        this.constructor.static.renderHtmlAttributes,
                        // computed attributes
diff --git a/src/dm/lineardata/ve.dm.ElementLinearData.js 
b/src/dm/lineardata/ve.dm.ElementLinearData.js
index 261c3c6..5c3b976 100644
--- a/src/dm/lineardata/ve.dm.ElementLinearData.js
+++ b/src/dm/lineardata/ve.dm.ElementLinearData.js
@@ -937,6 +937,7 @@
  */
 ve.dm.ElementLinearData.prototype.getUsedStoreValues = function ( range ) {
        var i, index, indexes, j,
+               store = this.getStore(),
                valueStore = {};
 
        range = range || new ve.Range( 0, this.data.length );
@@ -949,8 +950,11 @@
                while ( j-- ) {
                        index = indexes[ j ];
                        if ( !Object.prototype.hasOwnProperty.call( valueStore, 
index ) ) {
-                               valueStore[ index ] = this.getStore().value( 
index );
+                               valueStore[ index ] = store.value( index );
                        }
+               }
+               if ( this.data[ i ].originalDomElementsIndex !== undefined ) {
+                       valueStore[ this.data[ i ].originalDomElementsIndex ] = 
store.value( this.data[ i ].originalDomElementsIndex );
                }
        }
        return valueStore;
@@ -1032,7 +1036,7 @@
  */
 ve.dm.ElementLinearData.prototype.sanitize = function ( rules ) {
        var i, len, annotations, emptySet, setToRemove, type,
-               canContainContent, contentElement, isOpen, nodeClass, ann, 
oldHash,
+               canContainContent, contentElement, isOpen, nodeClass, ann,
                store = this.getStore(),
                allAnnotations = this.getAnnotationsFromRange( new ve.Range( 0, 
this.getLength() ), true );
 
@@ -1043,10 +1047,8 @@
                        // Remove originalDomElements from annotations
                        for ( i = 0, len = allAnnotations.getLength(); i < len; 
i++ ) {
                                ann = allAnnotations.get( i );
-                               if ( ann.element.originalDomElements ) {
-                                       oldHash = OO.getHash( ann );
-                                       delete allAnnotations.get( i 
).element.originalDomElements;
-                                       store.replaceHash( oldHash, ann );
+                               if ( ann.element.originalDomElementsIndex ) {
+                                       delete allAnnotations.get( i 
).element.originalDomElementsIndex;
                                }
                        }
                }
@@ -1168,7 +1170,7 @@
                        }
                        if ( rules.removeOriginalDomElements ) {
                                // Remove originalDomElements from nodes
-                               delete this.getData( i ).originalDomElements;
+                               delete this.getData( i 
).originalDomElementsIndex;
                        }
                }
        }
diff --git a/src/dm/metaitems/ve.dm.AlienMetaItem.js 
b/src/dm/metaitems/ve.dm.AlienMetaItem.js
index e25ebda..1f4a6df 100644
--- a/src/dm/metaitems/ve.dm.AlienMetaItem.js
+++ b/src/dm/metaitems/ve.dm.AlienMetaItem.js
@@ -29,8 +29,8 @@
 
 ve.dm.AlienMetaItem.static.preserveHtmlAttributes = false;
 
-ve.dm.AlienMetaItem.static.toDomElements = function ( dataElement, doc ) {
-       return ve.copyDomElements( dataElement.originalDomElements, doc );
+ve.dm.AlienMetaItem.static.toDomElements = function ( dataElement, doc, 
converter ) {
+       return ve.copyDomElements( converter.getStore().value( 
dataElement.originalDomElementsIndex ) || [], doc );
 };
 
 /* Registration */
diff --git a/src/dm/nodes/ve.dm.AlienNode.js b/src/dm/nodes/ve.dm.AlienNode.js
index 1b92cbd..bf992cb 100644
--- a/src/dm/nodes/ve.dm.AlienNode.js
+++ b/src/dm/nodes/ve.dm.AlienNode.js
@@ -57,8 +57,8 @@
        return element;
 };
 
-ve.dm.AlienNode.static.toDomElements = function ( dataElement, doc ) {
-       return ve.copyDomElements( dataElement.originalDomElements, doc );
+ve.dm.AlienNode.static.toDomElements = function ( dataElement, doc, converter 
) {
+       return ve.copyDomElements( converter.getStore().value( 
dataElement.originalDomElementsIndex ) || [], doc );
 };
 
 /**
diff --git a/src/dm/ve.dm.Annotation.js b/src/dm/ve.dm.Annotation.js
index e6bff4d..0db380c 100644
--- a/src/dm/ve.dm.Annotation.js
+++ b/src/dm/ve.dm.Annotation.js
@@ -18,11 +18,13 @@
  * @constructor
  * @param {Object} element Linear model annotation
  */
-ve.dm.Annotation = function VeDmAnnotation( element ) {
+ve.dm.Annotation = function VeDmAnnotation( element, store ) {
        // Parent constructor
        ve.dm.Model.call( this, element );
+
        // Properties
        this.name = this.constructor.static.name; // For ease of filtering
+       this.store = store;
 };
 
 /* Inheritance */
@@ -86,6 +88,17 @@
 /* Methods */
 
 /**
+ * Remap the store indexes stored in a linear model data element.
+ *
+ * @param {Object} mapping Index mapping
+ */
+ve.dm.Annotation.prototype.remapStoreIndexes = function ( mapping ) {
+       if ( this.element.originalDomElementsIndex !== undefined ) {
+               this.element.originalDomElementsIndex = mapping[ 
this.element.originalDomElementsIndex ];
+       }
+};
+
+/**
  * Get an object containing comparable annotation properties.
  *
  * This is used by the converter to merge adjacent annotations.
@@ -94,7 +107,7 @@
  */
 ve.dm.Annotation.prototype.getComparableObject = function () {
        var hashObject = this.getHashObject();
-       delete hashObject.originalDomElements;
+       delete hashObject.originalDomElementsIndex;
        return hashObject;
 };
 
@@ -107,8 +120,8 @@
  * @return {Object} An object all HTML attributes except data-parsoid & 
Parsoid IDs
  */
 ve.dm.Annotation.prototype.getComparableHtmlAttributes = function () {
-       var comparableAttributes, domElements = this.getOriginalDomElements();
-       if ( domElements[ 0 ] ) {
+       var comparableAttributes, domElements = this.store && 
this.getOriginalDomElements( this.store );
+       if ( domElements && domElements[ 0 ] ) {
                comparableAttributes = ve.getDomAttributes( domElements[ 0 ] );
                delete comparableAttributes[ 'data-parsoid' ];
                if ( comparableAttributes.id && comparableAttributes.id.match( 
/^mw[\w-]{2,}$/ ) ) {
@@ -149,7 +162,7 @@
        // Only annotations and nodes generated by the converter have 
originalDomElements set.
        // If this annotation was not generated by the converter, 
this.getOriginalDomElements()
        // will return an empty array.
-       return this.getOriginalDomElements().length > 0;
+       return this.getOriginalDomElementsIndex() !== undefined;
 };
 
 /**
diff --git a/src/dm/ve.dm.Converter.js b/src/dm/ve.dm.Converter.js
index c880a6c..2dc9445 100644
--- a/src/dm/ve.dm.Converter.js
+++ b/src/dm/ve.dm.Converter.js
@@ -146,6 +146,7 @@
  */
 ve.dm.Converter.static.renderHtmlAttributeList = function ( 
originalDomElements, targetDomElements, filter, computed, deep ) {
        var i, ilen, j, jlen, attrs, value;
+
        if ( filter === undefined ) {
                filter = true;
        }
@@ -345,7 +346,7 @@
  *  were a handlesOwnChildren node.
  */
 ve.dm.Converter.prototype.getDomElementsFromDataElement = function ( 
dataElements, doc, childDomElements ) {
-       var domElements,
+       var domElements, originalDomElements,
                dataElement = Array.isArray( dataElements ) ? dataElements[ 0 ] 
: dataElements,
                nodeClass = this.modelRegistry.lookup( dataElement.type );
 
@@ -359,10 +360,11 @@
        if ( !Array.isArray( domElements ) && !( nodeClass.prototype instanceof 
ve.dm.Annotation ) ) {
                throw new Error( 'toDomElements() failed to return an array 
when converting element of type ' + dataElement.type );
        }
+       originalDomElements = this.store.value( 
dataElement.originalDomElementsIndex );
        // Optimization: don't call renderHtmlAttributeList if returned 
domElements are equal to the originals
-       if ( dataElement.originalDomElements && !ve.isEqualDomElements( 
domElements, dataElement.originalDomElements ) ) {
+       if ( originalDomElements && !ve.isEqualDomElements( domElements, 
originalDomElements ) ) {
                ve.dm.Converter.static.renderHtmlAttributeList(
-                       dataElement.originalDomElements,
+                       originalDomElements,
                        domElements,
                        nodeClass.static.preserveHtmlAttributes,
                        // computed
@@ -393,7 +395,7 @@
                dataElements = [ dataElements ];
        }
        if ( dataElements.length ) {
-               dataElements[ 0 ].originalDomElements = domElements;
+               dataElements[ 0 ].originalDomElementsIndex = 
this.store.indexNoHash( domElements );
        }
        return dataElements;
 };
@@ -684,7 +686,7 @@
 
                                // Now take the appropriate action based on that
                                if ( modelClass.prototype instanceof 
ve.dm.Annotation ) {
-                                       annotation = 
this.annotationFactory.createFromElement( childDataElements[ 0 ] );
+                                       annotation = 
this.annotationFactory.createFromElement( childDataElements[ 0 ], this.store );
                                        // Start wrapping if needed
                                        if ( !context.inWrapper && 
!context.expectingContent ) {
                                                startWrapping();
@@ -1106,8 +1108,9 @@
                        matches, first, last,
                        leading = '',
                        trailing = '',
-                       origElementText = annotation.getOriginalDomElements()[ 
0 ] &&
-                               annotation.getOriginalDomElements()[ 0 
].textContent ||
+                       originalDomElements = 
annotation.getOriginalDomElements( converter.store ),
+                       origElementText = originalDomElements[ 0 ] &&
+                               originalDomElements[ 0 ].textContent ||
                                '';
 
                // Add text if needed
diff --git a/src/dm/ve.dm.IndexValueStore.js b/src/dm/ve.dm.IndexValueStore.js
index 43e82d1..32b9d9c 100644
--- a/src/dm/ve.dm.IndexValueStore.js
+++ b/src/dm/ve.dm.IndexValueStore.js
@@ -15,6 +15,7 @@
        this.hashStore = {};
        // maps indexes to values
        this.valueStore = [];
+       this.noHashIndexes = [];
 };
 
 /* Methods */
@@ -49,6 +50,18 @@
                }
                this.hashStore[ hash ] = index;
        }
+       return index;
+};
+
+/**
+ * Add a value to the store with no hash
+ *
+ * @param {Object|string|Array} value Value to store
+ * @return {number} The index of the value in the store
+ */
+ve.dm.IndexValueStore.prototype.indexNoHash = function ( value ) {
+       var index = this.valueStore.push( value ) - 1 ;
+       this.noHashIndexes.push( index );
        return index;
 };
 
@@ -162,13 +175,31 @@
  * @return {Object} Object in which the keys are indexes in other and the 
values are the corresponding keys in this
  */
 ve.dm.IndexValueStore.prototype.merge = function ( other ) {
-       var key, index, mapping = {};
+       var key, index, i, l, value,
+               values = [],
+               mapping = {};
+
        for ( key in other.hashStore ) {
                if ( !Object.prototype.hasOwnProperty.call( this.hashStore, key 
) ) {
-                       index = this.valueStore.push( other.valueStore[ 
other.hashStore[ key ] ] ) - 1;
+                       value = other.valueStore[ other.hashStore[ key ] ];
+                       index = this.valueStore.push( value ) - 1;
                        this.hashStore[ key ] = index;
+                       values.push( value );
                }
                mapping[ other.hashStore[ key ] ] = this.hashStore[ key ];
        }
+       for ( i = 0, l = other.noHashIndexes.length; i < l; i++ ) {
+               value = other.value( other.noHashIndexes[ i ] );
+               index = this.indexNoHash( value );
+               mapping[ other.noHashIndexes[ i ] ] = index;
+               values.push( value );
+       }
+       // Items in the index may contain pointers to other items in the index
+       // In these cases they should contain a remapStoreIndexes method (e.g. 
ve.dm.Annotation)
+       values.forEach( function ( value ) {
+               if ( value.remapStoreIndexes ) {
+                       value.remapStoreIndexes( mapping );
+               }
+       } );
        return mapping;
 };
diff --git a/src/dm/ve.dm.Model.js b/src/dm/ve.dm.Model.js
index 06bfe7c..7438035 100644
--- a/src/dm/ve.dm.Model.js
+++ b/src/dm/ve.dm.Model.js
@@ -210,13 +210,14 @@
  * @return {Object} Hash object
  */
 ve.dm.Model.static.getHashObject = function ( dataElement ) {
-       return {
+       var hash = {
                type: dataElement.type,
-               attributes: dataElement.attributes,
-               // For uniqueness we are only concerned with the first node
-               originalDomElements: dataElement.originalDomElements &&
-                       dataElement.originalDomElements[ 0 ].cloneNode( false 
).outerHTML
+               attributes: dataElement.attributes
        };
+       if ( dataElement.originalDomElementsIndex ) {
+               hash.originalDomElementsIndex = 
dataElement.originalDomElementsIndex;
+       }
+       return hash;
 };
 
 /**
@@ -325,10 +326,20 @@
 /**
  * Get the DOM element(s) this model was originally converted from, if any.
  *
+ * @return {number|undefined} Store index of DOM elements this model was 
converted from
+ */
+ve.dm.Model.prototype.getOriginalDomElementsIndex = function () {
+       return this.element ? this.element.originalDomElementsIndex : undefined;
+};
+
+/**
+ * Get the DOM element(s) this model was originally converted from, if any.
+ *
+ * @param {ve.dm.IndexValueStore} store Index value store where the DOM 
elements are stored
  * @return {HTMLElement[]} DOM elements this model was converted from, empty 
if not applicable
  */
-ve.dm.Model.prototype.getOriginalDomElements = function () {
-       return ( this.element && this.element.originalDomElements ) || [];
+ve.dm.Model.prototype.getOriginalDomElements = function ( store ) {
+       return store.value( this.getOriginalDomElementsIndex() ) || [];
 };
 
 /**
diff --git a/src/dm/ve.dm.ModelFactory.js b/src/dm/ve.dm.ModelFactory.js
index 55ada8a..914bef0 100644
--- a/src/dm/ve.dm.ModelFactory.js
+++ b/src/dm/ve.dm.ModelFactory.js
@@ -27,12 +27,13 @@
  * Create a new item from a model element
  *
  * @param {Object} element Model element
+ * @param {...Mixed} [args] Arguments to pass to the constructor
  * @return {ve.dm.Model} Model constructed from element
  * @throws {Error} Element must have a .type property
  */
 ve.dm.ModelFactory.prototype.createFromElement = function ( element ) {
        if ( element && element.type ) {
-               return this.create( element.type, element );
+               return this.create.apply( this, Array.prototype.concat.apply( [ 
element.type ], arguments ) );
        }
        throw new Error( 'Element must have a .type property' );
 };
diff --git a/src/dm/ve.dm.Node.js b/src/dm/ve.dm.Node.js
index 0401dc6..28a196b 100644
--- a/src/dm/ve.dm.Node.js
+++ b/src/dm/ve.dm.Node.js
@@ -257,7 +257,10 @@
  * @param {Object} dataElement Data element (opening) to remap. Will be 
modified.
  * @param {Object} mapping Object mapping old store indexes to new store 
indexes
  */
-ve.dm.Node.static.remapStoreIndexes = function () {
+ve.dm.Node.static.remapStoreIndexes = function ( dataElement, mapping ) {
+       if ( dataElement.originalDomElementsIndex !== undefined ) {
+               dataElement.originalDomElementsIndex = mapping[ 
dataElement.originalDomElementsIndex ];
+       }
 };
 
 /**
diff --git a/tests/ce/ve.ce.Surface.test.js b/tests/ce/ve.ce.Surface.test.js
index 86a4ac6..9a9df32 100644
--- a/tests/ce/ve.ce.Surface.test.js
+++ b/tests/ce/ve.ce.Surface.test.js
@@ -963,8 +963,8 @@
                                                { type: 'retain', length: 5 },
                                                {
                                                        type: 'replace',
-                                                       insert: [ [ 'B', [ 1 ] 
] ],
-                                                       remove: [ [ 'X', [ 1 ] 
] ],
+                                                       insert: [ [ 'B', [ 4 ] 
] ],
+                                                       remove: [ [ 'X', [ 4 ] 
] ],
                                                        insertedDataLength: 1,
                                                        insertedDataOffset: 0
                                                },
@@ -983,7 +983,7 @@
                                                { type: 'retain', length: 2 },
                                                {
                                                        type: 'replace',
-                                                       insert: [ [ 'Y', [ 0 ] 
] ],
+                                                       insert: [ [ 'Y', [ 2 ] 
] ],
                                                        remove: [],
                                                        insertedDataOffset: 0,
                                                        insertedDataLength: 1
@@ -1375,14 +1375,14 @@
                                                        type: 'annotate',
                                                        method: 'set',
                                                        bias: 'start',
-                                                       index: 0
+                                                       index: 9
                                                },
                                                { type: 'retain', length: 3 },
                                                {
                                                        type: 'annotate',
                                                        method: 'set',
                                                        bias: 'stop',
-                                                       index: 0
+                                                       index: 9
                                                },
                                                { type: 'retain', length: 5 }
                                        ]
@@ -2444,27 +2444,12 @@
                                msg: 'Paste paragraphs and a table into table 
cell'
                        },
                        {
+                               documentHtml: '<p></p>',
                                rangeOrSelection: new ve.Range( 1 ),
-                               pasteHtml: '<span rel="ve:Alien" 
id="useful">Foo</span><span rel="ve:Alien" id="mwAB">Bar</span>',
+                               pasteHtml: '<img src="null" id="mwAB"><img 
src="null" id="useful">',
                                fromVe: true,
-                               originalDomElements: true,
                                expectedRangeOrSelection: new ve.Range( 5 ),
-                               expectedOps: [
-                                       [
-                                               { type: 'retain', length: 1 },
-                                               {
-                                                       type: 'replace',
-                                                       insert: [
-                                                               { type: 
'alienInline', originalDomElements: $( '<span rel="ve:Alien" 
id="useful">Foo</span>' ).toArray() },
-                                                               { type: 
'/alienInline' },
-                                                               { type: 
'alienInline', originalDomElements: $( '<span rel="ve:Alien">Bar</span>' 
).toArray() },
-                                                               { type: 
'/alienInline' }
-                                                       ],
-                                                       remove: []
-                                               },
-                                               { type: 'retain', length: 
docLen - 1 }
-                                       ]
-                               ],
+                               expectedHtml: '<p><img src="null"><img 
src="null" id="useful"></p>',
                                msg: 'Parsoid IDs stripped'
                        },
                        {
@@ -2598,12 +2583,16 @@
        }
        QUnit.expect( expected );
 
-       function testRunner( documentHtml, pasteHtml, 
internalSourceRangeOrSelection, fromVe, useClipboardData, pasteTargetHtml, 
rangeOrSelection, pasteSpecial, expectedOps, expectedRangeOrSelection, 
expectedHtml, originalDomElements, msg ) {
+       function testRunner( documentHtml, pasteHtml, 
internalSourceRangeOrSelection, fromVe, useClipboardData, pasteTargetHtml, 
rangeOrSelection, pasteSpecial, expectedOps, expectedRangeOrSelection, 
expectedHtml, store, msg ) {
                var i, j, txs, ops, txops, htmlDoc, expectedSelection, 
testEvent,
                        e = {},
                        view = documentHtml ? 
ve.test.utils.createSurfaceViewFromHtml( documentHtml ) : exampleSurface,
                        model = view.getModel(),
                        doc = model.getDocument();
+
+               function summary( el ) {
+                       return ve.getDomElementSummary( el, true );
+               }
 
                function getLayoutSpecific( expected ) {
                        if ( $.isPlainObject( expected ) && !expected.type ) {
@@ -2646,22 +2635,23 @@
                                        txops = txs[ i ].getOperations();
                                        for ( j = 0; j < txops.length; j++ ) {
                                                if ( txops[ j ].remove ) {
-                                                       
ve.dm.example.postprocessAnnotations( txops[ j ].remove, doc.getStore(), 
originalDomElements );
-                                                       if ( 
!originalDomElements ) {
-                                                               
ve.dm.example.removeOriginalDomElements( txops[ j ].remove );
-                                                       }
+                                                       
ve.dm.example.postprocessAnnotations( txops[ j ].remove, doc.getStore() );
+                                                       
ve.dm.example.removeOriginalDomElements( txops[ j ].remove );
                                                }
                                                if ( txops[ j ].insert ) {
-                                                       
ve.dm.example.postprocessAnnotations( txops[ j ].insert, doc.getStore(), 
originalDomElements );
-                                                       if ( 
!originalDomElements ) {
-                                                               
ve.dm.example.removeOriginalDomElements( txops[ j ].insert );
-                                                       }
+                                                       
ve.dm.example.postprocessAnnotations( txops[ j ].insert, doc.getStore() );
+                                                       
ve.dm.example.removeOriginalDomElements( txops[ j ].insert );
                                                }
                                        }
                                        ops.push( txops );
                                }
                        }
                        assert.equalLinearData( ops, expectedOps, msg + ': 
data' );
+                       if ( store ) {
+                               for ( i in store ) {
+                                       assert.deepEqual( doc.getStore().value( 
i ).map( summary ), store[ i ].map( summary ), ': store value ' + i );
+                               }
+                       }
                }
                if ( expectedRangeOrSelection ) {
                        expectedSelection = 
ve.test.utils.selectionFromRangeOrSelection( model.getDocument(), 
getLayoutSpecific( expectedRangeOrSelection ) );
@@ -2685,8 +2675,8 @@
                testRunner(
                        cases[ i ].documentHtml, cases[ i ].pasteHtml, cases[ i 
].internalSourceRangeOrSelection, cases[ i ].fromVe, cases[ i 
].useClipboardData,
                        cases[ i ].pasteTargetHtml, cases[ i 
].rangeOrSelection, cases[ i ].pasteSpecial,
-                       cases[ i ].expectedOps, cases[ i 
].expectedRangeOrSelection, cases[ i ].expectedHtml, cases[ i 
].originalDomElements,
-                       cases[ i ].msg
+                       cases[ i ].expectedOps, cases[ i 
].expectedRangeOrSelection, cases[ i ].expectedHtml,
+                       cases[ i ].store, cases[ i ].msg
                );
        }
 
diff --git a/tests/ce/ve.ce.TextState.test.js b/tests/ce/ve.ce.TextState.test.js
index 57e3342..b5450a0 100644
--- a/tests/ce/ve.ce.TextState.test.js
+++ b/tests/ce/ve.ce.TextState.test.js
@@ -20,7 +20,7 @@
                                { type: 'retain', length: 5 },
                                {
                                        type: 'replace',
-                                       remove: [ [ 'b', [ 0 ] ], [ 'a', [ 0 ] 
], [ 'r', [ 0 ] ] ],
+                                       remove: [ [ 'b', [ 2 ] ], [ 'a', [ 2 ] 
], [ 'r', [ 2 ] ] ],
                                        insert: [ 'b', 'a', 'r' ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 3
@@ -38,7 +38,7 @@
                                {
                                        type: 'replace',
                                        remove: [],
-                                       insert: [ [ 'r', [ 0 ] ] ],
+                                       insert: [ [ 'r', [ 2 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 1
                                },
@@ -55,7 +55,7 @@
                                {
                                        type: 'replace',
                                        remove: [ 'b', 'a', 'r' ],
-                                       insert: [ [ 'b', [ 0 ] ], [ 'a', [ 0 ] 
], [ 'r', [ 0 ] ] ],
+                                       insert: [ [ 'b', [ 1 ] ], [ 'a', [ 1 ] 
], [ 'r', [ 1 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 3
                                },
@@ -72,7 +72,7 @@
                                {
                                        type: 'replace',
                                        remove: [],
-                                       insert: [ [ 'z', [ 0 ] ] ],
+                                       insert: [ [ 'z', [ 1 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 1
                                },
@@ -125,9 +125,9 @@
                                        type: 'replace',
                                        remove: [ 'b', 'a', 'r' ],
                                        insert: [
-                                               [ 'b', [ 0, 1 ] ],
-                                               [ 'a', [ 0, 1 ] ],
-                                               [ 'r', [ 0, 1 ] ]
+                                               [ 'b', [ 1, 2 ] ],
+                                               [ 'a', [ 1, 2 ] ],
+                                               [ 'r', [ 1, 2 ] ]
                                        ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 3
@@ -144,8 +144,8 @@
                                { type: 'retain', length: 5 },
                                {
                                        type: 'replace',
-                                       remove: [ [ 'b', [ 0, 1 ] ], [ 'a', [ 
0, 1 ] ], [ 'r', [ 0, 1 ] ] ],
-                                       insert: [ [ 'b', [ 0 ] ], [ 'a', [ 0 ] 
], [ 'r', [ 0 ] ] ],
+                                       remove: [ [ 'b', [ 2, 4 ] ], [ 'a', [ 
2, 4 ] ], [ 'r', [ 2, 4 ] ] ],
+                                       insert: [ [ 'b', [ 2 ] ], [ 'a', [ 2 ] 
], [ 'r', [ 2 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 3
                                },
@@ -162,7 +162,7 @@
                                {
                                        type: 'replace',
                                        remove: [],
-                                       insert: [ [ 'r', [ 0, 1 ] ] ],
+                                       insert: [ [ 'r', [ 2, 4 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 1
                                },
@@ -178,8 +178,8 @@
                                { type: 'retain', length: 5 },
                                {
                                        type: 'replace',
-                                       remove: [ [ 'b', [ 0 ] ], [ 'a', [ 0 ] 
], [ 'r', [ 0 ] ] ],
-                                       insert: [ [ 'b', [ 0, 1 ] ], [ 'a', [ 
0, 1 ] ], [ 'r', [ 0, 1 ] ] ],
+                                       remove: [ [ 'b', [ 2 ] ], [ 'a', [ 2 ] 
], [ 'r', [ 2 ] ] ],
+                                       insert: [ [ 'b', [ 2, 3 ] ], [ 'a', [ 
2, 3 ] ], [ 'r', [ 2, 3 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 3
                                },
@@ -196,7 +196,7 @@
                                {
                                        type: 'replace',
                                        remove: [],
-                                       insert: [ [ 'z', [ 0, 1 ] ] ],
+                                       insert: [ [ 'z', [ 1, 3 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 1
                                },
@@ -213,7 +213,7 @@
                                {
                                        type: 'replace',
                                        remove: [],
-                                       insert: [ [ 'z', [ 0 ] ] ],
+                                       insert: [ [ 'z', [ 1 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 1
                                },
@@ -247,11 +247,11 @@
                                { type: 'retain', length: 5 },
                                {
                                        type: 'replace',
-                                       remove: [ [ 'b', [ 0 ] ], [ 'a', [ 0 ] 
], [ 'r', [ 0 ] ] ],
+                                       remove: [ [ 'b', [ 1 ] ], [ 'a', [ 1 ] 
], [ 'r', [ 1 ] ] ],
                                        insert: [
-                                               [ 'b', [ 0, 1, 2 ] ],
-                                               [ 'a', [ 0, 1, 2 ] ],
-                                               [ 'r', [ 0, 1, 2 ] ]
+                                               [ 'b', [ 1, 3, 4 ] ],
+                                               [ 'a', [ 1, 3, 4 ] ],
+                                               [ 'r', [ 1, 3, 4 ] ]
                                        ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 3
@@ -272,7 +272,7 @@
                                        // TODO: Reuse bold 0 instead of 
creating a new bold 2?
                                        // (Some annotation types may need 
specific rules as to
                                        // when this can be done)
-                                       insert: [ [ 'b', [ 2 ] ], [ 'a', [ 2 ] 
], [ 'z', [ 2 ] ] ],
+                                       insert: [ [ 'b', [ 1 ] ], [ 'a', [ 1 ] 
], [ 'z', [ 1 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 3
                                },
@@ -288,8 +288,8 @@
                                { type: 'retain', length: 3 },
                                {
                                        type: 'replace',
-                                       remove: [ [ 'c', [ 0, 1 ] ], [ 'd', [ 0 
] ] ],
-                                       insert: [ [ 'c', [ 0, 1, 2 ] ], [ 'd', 
[ 0, 2 ] ] ],
+                                       remove: [ [ 'c', [ 1, 3 ] ], [ 'd', [ 1 
] ] ],
+                                       insert: [ [ 'c', [ 1, 3, 4 ] ], [ 'd', 
[ 1, 4 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 2
                                },
@@ -309,9 +309,9 @@
                                        // a diff algorithm that matches common 
start/end items
                                        // then replaces the entire interior. 
In real life usage
                                        // there won't usually be two separate 
changed regions.
-                                       remove: [ [ 'b', [ 1 ] ], [ 'a', [ 1 ] 
], [ 'r', [ 1 ] ], ' ', 'b', 'a', 'z' ],
+                                       remove: [ [ 'b', [ 3 ] ], [ 'a', [ 3 ] 
], [ 'r', [ 3 ] ], ' ', 'b', 'a', 'z' ],
                                        // The first insertion get
-                                       insert: [ 'b', 'a', 'r', ' ', [ 'b', [ 
0 ] ], [ 'a', [ 0 ] ], [ 'z', [ 0 ] ] ],
+                                       insert: [ 'b', 'a', 'r', ' ', [ 'b', [ 
1 ] ], [ 'a', [ 1 ] ], [ 'z', [ 1 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 7
                                },
@@ -328,7 +328,7 @@
                                {
                                        type: 'replace',
                                        remove: [],
-                                       insert: [ 'y', [ 'w', [ 0 ] ] ],
+                                       insert: [ 'y', [ 'w', [ 1 ] ] ],
                                        insertedDataOffset: 0,
                                        insertedDataLength: 2
                                },
diff --git a/tests/dm/lineardata/ve.dm.ElementLinearData.test.js 
b/tests/dm/lineardata/ve.dm.ElementLinearData.test.js
index 9d11025..7479398 100644
--- a/tests/dm/lineardata/ve.dm.ElementLinearData.test.js
+++ b/tests/dm/lineardata/ve.dm.ElementLinearData.test.js
@@ -1544,12 +1544,12 @@
                                html: '<p style="text-shadow: 0 0 1px 
#000;">F<b style="color:blue;">o</b>o</p>',
                                data: [
                                        { type: 'paragraph' },
-                                       'F', [ 'o', [ 0 ] ], 'o',
+                                       'F', [ 'o', [ 2 ] ], 'o',
                                        { type: '/paragraph' },
                                        { type: 'internalList' },
                                        { type: '/internalList' }
                                ],
-                               store: [ bold ],
+                               store: [ undefined, undefined, bold ],
                                rules: { removeOriginalDomElements: true },
                                msg: 'Original DOM elements removed'
                        },
@@ -1675,14 +1675,14 @@
                                data: [
                                        { type: 'paragraph' },
                                        'F', 'o', 'o', ' ', 'B', 'a', 'r', ' ',
-                                       [ 'B', [ 0 ] ],
-                                       [ 'a', [ 0 ] ],
-                                       [ 'z', [ 0 ] ],
-                                       [ ' ', [ 0 ] ],
-                                       [ 'Q', [ 0 ] ],
-                                       [ 'u', [ 0 ] ],
-                                       [ 'u', [ 0 ] ],
-                                       [ 'x', [ 0 ] ],
+                                       [ 'B', [ 2 ] ],
+                                       [ 'a', [ 2 ] ],
+                                       [ 'z', [ 2 ] ],
+                                       [ ' ', [ 2 ] ],
+                                       [ 'Q', [ 2 ] ],
+                                       [ 'u', [ 2 ] ],
+                                       [ 'u', [ 2 ] ],
+                                       [ 'x', [ 2 ] ],
                                        { type: '/paragraph' },
                                        { type: 'internalList' },
                                        { type: '/internalList' }
@@ -1898,11 +1898,15 @@
                        }
                ];
 
+       function getElement( ann ) {
+               return ann.element;
+       }
+
        QUnit.expect( cases.length );
        for ( i = 0; i < cases.length; i++ ) {
                assert.deepEqual(
-                       elementData.getUsedStoreValues( cases[ i ].range ),
-                       cases[ i ].expected,
+                       ve.copy( elementData.getUsedStoreValues( cases[ i 
].range ), getElement ),
+                       ve.copy( cases[ i ].expected, getElement ),
                        cases[ i ].msg
                );
        }
diff --git a/tests/dm/ve.dm.Annotation.test.js 
b/tests/dm/ve.dm.Annotation.test.js
index 6bfeca9..259b071 100644
--- a/tests/dm/ve.dm.Annotation.test.js
+++ b/tests/dm/ve.dm.Annotation.test.js
@@ -13,39 +13,24 @@
                                msg: 'Bold',
                                annotation: new ve.dm.BoldAnnotation( {
                                        type: 'textStyle/bold',
-                                       attributes: { nodeName: 'b' },
-                                       originalDomElements: $( '<b>Foo</b>' 
).toArray()
+                                       attributes: { nodeName: 'b' }
                                } ),
                                expected: {
                                        type: 'textStyle/bold',
-                                       attributes: { nodeName: 'b' },
-                                       originalDomElements: '<b></b>'
+                                       attributes: { nodeName: 'b' }
                                }
                        },
                        {
-                               msg: 'Bold with different content',
-                               annotation: new ve.dm.BoldAnnotation( {
-                                       type: 'textStyle/bold',
-                                       attributes: { nodeName: 'b' },
-                                       originalDomElements: $( '<b>Bar</b>' 
).toArray()
-                               } ),
-                               expected: {
-                                       type: 'textStyle/bold',
-                                       attributes: { nodeName: 'b' },
-                                       originalDomElements: '<b></b>'
-                               }
-                       },
-                       {
-                               msg: 'Italic with attributes',
+                               msg: 'Italic with original DOM elements',
                                annotation: new ve.dm.ItalicAnnotation( {
                                        type: 'textStyle/italic',
                                        attributes: { nodeName: 'i' },
-                                       originalDomElements: $( '<i 
style="color:red;">Foo</i>' ).toArray()
+                                       originalDomElementsIndex: 1
                                } ),
                                expected: {
                                        type: 'textStyle/italic',
                                        attributes: { nodeName: 'i' },
-                                       originalDomElements: '<i 
style="color:red;"></i>'
+                                       originalDomElementsIndex: 1
                                }
                        }
                ];
diff --git a/tests/dm/ve.dm.Document.test.js b/tests/dm/ve.dm.Document.test.js
index 944181a..0122f0b 100644
--- a/tests/dm/ve.dm.Document.test.js
+++ b/tests/dm/ve.dm.Document.test.js
@@ -64,10 +64,10 @@
        assert.strictEqual( doc.data, data, 'ElementLinearData is stored by 
reference' );
 
        doc = ve.dm.example.createExampleDocument( 'withMeta' );
-       assert.deepEqualWithDomElements( doc.getData(), 
ve.dm.example.withMetaPlainData,
+       assert.equalLinearDataWithDom( doc.getStore(), doc.getData(), 
ve.dm.example.withMetaPlainData,
                'metadata is stripped out of the linear model'
        );
-       assert.deepEqualWithDomElements( doc.getMetadata(), 
ve.dm.example.withMetaMetaData,
+       assert.equalLinearDataWithDom( doc.getStore(), doc.getMetadata(), 
ve.dm.example.withMetaMetaData,
                'metadata is put in the meta-linmod'
        );
        assert.equalNodeTree(
@@ -83,12 +83,12 @@
 QUnit.test( 'getData', 1, function ( assert ) {
        var doc = ve.dm.example.createExampleDocument(),
                expectedData = ve.dm.example.preprocessAnnotations( ve.copy( 
ve.dm.example.data ) );
-       assert.deepEqualWithDomElements( doc.getData(), expectedData.getData() 
);
+       assert.equalLinearDataWithDom( doc.getStore(), doc.getData(), 
expectedData.getData() );
 } );
 
 QUnit.test( 'getFullData', 1, function ( assert ) {
        var doc = ve.dm.example.createExampleDocument( 'withMeta' );
-       assert.deepEqualWithDomElements( doc.getFullData(), 
ve.dm.example.withMeta );
+       assert.equalLinearDataWithDom( doc.getStore(), doc.getFullData(), 
ve.dm.example.withMeta );
 } );
 
 QUnit.test( 'cloneFromRange', function ( assert ) {
@@ -865,7 +865,8 @@
                        { type: '/internalList' }
                ] );
                slice = doc.shallowCloneFromRange( cases[ i ].range );
-               assert.deepEqualWithDomElements(
+               assert.equalLinearDataWithDom(
+                       doc.getStore(),
                        slice.getData(),
                        expectedData,
                        cases[ i ].msg + ': data'
diff --git a/tests/dm/ve.dm.Transaction.test.js 
b/tests/dm/ve.dm.Transaction.test.js
index 0424aa1..5fbcdd3 100644
--- a/tests/dm/ve.dm.Transaction.test.js
+++ b/tests/dm/ve.dm.Transaction.test.js
@@ -26,7 +26,7 @@
                        tx = constructor.apply(
                                ve.dm.Transaction, cases[ msg ].args
                        );
-                       assert.deepEqualWithDomElements( tx.getOperations(), 
cases[ msg ].ops, msg + ': operations match' );
+                       assert.equalLinearDataWithDom( cases[ msg ].args[ 0 
].getStore(), tx.getOperations(), cases[ msg ].ops, msg + ': operations match' 
);
                        if ( testRange ) {
                                assert.equalRange(
                                        tx.getModifiedRange(),
@@ -1072,6 +1072,7 @@
                        actualStoreItems[ j ] = doc.store.value( 
doc.store.indexOfHash(
                                OO.getHash( expectedStoreItems[ j ] )
                        ) );
+                       expectedStoreItems[ j ].store = store2;
                }
                assert.deepEqual( actualStoreItems, expectedStoreItems, cases[ 
i ].msg + ': store items' );
        }
diff --git a/tests/dm/ve.dm.TransactionProcessor.test.js 
b/tests/dm/ve.dm.TransactionProcessor.test.js
index 130debc..bbd136a 100644
--- a/tests/dm/ve.dm.TransactionProcessor.test.js
+++ b/tests/dm/ve.dm.TransactionProcessor.test.js
@@ -707,7 +707,7 @@
                        expectedDoc.buildNodeTree();
                        // Commit
                        testDoc.commit( tx );
-                       assert.deepEqualWithDomElements( testDoc.getFullData(), 
expectedDoc.getFullData(), 'commit (data): ' + msg );
+                       assert.equalLinearDataWithDom( testDoc.getStore(), 
testDoc.getFullData(), expectedDoc.getFullData(), 'commit (data): ' + msg );
                        assert.equalNodeTree(
                                testDoc.getDocumentNode(),
                                expectedDoc.getDocumentNode(),
@@ -715,7 +715,7 @@
                        );
                        // Rollback
                        testDoc.commit( tx.reversed() );
-                       assert.deepEqualWithDomElements( testDoc.getFullData(), 
originalDoc.getFullData(), 'rollback (data): ' + msg );
+                       assert.equalLinearDataWithDom( testDoc.getStore(), 
testDoc.getFullData(), originalDoc.getFullData(), 'rollback (data): ' + msg );
                        assert.equalNodeTree(
                                testDoc.getDocumentNode(),
                                originalDoc.getDocumentNode(),
@@ -730,7 +730,7 @@
                                cases[ msg ].exception,
                                'exception thrown: ' + msg
                        );
-                       assert.deepEqualWithDomElements( testDoc.getFullData(), 
originalDoc.getFullData(), 'data unmodified: ' + msg );
+                       assert.equalLinearDataWithDom( testDoc.getStore(), 
testDoc.getFullData(), originalDoc.getFullData(), 'data unmodified: ' + msg );
                        assert.equalNodeTree(
                                testDoc.getDocumentNode(),
                                originalDoc.getDocumentNode(),
diff --git a/tests/dm/ve.dm.example.js b/tests/dm/ve.dm.example.js
index 87df020..793f03c 100644
--- a/tests/dm/ve.dm.example.js
+++ b/tests/dm/ve.dm.example.js
@@ -47,6 +47,10 @@
                if ( Array.isArray( data[ i ][ key ] ) && data[ i ][ key ][ 0 
].type ) {
                        data[ i ][ key ] = ve.dm.example.createAnnotationSet( 
store, data[ i ][ key ] ).getIndexes();
                }
+               if ( data[ i ].originalDomElements ) {
+                       data[ i ].originalDomElementsIndex = store.indexNoHash( 
data[ i ].originalDomElements );
+                       delete data[ i ].originalDomElements;
+               }
        }
        return new ve.dm.FlatLinearData( store, data );
 };
@@ -72,10 +76,10 @@
                        data[ i ][ key ] = new ve.dm.AnnotationSet( store, 
data[ i ][ key ] ).get();
                        for ( j = 0; j < data[ i ][ key ].length; j++ ) {
                                data[ i ][ key ][ j ] = data[ i ][ key ][ j 
].element;
-                               if ( !preserveDomElements && data[ i ][ key ][ 
j ].originalDomElements ) {
+                               if ( !preserveDomElements && data[ i ][ key ][ 
j ].originalDomElementsIndex ) {
                                        // Make a shallow clone and remove 
.originalDomElements from it
                                        data[ i ][ key ][ j ] = $.extend( {}, 
data[ i ][ key ][ j ] );
-                                       delete data[ i ][ key ][ j 
].originalDomElements;
+                                       delete data[ i ][ key ][ j 
].originalDomElementsIndex;
                                }
                        }
                }
@@ -92,8 +96,8 @@
 ve.dm.example.removeOriginalDomElements = function ( data ) {
        var i, len;
        for ( i = 0, len = data.length; i < len; i++ ) {
-               if ( data[ i ].originalDomElements ) {
-                       delete data[ i ].originalDomElements;
+               if ( data[ i ].originalDomElementsIndex ) {
+                       delete data[ i ].originalDomElementsIndex;
                }
        }
        return data;
@@ -104,10 +108,11 @@
  *
  * @method
  * @param {Object} annotation Plain object with type and attributes properties
+ * @param {ve.dm.IndexValueStore} [store] Index value store
  * @return {ve.dm.Annotation} Instance of the right ve.dm.Annotation subclass
  */
-ve.dm.example.createAnnotation = function ( annotation ) {
-       return ve.dm.annotationFactory.createFromElement( annotation );
+ve.dm.example.createAnnotation = function ( annotation, store ) {
+       return ve.dm.annotationFactory.createFromElement( annotation, store );
 };
 
 /**
@@ -124,7 +129,7 @@
 ve.dm.example.createAnnotationSet = function ( store, annotations ) {
        var i;
        for ( i = 0; i < annotations.length; i++ ) {
-               annotations[ i ] = ve.dm.example.createAnnotation( annotations[ 
i ] );
+               annotations[ i ] = ve.dm.example.createAnnotation( annotations[ 
i ], store );
        }
        return new ve.dm.AnnotationSet( store, store.indexes( annotations ) );
 };
@@ -1423,7 +1428,7 @@
                        { type: 'internalList' },
                        { type: '/internalList' }
                ],
-               storeLength: 2,
+               storeLength: 4,
                fromDataBody: '<p><code>a</code>b<tt>c</tt>d<code>ef</code></p>'
        },
        'additive annotations': {
@@ -1440,7 +1445,7 @@
                        { type: 'internalList' },
                        { type: '/internalList' }
                ],
-               storeLength: 2
+               storeLength: 4
        },
        'additive annotations overlapping other annotations': {
                body: '<p><i><big>a<big><b>b</b></big><b>c</b></big></i></p>',
@@ -1453,7 +1458,7 @@
                        { type: 'internalList' },
                        { type: '/internalList' }
                ],
-               storeLength: 3
+               storeLength: 5
        },
        'annotations normalised on import': {
                body: '<p><em>Foo</em><strong>bar</strong></p>',
@@ -2192,9 +2197,6 @@
                        { type: 'internalList' },
                        { type: '/internalList' }
                ],
-               normalizedBody:
-                       '<p><b>Foobar</b><strong>baz</strong></p>' +
-                       '<p><a href="quux">Foobar</a><a 
href="whee">baz</a></p>',
                fromDataBody:
                        '<p><b>Foobarbaz</b></p>' +
                        '<p><a href="quux">Foobar</a><a href="whee">baz</a></p>'
diff --git a/tests/ve.qunit.js b/tests/ve.qunit.js
index 74f6cc3..492139b 100644
--- a/tests/ve.qunit.js
+++ b/tests/ve.qunit.js
@@ -181,34 +181,41 @@
        };
 
        QUnit.assert.equalLinearData = function ( actual, expected, message ) {
-               function removeOriginalDomElements( arr ) {
-                       var i = 0,
-                               len = arr.length;
-                       for ( ; i < len; i++ ) {
-                               if ( arr[ i ].originalDomElements ) {
-                                       delete arr[ i ].originalDomElements;
-                               }
+               function removeOriginalDomElements( val ) {
+                       if ( val && val.originalDomElementsIndex !== undefined 
) {
+                               delete val.originalDomElementsIndex;
+                       }
+                       if ( val && val.originalDomElements !== undefined ) {
+                               delete val.originalDomElements;
                        }
                }
 
-               if ( Array.isArray( actual ) ) {
-                       actual = actual.slice();
-                       removeOriginalDomElements( actual );
-               }
-               if ( Array.isArray( expected ) ) {
-                       expected = expected.slice();
-                       removeOriginalDomElements( expected );
+               actual = ve.copy( actual );
+               expected = ve.copy( expected );
+               actual = ve.copy( actual, null, removeOriginalDomElements );
+               expected = ve.copy( expected, null, removeOriginalDomElements );
+
+               QUnit.push( QUnit.equiv( actual, expected ), actual, expected, 
message );
+       };
+
+       QUnit.assert.equalLinearDataWithDom = function ( store, actual, 
expected, message ) {
+               function addOriginalDomElements( val ) {
+                       if ( val && val.originalDomElementsIndex !== undefined 
) {
+                               val.originalDomElements = store.value( 
val.originalDomElementsIndex );
+                               delete val.originalDomElementsIndex;
+                       }
                }
 
-               // FIXME domElements handling here shouldn't be necessary, but 
it is because of AlienNode
-               actual = ve.copy( actual, ve.convertDomElements );
-               expected = ve.copy( expected, ve.convertDomElements );
+               actual = ve.copy( actual );
+               expected = ve.copy( expected );
+               actual = ve.copy( actual, null, addOriginalDomElements );
+               expected = ve.copy( expected, null, addOriginalDomElements );
 
                QUnit.push( QUnit.equiv( actual, expected ), actual, expected, 
message );
        };
 
        /**
-        * Assert that two objects which may contain dom elements are equal.
+        * Assert that two objects which may contain DOM elements are equal.
         *
         * @method
         * @static
diff --git a/tests/ve.test.utils.js b/tests/ve.test.utils.js
index 7393ac2..88dc020 100644
--- a/tests/ve.test.utils.js
+++ b/tests/ve.test.utils.js
@@ -182,7 +182,7 @@
                        assert.equalLinearData( actualData, caseItem.data, msg 
+ ': data' );
                        assert.deepEqual( model.getInnerWhitespace(), 
caseItem.innerWhitespace || new Array( 2 ), msg + ': inner whitespace' );
                        if ( caseItem.storeLength !== undefined ) {
-                               assert.strictEqual( 
model.getStore().valueStore.length, caseItem.storeLength, msg + ': store length 
matches' );
+                               assert.strictEqual( Object.keys( 
model.getStore().hashStore ).length, caseItem.storeLength, msg + ': store 
length matches' );
                        }
                        // check storeItems have been added to store
                        if ( caseItem.storeItems ) {

-- 
To view, visit https://gerrit.wikimedia.org/r/304970
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I8a71c1a40ec35108d0a9a388da6f75632f8dc53c
Gerrit-PatchSet: 1
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Esanders <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to