Explorar el Código

Merge branch 't/18c'. Closes #18.

Aleksander Nowodzinski hace 10 años
padre
commit
3bc0f59b61

+ 1 - 2
packages/ckeditor5-utils/src/collection.js

@@ -7,7 +7,6 @@
 
 import EmitterMixin from './emittermixin.js';
 import CKEditorError from './ckeditorerror.js';
-import utilsObject from './lib/lodash/object.js';
 import utils from './utils.js';
 
 /**
@@ -266,7 +265,7 @@ export default class Collection {
 	}
 }
 
-utilsObject.extend( Collection.prototype, EmitterMixin );
+utils.mix( Collection, EmitterMixin );
 
 /**
  * Fired when an item is added to the collection.

+ 2 - 1
packages/ckeditor5-utils/src/model.js

@@ -9,6 +9,7 @@ import EmitterMixin from './emittermixin.js';
 import CKEditorError from './ckeditorerror.js';
 import utilsObject from './lib/lodash/object.js';
 import utilsLang from './lib/lodash/lang.js';
+import utils from './utils.js';
 
 /**
  * The base MVC model class.
@@ -625,7 +626,7 @@ function attachBindToListeners( model, toBindings ) {
 	} );
 }
 
-utilsObject.extend( Model.prototype, EmitterMixin );
+utils.mix( Model, EmitterMixin );
 
 /**
  * Fired when an attribute changed value.

+ 33 - 0
packages/ckeditor5-utils/src/utils.js

@@ -107,6 +107,39 @@ const utils = {
 		}
 
 		return null;
+	},
+
+	/**
+	 * Copies enumerable properties and symbols from the objects given as 2nd+ parameters to the
+	 * prototype of first object (a constructor).
+	 *
+	 *		class Editor {
+	 *			...
+	 *		}
+	 *
+	 *		const SomeMixin = {
+	 *			a() {
+	 *				return 'a';
+	 *			}
+	 *		};
+	 *
+	 *		utils.mix( Editor, SomeMixin, ... );
+	 *
+	 *		new Editor().a(); -> 'a'
+	 *
+	 * @param {Function} [baseClass] Class which prototype will be extended.
+	 * @param {Object} [...mixins] Objects from which to get properties.
+	 */
+	mix( baseClass, ...mixins ) {
+		mixins.forEach( ( mixin ) => {
+			Object.getOwnPropertyNames( mixin ).concat( Object.getOwnPropertySymbols( mixin ) )
+				.forEach( ( key ) => {
+					const sourceDescriptor = Object.getOwnPropertyDescriptor( mixin, key );
+					sourceDescriptor.enumerable = false;
+
+					Object.defineProperty( baseClass.prototype, key, sourceDescriptor );
+				} );
+		} );
 	}
 };
 

+ 117 - 0
packages/ckeditor5-utils/tests/utils.js

@@ -143,4 +143,121 @@ describe( 'utils', () => {
 			yield 33;
 		}
 	} );
+
+	describe( 'mix', () => {
+		const MixinA = {
+			a() {
+				return 'a';
+			}
+		};
+		const MixinB = {
+			b() {
+				return 'b';
+			}
+		};
+
+		it( 'mixes 2nd+ param\'s properties into the first class', () => {
+			class Foo {}
+			utils.mix( Foo, MixinA, MixinB );
+
+			expect( Foo ).to.not.have.property( 'a' );
+			expect( Foo ).to.not.have.property( 'b' );
+
+			const foo = new Foo();
+
+			expect( foo.a() ).to.equal( 'a' );
+			expect( foo.b() ).to.equal( 'b' );
+		} );
+
+		it( 'does not break the instanceof operator', () => {
+			class Foo {}
+			utils.mix( Foo, MixinA );
+
+			let foo = new Foo();
+
+			expect( foo ).to.be.instanceof( Foo );
+		} );
+
+		it( 'defines properties with the same descriptors as native classes', () => {
+			class Foo {
+				foo() {}
+			}
+
+			utils.mix( Foo, MixinA );
+
+			const actualDescriptor = Object.getOwnPropertyDescriptor( Foo.prototype, 'a' );
+			const expectedDescriptor = Object.getOwnPropertyDescriptor( Foo.prototype, 'foo' );
+
+			expect( actualDescriptor ).to.have.property( 'writable', expectedDescriptor.writable );
+			expect( actualDescriptor ).to.have.property( 'enumerable', expectedDescriptor.enumerable );
+			expect( actualDescriptor ).to.have.property( 'configurable', expectedDescriptor.configurable );
+		} );
+
+		it( 'copies setters and getters (with descriptors as of native classes)', () => {
+			class Foo {
+				set foo( v ) {
+					this._foo = v;
+				}
+				get foo() {}
+			}
+
+			const Mixin = {
+				set a( v ) {
+					this._a = v;
+				},
+				get a() {
+					return this._a;
+				}
+			};
+
+			utils.mix( Foo, Mixin );
+
+			const actualDescriptor = Object.getOwnPropertyDescriptor( Foo.prototype, 'a' );
+			const expectedDescriptor = Object.getOwnPropertyDescriptor( Foo.prototype, 'foo' );
+
+			expect( actualDescriptor ).to.have.property( 'enumerable', expectedDescriptor.enumerable );
+			expect( actualDescriptor ).to.have.property( 'configurable', expectedDescriptor.configurable );
+
+			const foo = new Foo();
+
+			foo.a = 1;
+			expect( foo._a ).to.equal( 1 );
+
+			foo._a = 2;
+			expect( foo.a ).to.equal( 2 );
+		} );
+
+		it( 'copies symbols (with descriptors as of native classes)', () => {
+			const symbolA = Symbol( 'a' );
+			const symbolFoo = Symbol( 'foo' );
+
+			// https://github.com/jscs-dev/node-jscs/issues/2078
+			// jscs:disable disallowSpacesInFunction
+			class Foo {
+				[ symbolFoo ]() {
+					return 'foo';
+				}
+			}
+
+			const Mixin = {
+				[ symbolA ]() {
+					return 'a';
+				}
+			};
+			// jscs:enable disallowSpacesInFunction
+
+			utils.mix( Foo, Mixin );
+
+			const actualDescriptor = Object.getOwnPropertyDescriptor( Foo.prototype, symbolA );
+			const expectedDescriptor = Object.getOwnPropertyDescriptor( Foo.prototype, symbolFoo );
+
+			expect( actualDescriptor ).to.have.property( 'writable', expectedDescriptor.writable );
+			expect( actualDescriptor ).to.have.property( 'enumerable', expectedDescriptor.enumerable );
+			expect( actualDescriptor ).to.have.property( 'configurable', expectedDescriptor.configurable );
+
+			const foo = new Foo();
+
+			expect( foo[ symbolA ]() ).to.equal( 'a' );
+		} );
+	} );
 } );