jenkins-bot has submitted this change and it was merged.

Change subject: Correctly preserve metadata in `Transaction.newFromUnwrap`.
......................................................................


Correctly preserve metadata in `Transaction.newFromUnwrap`.

The `Transaction.pushReplace` method has a corner case if the removed
region has metadata and the inserted region is empty.  This works fine
unless there are two adjacent `pushReplace` operations, which can occur
in `Transaction.newFromUnwrap`.  Fix this by having `pushReplace` look
at a preceding replace and correctly merge the two operations if
possible (in particular in the tricky case where the previous case has
a zero-length insertion).  Pleasantly, this can be done without a lot of
special-casing code in `pushReplace` or `newFromUnwrap`.

Add test cases verifying the `newFromUnwrap` works correctly (both
in commit and in rollback) when there is metadata present.

Change-Id: I6cfec0d2b1823dad724422f018a3c73dc0c7f186
---
M modules/ve/dm/ve.dm.Transaction.js
M modules/ve/test/dm/ve.dm.Transaction.test.js
M modules/ve/test/dm/ve.dm.TransactionProcessor.test.js
M modules/ve/test/dm/ve.dm.example.js
4 files changed, 216 insertions(+), 8 deletions(-)

Approvals:
  Catrope: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/modules/ve/dm/ve.dm.Transaction.js 
b/modules/ve/dm/ve.dm.Transaction.js
index a75e520..761c036 100644
--- a/modules/ve/dm/ve.dm.Transaction.js
+++ b/modules/ve/dm/ve.dm.Transaction.js
@@ -890,11 +890,22 @@
                removeMetadata = metadataReplace.remove,
                insertMetadata = metadataReplace.insert;
 
-       if ( lastOp && lastOp.type === 'replace' && !lastOp.removeMetadata && 
!removeMetadata ) {
-               // simple replaces can just be concatenated
-               // TODO: allow replaces with meta to be merged?
-               lastOp.insert = lastOp.insert.concat( insert );
-               lastOp.remove = lastOp.remove.concat( remove );
+       // simple replaces can be combined
+       // (but don't do this if there is metadata to be removed and the 
previous
+       // replace had a non-zero insertion, because that would shift the 
metadata
+       // location.)
+       if (
+               lastOp && lastOp.type === 'replace' &&
+               !( lastOp.insert.length > 0 && removeMetadata !== undefined )
+       ) {
+               lastOp = this.operations.pop();
+               this.lengthDifference -= lastOp.insert.length - 
lastOp.remove.length;
+               this.pushReplace(
+                       doc,
+                       offset - lastOp.remove.length,
+                       lastOp.remove.length + removeLength,
+                       lastOp.insert.concat( insert )
+               );
        } else {
                op = {
                        'type': 'replace',
@@ -906,8 +917,8 @@
                        op.insertMetadata = insertMetadata;
                }
                this.operations.push( op );
+               this.lengthDifference += insert.length - remove.length;
        }
-       this.lengthDifference += insert.length - remove.length;
 };
 
 /**
diff --git a/modules/ve/test/dm/ve.dm.Transaction.test.js 
b/modules/ve/test/dm/ve.dm.Transaction.test.js
index 776b521..ac04de0 100644
--- a/modules/ve/test/dm/ve.dm.Transaction.test.js
+++ b/modules/ve/test/dm/ve.dm.Transaction.test.js
@@ -1024,6 +1024,9 @@
 QUnit.test( 'newFromWrap', function ( assert ) {
        var i, key,
                doc = ve.dm.example.createExampleDocument(),
+               metaDoc = ve.dm.example.createExampleDocument( 'withMeta' ),
+               listMetaDoc = ve.dm.example.createExampleDocument( 
'listWithMeta' ),
+               listDoc = ve.dm.example.createExampleDocumentFromObject( 
'listDoc', null, { 'listDoc': listMetaDoc.getData() } ),
                cases = {
                        'changes a heading to a paragraph': {
                                'args': [doc, new ve.Range( 1, 4 ), [ { 'type': 
'heading', 'attributes': { 'level': 1 } } ], [ { 'type': 'paragraph' } ], [], 
[]],
@@ -1046,6 +1049,25 @@
                                        { 'type': 'retain', 'length': 10 },
                                        { 'type': 'replace', 'remove': [ { 
'type': '/listItem' }, { 'type': '/list' } ], 'insert': [] },
                                        { 'type': 'retain', 'length': 37 }
+                               ]
+                       },
+                       'unwraps a multiple-item list': {
+                               'args': [listDoc, new ve.Range( 1, 11 ), [ { 
'type': 'list' } ], [], [ { 'type': 'listItem', 'attributes': {'styles': 
['bullet']} } ], [] ],
+                               'ops': [
+                                       { 'type': 'replace',
+                                         'remove': [ { 'type': 'list' }, { 
'type': 'listItem', 'attributes': { 'styles': ['bullet'] } } ],
+                                         'insert': []
+                                       },
+                                       { 'type': 'retain', 'length': 3 },
+                                       { 'type': 'replace',
+                                         'remove': [ { 'type': '/listItem' }, 
{ 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } } ],
+                                         'insert': []
+                                       },
+                                       { 'type': 'retain', 'length': 3 },
+                                       { 'type': 'replace',
+                                         'remove': [ { 'type': '/listItem' }, 
{ 'type': '/list' } ],
+                                         'insert': []
+                                       }
                                ]
                        },
                        'replaces a table with a list': {
@@ -1094,6 +1116,48 @@
                                        { 'type': 'retain', 'length': 2 }
                                ]
                        },
+                       'metadata is preserved on wrap': {
+                               'args': [metaDoc, new ve.Range( 1, 10 ), [ { 
'type': 'paragraph' } ], [ { 'type': 'heading', 'level': 1 } ], [], [] ],
+                               'ops': [
+                                       { 'type': 'replace',
+                                         'remove': [ { 'type': 'paragraph' } ],
+                                         'insert': [ { 'type': 'heading', 
'level': 1 } ],
+                                         'insertMetadata': 
metaDoc.getMetadata().slice(0, 1),
+                                         'removeMetadata': 
metaDoc.getMetadata().slice(0, 1)
+                                       },
+                                       { 'type': 'retain', 'length': 9 },
+                                       { 'type': 'replace',
+                                         'remove': [ { 'type': '/paragraph' } 
],
+                                         'insert': [ { 'type': '/heading' } ]
+                                       },
+                                       { 'type': 'retain', 'length': 2 }
+                               ]
+                       },
+                       'metadata is preserved on unwrap': {
+                               'args': [listMetaDoc, new ve.Range( 1, 11 ), [ 
{ 'type': 'list' } ], [], [ { 'type': 'listItem', 'attributes': {'styles': 
['bullet']} } ], [] ],
+                               'ops': [
+                                       { 'type': 'replace',
+                                         'remove': [ { 'type': 'list' }, { 
'type': 'listItem', 'attributes': { 'styles': ['bullet'] } } ],
+                                         'insert': [],
+                                         'insertMetadata': 
ve.dm.MetaLinearData.static.merge( listMetaDoc.getMetadata().slice(0, 3) ),
+                                         'removeMetadata': 
listMetaDoc.getMetadata().slice(0, 3)
+                                       },
+                                       { 'type': 'retain', 'length': 3 },
+                                       { 'type': 'replace',
+                                         'remove': [ { 'type': '/listItem' }, 
{ 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } } ],
+                                         'insert': [],
+                                         'insertMetadata': 
ve.dm.MetaLinearData.static.merge( listMetaDoc.getMetadata().slice(5, 8) ),
+                                         'removeMetadata': 
listMetaDoc.getMetadata().slice(5, 8)
+                                       },
+                                       { 'type': 'retain', 'length': 3 },
+                                       { 'type': 'replace',
+                                         'remove': [ { 'type': '/listItem' }, 
{ 'type': '/list' } ],
+                                         'insert': [],
+                                         'insertMetadata': 
ve.dm.MetaLinearData.static.merge( listMetaDoc.getMetadata().slice(10, 13) ),
+                                         'removeMetadata': 
listMetaDoc.getMetadata().slice(10, 13)
+                                       }
+                               ]
+                       },
                        'checks integrity of unwrapOuter parameter': {
                                'args': [doc, new ve.Range( 13, 32 ), [ { 
'type': 'table' } ], [], [], []],
                                'exception': Error
diff --git a/modules/ve/test/dm/ve.dm.TransactionProcessor.test.js 
b/modules/ve/test/dm/ve.dm.TransactionProcessor.test.js
index 382fd25..c420b05 100644
--- a/modules/ve/test/dm/ve.dm.TransactionProcessor.test.js
+++ b/modules/ve/test/dm/ve.dm.TransactionProcessor.test.js
@@ -452,6 +452,22 @@
                                        data.splice( 2, 2 );
                                        data.splice( 4, 3 );
                                }
+                       },
+                       'preserves metadata on unwrap': {
+                               'data': ve.dm.example.listWithMeta,
+                               'calls': [
+                                       [ 'newFromWrap', new ve.Range( 1, 11 ),
+                                         [ { 'type': 'list' } ], [],
+                                         [ { 'type': 'listItem', 'attributes': 
{'styles': ['bullet']} } ], [] ]
+                               ],
+                               'expected': function ( data ) {
+                                       data.splice( 35, 1 ); // remove '/list'
+                                       data.splice( 32, 1 ); // remove 
'/listItem'
+                                       data.splice( 20, 1 ); // remove 
'listItem'
+                                       data.splice( 17, 1 ); // remove 
'/listItem'
+                                       data.splice(  5, 1 ); // remove 
'listItem'
+                                       data.splice(  2, 1 ); // remove 'list'
+                               }
                        }
                };
 
@@ -473,10 +489,15 @@
 
                tx = new ve.dm.Transaction();
                for ( i = 0; i < cases[msg].calls.length; i++ ) {
-                       // pushReplace needs the document as its first argument
-                       if ( cases[msg].calls[i][0] === 'pushReplace' ) {
+                       // some calls need the document as its first argument
+                       if ( /^(pushReplace$|new)/.test( cases[msg].calls[i][0] 
) ) {
                                cases[msg].calls[i].splice( 1, 0, testDoc );
                        }
+                       // special case static methods of Transaction
+                       if ( /^new/.test( cases[msg].calls[i][0] ) ) {
+                               tx = 
ve.dm.Transaction[cases[msg].calls[i][0]].apply( null, 
cases[msg].calls[i].slice( 1 ) );
+                               break;
+                       }
                        tx[cases[msg].calls[i][0]].apply( tx, 
cases[msg].calls[i].slice( 1 ) );
                }
 
diff --git a/modules/ve/test/dm/ve.dm.example.js 
b/modules/ve/test/dm/ve.dm.example.js
index b21fe09..0f0e676 100644
--- a/modules/ve/test/dm/ve.dm.example.js
+++ b/modules/ve/test/dm/ve.dm.example.js
@@ -589,6 +589,118 @@
 ];
 
 
+ve.dm.example.listWithMeta = [
+       //  0 - Beginning of list
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="one" />' ).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': 'list' },
+       //  1 - Beginning of first list item
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="two" />' ).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } },
+       //  2 - Beginning of paragraph
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="three" />' 
).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': 'paragraph' },
+       //  3 - Plain "a"
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="four" />' ).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       'a',
+       //  4 - End of paragraph
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="five" />' ).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': '/paragraph' },
+       //  5 - End of first list item
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="six" />' ).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': '/listItem' },
+       //  6 - Beginning of second list item
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="seven" />' 
).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } },
+       //  7 - Beginning of paragraph
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="eight" />' 
).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': 'paragraph' },
+       //  8 - Plain "b"
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="nine" />' ).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       'b',
+       //  9 - End of paragraph
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="ten" />' ).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': '/paragraph' },
+       // 10 - End of second list item
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="eleven" />' 
).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': '/listItem' },
+       // 11 - End of list
+       {
+               'type': 'alienMeta',
+               'attributes': {
+                       'domElements': $( '<meta property="twelve" />' 
).toArray()
+               }
+       },
+       { 'type': '/alienMeta' },
+       { 'type': '/list' }
+];
+
+
 ve.dm.example.complexTableHtml = 
'<table><caption>Foo</caption><thead><tr><th>Bar</th></tr></thead>' +
        
'<tfoot><tr><td>Baz</td></tr></tfoot><tbody><tr><td>Quux</td><td>Whee</td></tr></tbody></table>';
 

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I6cfec0d2b1823dad724422f018a3c73dc0c7f186
Gerrit-PatchSet: 3
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Cscott <canan...@wikimedia.org>
Gerrit-Reviewer: Catrope <roan.katt...@gmail.com>
Gerrit-Reviewer: Cscott <canan...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to