Browse Source

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

Aleksander Nowodzinski 10 years ago
parent
commit
a3667bb8c6

+ 1 - 2
packages/ckeditor5-engine/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-engine/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.

+ 2 - 2
packages/ckeditor5-engine/src/treemodel/document.js

@@ -10,7 +10,7 @@ import Batch from './batch.js';
 import Selection from './selection.js';
 import EmitterMixin from '../emittermixin.js';
 import CKEditorError from '../ckeditorerror.js';
-import objectUtils from '../lib/lodash/object.js';
+import utils from '../utils.js';
 
 const graveyardSymbol = Symbol( 'graveyard' );
 
@@ -229,4 +229,4 @@ export default class Document {
 	 */
 }
 
-objectUtils.extend( Document.prototype, EmitterMixin );
+utils.mix( Document, EmitterMixin );

+ 2 - 2
packages/ckeditor5-engine/src/treemodel/liveposition.js

@@ -8,7 +8,7 @@
 import Position from './position.js';
 import Range from './range.js';
 import EmitterMixin from '../emittermixin.js';
-import objectUtils from '../lib/lodash/object.js';
+import utils from '../utils.js';
 
 /**
  * Enum representing how position is "sticking" with their neighbour nodes.
@@ -167,4 +167,4 @@ function transform( type, range, position ) {
 	this.root = transformed.root;
 }
 
-objectUtils.extend( LivePosition.prototype, EmitterMixin );
+utils.mix( LivePosition, EmitterMixin );

+ 2 - 2
packages/ckeditor5-engine/src/treemodel/liverange.js

@@ -8,7 +8,7 @@
 import Range from './range.js';
 import LivePosition from './liveposition.js';
 import EmitterMixin from '../emittermixin.js';
-import objectUtils from '../lib/lodash/object.js';
+import utils from '../utils.js';
 
 /**
  * LiveRange is a Range in the Tree Model that updates itself as the tree changes. It may be used as a bookmark.
@@ -134,4 +134,4 @@ function fixBoundaries( type, range, position ) {
 	}
 }
 
-objectUtils.extend( LiveRange.prototype, EmitterMixin );
+utils.mix( LiveRange, EmitterMixin );

+ 2 - 2
packages/ckeditor5-engine/src/treemodel/selection.js

@@ -9,7 +9,7 @@ import LiveRange from './liverange.js';
 import AttributeList from './attributelist.js';
 import EmitterMixin from '../emittermixin.js';
 import CKEditorError from '../ckeditorerror.js';
-import objectUtils from '../lib/lodash/object.js';
+import utils from '../utils.js';
 
 /**
  * Represents a selection that is made on nodes in {@link treeModel.Document}. Selection instance is
@@ -199,4 +199,4 @@ function pushRange( range ) {
 	this._ranges.push( LiveRange.createFromRange( range ) );
 }
 
-objectUtils.extend( Selection.prototype, EmitterMixin );
+utils.mix( Selection, EmitterMixin );

+ 2 - 2
packages/ckeditor5-engine/src/ui/view.js

@@ -11,7 +11,7 @@ import Region from './region.js';
 import Template from './template.js';
 import CKEditorError from '../ckeditorerror.js';
 import DOMEmitterMixin from './domemittermixin.js';
-import objectUtils from '../lib/lodash/object.js';
+import utils from '../utils.js';
 
 /**
  * Basic View class.
@@ -386,7 +386,7 @@ export default class View extends Model {
 	}
 }
 
-objectUtils.extend( View.prototype, DOMEmitterMixin );
+utils.mix( View, DOMEmitterMixin );
 
 const validSelectorTypes = new Set( [ 'string', 'boolean', 'function' ] );
 

+ 33 - 0
packages/ckeditor5-engine/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-engine/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' );
+		} );
+	} );
 } );