jenkins-bot has submitted this change and it was merged. Change subject: ve.sparseSplice: Splice one array into another, replicating any holes ......................................................................
ve.sparseSplice: Splice one array into another, replicating any holes Change-Id: I6a06cf90d7036c8bdadadd74c4a667b36e54d72b --- M src/ve.utils.js M tests/ve.test.js 2 files changed, 123 insertions(+), 1 deletion(-) Approvals: Tchanders: Looks good to me, approved jenkins-bot: Verified diff --git a/src/ve.utils.js b/src/ve.utils.js index 013d85a..37d157c 100644 --- a/src/ve.utils.js +++ b/src/ve.utils.js @@ -224,7 +224,7 @@ * Includes a replacement for broken implementations of Array.prototype.splice(). * * @param {Array|ve.dm.BranchNode} arr Target object (must have `splice` method, object will be modified) - * @param {number} offset Offset in arr to splice at. This may NOT be negative, unlike the + * @param {number} offset Offset in arr to splice at. This MUST NOT be negative, unlike the * 'index' parameter in Array#splice. * @param {number} remove Number of elements to remove at the offset. May be zero * @param {Array} data Array of items to insert at the offset. Must be non-empty if remove=0 @@ -287,6 +287,56 @@ }; /** + * Splice one array into another, replicating any holes + * + * Similar to arr.splice.apply( arr, [ offset, remove ].concat( data ) ), except holes in + * data remain holes in arr. Optimized for length changes that are negative, zero, or + * fairly small positive. + * + * @param {Array} arr Array to modify + * @param {number} offset Offset in arr to splice at. This MUST NOT be negative, unlike the + * 'index' parameter in Array#splice. + * @param {number} remove Number of elements to remove at the offset. May be zero + * @param {Array} data Array of items to insert at the offset + * @return {Array} Array of items removed, with holes preserved + */ +ve.sparseSplice = function ( arr, offset, remove, data ) { + var i, + removed = [], + endOffset = offset + remove, + diff = data.length - remove; + if ( data === arr ) { + // Pathological case: arr and data are reference-identical + data = data.slice(); + } + // Remove content without adjusting length + arr.slice( offset, endOffset ).forEach( function ( item, i ) { + removed[ i ] = item; + delete arr[ offset + i ]; + } ); + // Adjust length + if ( diff > 0 ) { + // Grow with undefined values, then delete. (This is optimised for diff + // comparatively small: otherwise, it would sometimes be quicker to relocate + // each element of arr that lies above offset). + ve.batchSplice( arr, endOffset, 0, new Array( diff ) ); + for ( i = endOffset + diff - 1; i >= endOffset; i-- ) { + delete arr[ i ]; + } + } else if ( diff < 0 ) { + // Shrink + arr.splice( offset, -diff ); + } + // Insert new content + data.forEach( function ( item, i ) { + arr[ offset + i ] = item; + } ); + // Set removed.length in case there are holes at the end + removed.length = remove; + return removed; +}; + +/** * Insert one array into another. * * Shortcut for `ve.batchSplice( arr, offset, 0, src )`. diff --git a/tests/ve.test.js b/tests/ve.test.js index 8555a38..3070b87 100644 --- a/tests/ve.test.js +++ b/tests/ve.test.js @@ -266,6 +266,78 @@ ); } ); +QUnit.test( 'sparseSplice', function ( assert ) { + var tests, i, len, test; + // Convert a sparse array of primitives to an array of strings, with '' for holes. + // This is needed because QUnit.equiv treats holes as equivalent to undefined. + function mapToString( flatArray ) { + var j, jLen, + strings = []; + for ( j = 0, jLen = flatArray.length; j < jLen; j++ ) { + strings.push( flatArray.hasOwnProperty( j ) ? String( flatArray[ j ] ) : '' ); + } + return strings; + } + function runTest( arr, offset, remove, data, expectedReturn, expectedArray, msg ) { + var observedReturn, + testArr = arr.slice(); + + observedReturn = ve.sparseSplice( testArr, offset, remove, data ); + assert.deepEqual( + mapToString( observedReturn ), + mapToString( expectedReturn ), + msg + ': return' + ); + assert.deepEqual( + mapToString( testArr ), + mapToString( expectedArray ), + msg + ': modification' + ); + } + tests = [ + /*jshint elision:true */ + // jscs:disable disallowTrailingComma + // jscs:disable disallowSpaceBeforeBinaryOperators + // arr, offset, remove, data, expectedReturn, expectedArray, msg + [ [], 0, 0, [ , 3 ], [], [ , 3 ], 'insert empty, leading hole' ], + [ [], 0, 0, [ 1, , 3 ], [], [ 1, , 3 ], 'insert empty, middle hole' ], + // Note: the first trailing comma does not create a hole + [ [], 0, 0, [ 1, , ], [], [ 1, , ], 'insert empty, trailing hole' ], + [ [ 4, , 5 ], 0, 0, [ 1, , 3 ], [], [ 1, , 3, 4, , 5 ], 'insert start' ], + [ [ 0, , 4 ], 1, 0, [ 1, , 3 ], [], [ 0, 1, , 3, , 4 ], 'insert mid' ], + [ [ 0, , 4 ], 3, 0, [ 1, , 3 ], [], [ 0, , 4, 1, , 3 ], 'insert end' ], + + [ [ 4, , 5, , 6 ], 0, 4, [ 1, , 3 ], [ 4, , 5, , ], [ 1, , 3, 6 ], 'diff<0 start' ], + [ [ 4, , , 5, , 6 ], 1, 4, [ 1, , 3 ], [ , , 5, , ], [ 4, 1, , 3, 6 ], 'diff<0 mid' ], + [ [ 4, , 5, , 6 ], 1, 4, [ 1, , 3 ], [ , 5, , 6 ], [ 4, 1, , 3 ], 'diff<0 end' ], + + [ [ 4, , 5, , 6 ], 0, 2, [ 1, , 3 ], [ 4, , ], [ 1, , 3, 5, , 6 ], 'diff>0 start' ], + [ [ 4, , 5, , 6 ], 1, 2, [ 1, , 3 ], [ , 5 ], [ 4, 1, , 3, , 6 ], 'diff>0 mid' ], + [ [ 4, , 5, , 6 ], 3, 2, [ 1, , 3 ], [ , 6 ], [ 4, , 5, 1, , 3 ], 'diff>0 end' ], + + [ [ 4, , 5, , 6 ], 0, 3, [ 1, , 3 ], [ 4, , 5 ], [ 1, , 3, , 6 ], 'diff=0 start' ], + [ [ 4, , 5, , 6 ], 1, 3, [ 1, , 3 ], [ , 5, , ], [ 4, 1, , 3, 6 ], 'diff=0 mid' ], + [ [ 4, , 5, , 6 ], 2, 3, [ 1, , 3 ], [ 5, , 6 ], [ 4, , 1, , 3 ], 'diff=0 end' ] + // jscs:enable disallowSpaceBeforeBinaryOperators + // jscs:enable disallowTrailingComma + /*jshint elision:false */ + ]; + QUnit.expect( 2 * tests.length + 1 ); + assert.notDeepEqual( + /*jshint elision:true */ + // jscs:disable disallowTrailingComma + mapToString( [ 1, , ] ), + // jscs:enable disallowTrailingComma + /*jshint elision:false */ + mapToString( [ 1, undefined ] ), + 'holes look different to undefined' + ); + for ( i = 0, len = tests.length; i < len; i++ ) { + test = tests[ i ]; + runTest.apply( null, test ); + } +} ); + QUnit.test( 'batchSplice', function ( assert ) { var spliceWasSupported = ve.supportsSplice; -- To view, visit https://gerrit.wikimedia.org/r/315657 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I6a06cf90d7036c8bdadadd74c4a667b36e54d72b Gerrit-PatchSet: 2 Gerrit-Project: VisualEditor/VisualEditor Gerrit-Branch: master Gerrit-Owner: Divec <da...@troi.org> Gerrit-Reviewer: Divec <da...@troi.org> Gerrit-Reviewer: Tchanders <thalia.e.c...@googlemail.com> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits