diff --git a/CHANGES.md b/CHANGES.md index 6d7d187c81a..ef7de3da7ee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ New Features: * [#607](https://github.com/ckeditor/ckeditor-dev/issues/607): Manually inserted hex color is prefixed with hash tag if needed. It ensures a valid hex color is used when setting table cell border or background color via [Color Dialog](http://ckeditor.com/addon/colordialog) window. * [#584](https://github.com/ckeditor/ckeditor-dev/issues/584): [Font size and Family](http://ckeditor.com/addon/font) and [Format](http://ckeditor.com/addon/format) drop-downs are not toggleable anymore. Default option to reset styles added. * [#856](https://github.com/ckeditor/ckeditor-dev/issues/856): Introduced [`CKEDITOR.tools.keystrokeToArray`](https://docs.ckeditor.com/ckeditor-docs/build/#!/api/CKEDITOR.tools-method-keystrokeToArray). It converts given keystroke into its string representation, returning every key name as separate array element. +* [#1053](https://github.com/ckeditor/ckeditor-dev/issues/1053): Introduced [`CKEDITOR.tools.object.merge`](https://docs.ckeditor.com/ckeditor4/docs/#!/api/CKEDITOR.tools.object-method-merge). It allows to merge two objects, returning the new object with all properties from both objects deeply cloned. Fixed Issues: diff --git a/core/tools.js b/core/tools.js index 1959f483e94..33bc7decc7a 100644 --- a/core/tools.js +++ b/core/tools.js @@ -1991,6 +1991,59 @@ } return null; + }, + + /** + * Merges two objects and returns new one. + * + * var obj1 = { + * a: 1, + * conflicted: 10, + * obj: { + * c: 1 + * } + * }, + * obj2 = { + * b: 2, + * conflicted: 20, + * obj: { + * d: 2 + * } + * }; + * + * CKEDITOR.tools.object.merge( obj1, obj2 ); + * + * This code produces the following object; + * + * { + * a: 1, + * b: 2, + * conflicted: 20, + * obj: { + * c: 1, + * d: 2 + * } + * } + * + * @param {Object} obj1 Source object, which will be used to create a new base object. + * @param {Object} obj2 An object, which properties will be merged to the base one. + * @returns {Object} Merged object. + * @member CKEDITOR.tools.object + */ + merge: function( obj1, obj2 ) { + var tools = CKEDITOR.tools, + copy1 = tools.clone( obj1 ), + copy2 = tools.clone( obj2 ); + + tools.array.forEach( tools.objectKeys( copy2 ), function( key ) { + if ( typeof copy2[ key ] === 'object' && typeof copy1[ key ] === 'object' ) { + copy1[ key ] = tools.object.merge( copy1[ key ], copy2[ key ] ); + } else { + copy1[ key ] = copy2[ key ]; + } + } ); + + return copy1; } } }; diff --git a/tests/_benderjs/ckeditor/static/extensions.js b/tests/_benderjs/ckeditor/static/extensions.js index 5000c87759f..1abf8b11581 100644 --- a/tests/_benderjs/ckeditor/static/extensions.js +++ b/tests/_benderjs/ckeditor/static/extensions.js @@ -135,6 +135,40 @@ assert.areSame( expected, bender.tools.compatHtml( actual, false, true, false, true, true ), message ); }; + /** + * Asserts that two objects are deep equal. + * + * @param {Object} expected + * @param {Object} actual + * @param {String} [message] + */ + bender.objectAssert.areDeepEqual = function( expected, actual, message ) { + // Based on http://yuilibrary.com/yui/docs/api/files/test_js_ObjectAssert.js.html#l12. + var expectedKeys = YUITest.Object.keys( expected ), + actualKeys = YUITest.Object.keys( actual ); + + YUITest.Assert._increment(); + + // First check keys array length. + if ( expectedKeys.length != actualKeys.length ) { + YUITest.Assert.fail( YUITest.Assert._formatMessage( message, + 'Object should have ' + expectedKeys.length + ' keys but has ' + actualKeys.length ) ); + } + + // Then check values. + for ( var name in expected ) { + if ( expected.hasOwnProperty( name ) ) { + if ( expected[ name ] && typeof expected[ name ] === 'object' ) { + bender.objectAssert.areDeepEqual( expected[ name ], actual[ name ] ); + } + else if ( expected[ name ] !== actual[ name ] ) { + throw new YUITest.ComparisonFailure( YUITest.Assert._formatMessage( message, + 'Values should be equal for property ' + name ), expected[ name ], actual[ name ] ); + } + } + } + }; + // Add support test ignore. YUITest.Ignore = function() {}; diff --git a/tests/core/tools/object.js b/tests/core/tools/object.js index 84798427a11..7f2cbdddf12 100644 --- a/tests/core/tools/object.js +++ b/tests/core/tools/object.js @@ -62,6 +62,68 @@ returned = this.object.findKey( inputObject, innerObject ); assert.areSame( returned, 'c' ); + }, + + // (#1053) + 'test object.merge': function() { + var obj1 = { + one: 1, + conflicted: 10, + falsy: false, + nully: null, + obj: { + nested1: 1, + nestedObj: { + a: 3 + } + }, + array: [ 1, 2 ] + }, + obj2 = { + two: 2, + conflicted: 20, + truthy: true, + undef: undefined, + obj: { + nested2: 2, + nestedObj: { + b: 4 + } + }, + array: [ 3, 4 ] + }, + expected = { + one: 1, + two: 2, + conflicted: 20, + falsy: false, + truthy: true, + nully: null, + undef: undefined, + obj: { + nested1: 1, + nested2: 2, + nestedObj: { + a: 3, + b: 4 + } + }, + array: [ 3, 4 ] + }, + actual = this.object.merge( obj1, obj2 ); + + assert.areNotSame( obj1, actual, 'Merging does not modify obj1' ); + assert.areNotSame( obj2, actual, 'Merging does not modify obj2' ); + objectAssert.areDeepEqual( expected, actual, 'Merging produces correct object' ); + + // Reversed merge should produce same object, but with different conflicted and array properties. + expected.conflicted = 10; + expected.array = [ 1, 2 ]; + actual = this.object.merge( obj2, obj1 ); + + assert.areNotSame( obj1, actual, 'Merging does not modify obj1' ); + assert.areNotSame( obj2, actual, 'Merging does not modify obj2' ); + objectAssert.areDeepEqual( expected, actual, 'Merging produces correct object' ); } } ); } )(); diff --git a/tests/plugins/copyformatting/_helpers/tools.js b/tests/plugins/copyformatting/_helpers/tools.js index c739a1f9d88..2b0662b2eaa 100644 --- a/tests/plugins/copyformatting/_helpers/tools.js +++ b/tests/plugins/copyformatting/_helpers/tools.js @@ -4,34 +4,6 @@ 'use strict'; -// Based on http://yuilibrary.com/yui/docs/api/files/test_js_ObjectAssert.js.html#l12. -YUITest.ObjectAssert.areDeepEqual = function( expected, actual, message ) { - var expectedKeys = YUITest.Object.keys( expected ), - actualKeys = YUITest.Object.keys( actual ), - areEqual = YUITest.ObjectAssert.areEqual; - - YUITest.Assert._increment(); - - // First check keys array length. - if ( expectedKeys.length != actualKeys.length ) { - YUITest.Assert.fail( YUITest.Assert._formatMessage( message, - 'Object should have ' + expectedKeys.length + ' keys but has ' + actualKeys.length ) ); - } - - // Then check values. - for ( var name in expected ) { - if ( expected.hasOwnProperty( name ) ) { - if ( typeof expected[ name ] === 'object' ) { - areEqual( expected[ name ], actual[ name ] ); - } - else if ( expected[ name ] !== actual[ name ] ) { - throw new YUITest.ComparisonFailure( YUITest.Assert._formatMessage( message, - 'Values should be equal for property ' + name ), expected[ name ], actual[ name ] ); - } - } - } -}; - // Safari and IE8 use text selection and all other browsers use element selection. Therefore we must normalize it. function fixHtml( html ) { if ( ( CKEDITOR.env.webkit && !CKEDITOR.env.chrome ) || ( CKEDITOR.env.ie && CKEDITOR.env.version === 8 ) ) {