瀏覽代碼

Closes #49. Merge branch 't/49'

Aleksander Nowodzinski 10 年之前
父節點
當前提交
62beb82b74

+ 40 - 8
packages/ckeditor5-ui/src/collection.js

@@ -58,16 +58,10 @@ CKEDITOR.define( [ 'emittermixin', 'ckeditorerror', 'utils' ], function( Emitter
 		 * Gets one item from the collection.
 		 *
 		 * @param {Number} index The index to take the item from.
-		 * @returns {Model} The requested item.
+		 * @returns {Model} The requested item or `null` if such item does not exist.
 		 */
 		get( index ) {
-			var model = this._models[ index ];
-
-			if ( !model ) {
-				throw new Error( 'Index not found' );
-			}
-
-			return model;
+			return this._models[ index ] || null;
 		}
 
 		/**
@@ -106,6 +100,44 @@ CKEDITOR.define( [ 'emittermixin', 'ckeditorerror', 'utils' ], function( Emitter
 
 			return removedModel;
 		}
+
+		/**
+		 * Executes the callback for each model in the collection.
+		 *
+		 * @param {Function} callback
+		 * @param {Model} callback.item
+		 * @param {String} callback.index
+		 * @params {Object} ctx Context in which the `callback` will be called.
+		 */
+		forEach( callback, ctx ) {
+			this._models.forEach( callback, ctx );
+		}
+
+		/**
+		 * Finds the first item in the collection for which the `callback` returns a true value.
+		 *
+		 * @param {Function} callback
+		 * @param {Model} callback.item
+		 * @param {String} callback.name
+		 * @returns {Model} The item for which `callback` returned a true value.
+		 * @params {Object} ctx Context in which the `callback` will be called.
+		 */
+		find( callback, ctx ) {
+			return this._models.find( callback, ctx );
+		}
+
+		/**
+		 * Returns an array with items for which the `callback` returned a true value.
+		 *
+		 * @param {Function} callback
+		 * @param {Model} callback.item
+		 * @param {String} callback.name
+		 * @params {Object} ctx Context in which the `callback` will be called.
+		 * @returns {Model[]} The array with matching items.
+		 */
+		filter( callback, ctx ) {
+			return this._models.filter( callback, ctx );
+		}
 	}
 
 	utils.extend( Collection.prototype, EmitterMixin );

+ 45 - 16
packages/ckeditor5-ui/src/namedcollection.js

@@ -77,22 +77,10 @@ CKEDITOR.define( [ 'emittermixin', 'ckeditorerror', 'utils' ], function( Emitter
 		 * Gets one item from the collection.
 		 *
 		 * @param {String} name The name of the item to take.
-		 * @returns {Model} The requested item.
+		 * @returns {Model} The requested item or `null` if such item does not exist.
 		 */
 		get( name ) {
-			var model = this._models.get( name );
-
-			if ( !model ) {
-				/**
-				 * Model not found.
-				 *
-				 * @error namedcollection-get
-				 * @param {String} name Name of the model.
-				 */
-				throw new CKEditorError( 'namedcollection-get: Model not found.', { name: name } );
-			}
-
-			return model;
+			return this._models.get( name ) || null;
 		}
 
 		/**
@@ -139,10 +127,51 @@ CKEDITOR.define( [ 'emittermixin', 'ckeditorerror', 'utils' ], function( Emitter
 		 *
 		 * @param {Function} callback
 		 * @param {Model} callback.item
+		 * @param {String} callback.index
+		 * @params {Object} ctx Context in which the `callback` will be called.
+		 */
+		forEach( callback, ctx ) {
+			this._models.forEach( callback, ctx );
+		}
+
+		/**
+		 * Finds the first item in the collection for which the `callback` returns a true value.
+		 *
+		 * @param {Function} callback
+		 * @param {Model} callback.item
 		 * @param {String} callback.name
+		 * @params {Object} ctx Context in which the `callback` will be called.
+		 * @returns {Model} The first item for which `callback` returned a true value.
 		 */
-		forEach( callback ) {
-			this._models.forEach( callback );
+		find( callback, ctx ) {
+			// TODO: Use ES6 destructuring.
+			for ( let pair of this._models ) {
+				if ( callback.call( ctx, pair[ 1 ], pair[ 0 ] ) ) {
+					return pair[ 1 ];
+				}
+			}
+		}
+
+		/**
+		 * Returns an object (`name => item`) with items for which the `callback` returned a true value.
+		 *
+		 * @param {Function} callback
+		 * @param {Model} callback.item
+		 * @param {String} callback.name
+		 * @params {Object} ctx Context in which the `callback` will be called.
+		 * @returns {Object} The object with matching items.
+		 */
+		filter( callback, ctx ) {
+			var ret = {};
+
+			// TODO: Use ES6 destructuring.
+			for ( let pair of this._models ) {
+				if ( callback.call( ctx, pair[ 1 ], pair[ 0 ] ) ) {
+					ret[ pair[ 0 ] ] = pair[ 1 ];
+				}
+			}
+
+			return ret;
 		}
 	}
 

+ 58 - 4
packages/ckeditor5-ui/tests/mvc/collection/collection.js

@@ -7,6 +7,8 @@
 
 var modules = bender.amd.require( 'collection', 'model' );
 
+bender.tools.createSinonSandbox();
+
 describe( 'add', function() {
 	it( 'should change the length and enable get', function() {
 		var Model = modules.model;
@@ -36,13 +38,11 @@ describe( 'add', function() {
 } );
 
 describe( 'get', function() {
-	it( 'should throw an error on invalid index', function() {
+	it( 'should return null if index does not exist', function() {
 		var box = getCollection();
 		box.add( getItem() );
 
-		expect( function() {
-			box.get( 1 );
-		} ).to.throw( Error, 'Index not found' );
+		expect( box.get( 1 ) ).to.be.null;
 	} );
 } );
 
@@ -116,6 +116,60 @@ describe( 'remove', function() {
 	} );
 } );
 
+describe( 'forEach', function() {
+	it( 'uses native forEach', function() {
+		var collection = getCollection();
+		collection.add( getItem() );
+
+		var spy = bender.sinon.spy( Array.prototype, 'forEach' );
+		var ctx = {};
+
+		collection.forEach( callback, ctx );
+
+		sinon.assert.calledWithExactly( spy, callback, ctx );
+
+		function callback() {}
+	} );
+} );
+
+describe( 'find', function() {
+	it( 'uses native find', function() {
+		var collection = getCollection();
+		var needl = getItem();
+
+		var spy = bender.sinon.stub( Array.prototype, 'find', function() {
+			return needl;
+		} );
+		var ctx = {};
+
+		var ret = collection.find( callback, ctx );
+
+		sinon.assert.calledWithExactly( spy, callback, ctx );
+		expect( ret ).to.equal( needl, 'ret value was forwarded' );
+
+		function callback() {}
+	} );
+} );
+
+describe( 'filter', function() {
+	it( 'uses native filter', function() {
+		var collection = getCollection();
+		var needl = getItem();
+
+		var spy = bender.sinon.stub( Array.prototype, 'filter', function() {
+			return needl;
+		} );
+		var ctx = {};
+
+		var ret = collection.filter( callback, ctx );
+
+		sinon.assert.calledWithExactly( spy, callback, ctx );
+		expect( ret ).to.equal( needl, 'ret value was forwarded' );
+
+		function callback() {}
+	} );
+} );
+
 function getCollection() {
 	var Collection = modules.collection;
 

+ 64 - 15
packages/ckeditor5-ui/tests/mvc/collection/namedcollection.js

@@ -63,14 +63,10 @@ describe( 'add', function() {
 
 describe( 'get', function() {
 	it( 'should throw an error on invalid name', function() {
-		var CKEditorError = modules.ckeditorerror;
 		var box = getCollection();
-
 		box.add( getItem( 'foo' ) );
 
-		expect( function() {
-			box.get( 'bar' );
-		} ).to.throw( CKEditorError, /^namedcollection-get/ );
+		expect( box.get( 'bar' ) ).to.be.null;
 	} );
 } );
 
@@ -172,20 +168,73 @@ describe( 'remove', function() {
 } );
 
 describe( 'forEach', function() {
-	it( 'should iterate over the models', function() {
-		var box = getCollection();
-		var item1 = getItem( 'foo' );
-		var item2 = getItem( 'bar' );
+	it( 'executes callback for each item', function() {
+		var collection = getCollection();
+		collection.add( getItem( 'foo' ) );
+		collection.add( getItem( 'bar' ) );
+
+		var ctx = {};
+		var models = [];
+		var names = [];
+
+		collection.forEach( callback, ctx );
+		expect( models.sort() ).deep.equals( [ 'bar', 'foo' ] );
+		expect( names.sort() ).deep.equals( [ 'bar', 'foo' ] );
+
+		function callback( model, name ) {
+			expect( this ).to.equal( ctx ); /* jshint ignore:line */
+			models.push( model.name );
+			names.push( name );
+		}
+	} );
+} );
 
-		box.add( item1 );
-		box.add( item2 );
+describe( 'find', function() {
+	it( 'finds the right item', function() {
+		var Model = modules.model;
 
-		expect( box ).to.have.length( 2 );
+		var collection = getCollection();
+		var needl = getItem( 'foo' );
+		collection.add( getItem( 'bar' ) );
+		collection.add( needl );
+		collection.add( getItem( 'bom' ) );
 
-		var spy = sinon.spy();
-		box.forEach( spy );
+		var ctx = {};
+
+		var ret = collection.find( callback, ctx );
+		expect( ret ).to.equal( needl );
+
+		function callback( model, name ) {
+			expect( this ).to.equal( ctx ); /* jshint ignore:line */
+			expect( model ).is.an.instanceof( Model );
+			expect( name ).to.be.a( 'string' );
+
+			return model.name == 'foo';
+		}
+	} );
+} );
+
+describe( 'filter', function() {
+	it( 'finds the right items', function() {
+		var Model = modules.model;
+
+		var collection = getCollection();
+		collection.add( getItem( 'bar' ) );
+		collection.add( getItem( 'foo' ) );
+		collection.add( getItem( 'bom' ) );
+
+		var ctx = {};
+
+		var ret = collection.filter( callback, ctx );
+		expect( ret ).to.have.keys( [ 'bar', 'bom' ] );
+
+		function callback( model, name ) {
+			expect( this ).to.equal( ctx ); /* jshint ignore:line */
+			expect( model ).is.an.instanceof( Model );
+			expect( name ).to.be.a( 'string' );
 
-		sinon.assert.callOrder( spy.withArgs( item1, 'foo' ), spy.withArgs( item2, 'bar' ) );
+			return model.name[ 0 ] == 'b';
+		}
 	} );
 } );