From 4f0ac479edfe44bf49d0a7cbf62e5d90d949452f Mon Sep 17 00:00:00 2001 From: Ed Sanders Date: Fri, 22 Feb 2013 12:49:13 -0800 Subject: [PATCH] Implement SurfaceFragment.unwrapAllNodes and fix wrapAllNodes. wrapAllNodes was calulating the new selection incorrectly. This has been fixed and a test added. unwrapAllNodes takes a depth as its argument and unwraps that many elements from inside the selection. Tests for wrap/unwrap apply also now check that applying a wrap and then its inverse as an unwrap result in the document reverting to its original state. Change-Id: I7dcacdfb5894be59ffad69b369d7b32933a25b61 --- modules/ve/dm/ve.dm.SurfaceFragment.js | 39 +++++++++++++++---- .../ve/test/dm/ve.dm.SurfaceFragment.test.js | 35 +++++++++++++++-- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/modules/ve/dm/ve.dm.SurfaceFragment.js b/modules/ve/dm/ve.dm.SurfaceFragment.js index c2080992ac..2ba5a145bf 100644 --- a/modules/ve/dm/ve.dm.SurfaceFragment.js +++ b/modules/ve/dm/ve.dm.SurfaceFragment.js @@ -642,30 +642,53 @@ ve.dm.SurfaceFragment.prototype.wrapAllNodes = function ( wrapper ) { if ( !this.surface ) { return this; } + + var tx, newRange; + if ( !ve.isArray( wrapper ) ) { wrapper = [wrapper]; } - var tx = ve.dm.Transaction.newFromWrap( this.document, this.range, [], wrapper, [], [] ); - this.range = tx.translateRange( this.range ); - this.surface.change( tx, !this.noAutoSelect && this.range ); + + newRange = new ve.Range( this.range.start, this.range.end + ( wrapper.length * 2 ) ); + + tx = ve.dm.Transaction.newFromWrap( this.document, this.range, [], wrapper, [], [] ); + this.surface.change( tx, !this.noAutoSelect && newRange ); + + this.range = newRange; + return this; }; /** * Unwrap nodes in the fragment out of one or more elements. * - * TODO: Figure out what the arguments for this function should be - * * @method - * @param {string|string[]} type Node types to unwrap, or array of node types to unwrap + * @param {number} depth Number of nodes to unwrap * @returns {ve.dm.SurfaceFragment} This fragment */ -ve.dm.SurfaceFragment.prototype.unwrapAllNodes = function () { +ve.dm.SurfaceFragment.prototype.unwrapAllNodes = function ( depth ) { // Handle null fragment if ( !this.surface ) { return this; } - // TODO: Implement + var i, tx, newRange, wrapper = [], + innerRange = new ve.Range( this.range.start + depth, this.range.end - depth ); + + if ( this.range.end - this.range.start < depth * 2 ) { + throw new Error( 'cannot unwrap by greater depth than maximum theoretical depth of selection' ); + } + + for ( i = 0; i < depth; i++ ) { + wrapper.push( this.surface.getDocument().data[this.range.start + i] ); + } + + newRange = new ve.Range( this.range.start, this.range.end - ( depth * 2 ) ); + + tx = ve.dm.Transaction.newFromWrap( this.document, innerRange, wrapper, [], [], [] ); + this.surface.change( tx, !this.noAutoSelect && newRange ); + + this.range = newRange; + return this; }; diff --git a/modules/ve/test/dm/ve.dm.SurfaceFragment.test.js b/modules/ve/test/dm/ve.dm.SurfaceFragment.test.js index 88c2a5bb30..2e124f568d 100644 --- a/modules/ve/test/dm/ve.dm.SurfaceFragment.test.js +++ b/modules/ve/test/dm/ve.dm.SurfaceFragment.test.js @@ -174,10 +174,12 @@ QUnit.test( 'wrapNodes', 2, function ( assert ) { ); } ); -QUnit.test( 'wrapAllNodes', 2, function ( assert ) { +QUnit.test( 'wrapAllNodes/unwrapAllNodes', 10, function ( assert ) { var doc = new ve.dm.Document( ve.copyArray( ve.dm.example.data ) ), surface = new ve.dm.Surface( doc ), - fragment = new ve.dm.SurfaceFragment( surface, new ve.Range( 55, 61 ) ); + fragment = new ve.dm.SurfaceFragment( surface, new ve.Range( 55, 61 ) ), + expectedData; + // Make 2 paragraphs into 1 lists of 1 item with 2 paragraphs fragment.wrapAllNodes( [{ 'type': 'list', 'attributes': { 'style': 'bullet' } }, { 'type': 'listItem' }] @@ -202,6 +204,12 @@ QUnit.test( 'wrapAllNodes', 2, function ( assert ) { ], 'wrapping nodes can add multiple levels of wrapping to multiple elements' ); + assert.deepEqual( fragment.getRange(), new ve.Range( 55, 65 ), 'new range contains wrapping elements' ); + + fragment.unwrapAllNodes( 2 ); + assert.deepEqual( doc.getData(), ve.dm.example.data, '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' ); + // Make a 1 paragraph into 1 list with 1 item fragment = new ve.dm.SurfaceFragment( surface, new ve.Range( 9, 12 ) ); fragment.wrapAllNodes( @@ -224,7 +232,28 @@ QUnit.test( 'wrapAllNodes', 2, function ( assert ) { ], 'wrapping nodes can add multiple levels of wrapping to a single element' ); -} ); + assert.deepEqual( fragment.getRange(), new ve.Range( 9, 16 ), 'new range contains wrapping elements' ); + + fragment.unwrapAllNodes( 2 ); + assert.deepEqual( doc.getData(), ve.dm.example.data, '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 = new ve.dm.SurfaceFragment( surface, new ve.Range( 5, 37 ) ); + + assert.throws( function() { + fragment.unwrapAllNodes( 20 ); + }, /cannot unwrap by greater depth/, 'error thrown trying to unwrap more nodes that it is possible to contain' ); + + expectedData = ve.copyArray( ve.dm.example.data ); + expectedData.splice( 5, 4 ); + expectedData.splice( 29, 4 ); + fragment.unwrapAllNodes( 4 ); + assert.deepEqual( + doc.getData(), + expectedData, + 'unwrapping 4 levels (table, tableSection, tableRow and tableCell)' + ); +}); function runIsolateTest( assert, range, expected, label ) { var doc = new ve.dm.Document( ve.copyArray( ve.dm.example.isolationData ) ),