Bläddra i källkod

Closes #48. Merge branch 't/48'

Aleksander Nowodzinski 10 år sedan
förälder
incheckning
00913c7b8d

+ 15 - 3
packages/ckeditor5-engine/src/collection.js

@@ -8,11 +8,13 @@
 /**
  * Collections are ordered sets of models.
  *
+ * See also {@link core/NamedCollection}.
+ *
  * @class Collection
  * @mixins EventEmitter
  */
 
-CKEDITOR.define( [ 'emittermixin', 'utils' ], function( EmitterMixin, utils ) {
+CKEDITOR.define( [ 'emittermixin', 'ckeditorerror', 'utils' ], function( EmitterMixin, CKEditorError, utils ) {
 	class Collection {
 		/**
 		 * Creates a new Collection instance.
@@ -80,14 +82,24 @@ CKEDITOR.define( [ 'emittermixin', 'utils' ], function( EmitterMixin, utils ) {
 				modelOrIndex = this._models.indexOf( modelOrIndex );
 
 				if ( modelOrIndex == -1 ) {
-					throw new Error( 'Model not found' );
+					/**
+					 * Model not found.
+					 *
+					 * @error collection-model-404
+					 */
+					throw new CKEditorError( 'collection-model-404: Model not found.' );
 				}
 			}
 
 			var removedModel = this._models.splice( modelOrIndex, 1 )[ 0 ];
 
 			if ( !removedModel ) {
-				throw new Error( 'Index not found' );
+				/**
+				 * Index not found.
+				 *
+				 * @error collection-index-404
+				 */
+				throw new CKEditorError( 'collection-index-404: Index not found.' );
 			}
 
 			this.fire( 'remove', removedModel );

+ 5 - 13
packages/ckeditor5-engine/src/editor.js

@@ -100,29 +100,21 @@ CKEDITOR.define( [
 				return that.plugins.load( config.plugins );
 			}
 
-			function initPlugins() {
+			function initPlugins( loadedPlugins ) {
 				// Start with a resolved promise.
 				var promise = Promise.resolve();
 
 				// Chain it with promises that resolve with the init() call of every plugin.
-				for ( var i = 0; i < that.plugins.length; i++ ) {
-					promise = promise.then( callInit( i ) );
+				for ( let i = 0; i < loadedPlugins.length; i++ ) {
+					promise = promise.then( () => loadedPlugins[ i ].init() );
 				}
 
 				// Return the promise chain.
 				return promise;
-
-				function callInit( index ) {
-					return function() {
-						// Returns init(). If it is a promise, the next then() interation will be called only when it
-						// will be resolved, enabling asynchronous init().
-						return that.plugins.get( index ).init();
-					};
-				}
 			}
 
 			function findCreators() {
-				that.plugins.forEach( function( plugin, name ) {
+				that.plugins.forEach( ( plugin, name ) => {
 					if ( plugin instanceof Creator ) {
 						that._creators[ name ] = plugin;
 					}
@@ -178,7 +170,7 @@ CKEDITOR.define( [
 
 			delete this.element;
 
-			return Promise.resolve().then( function() {
+			return Promise.resolve().then( () => {
 				return that._creator && that._creator.destroy();
 			} );
 		}

+ 166 - 0
packages/ckeditor5-engine/src/namedcollection.js

@@ -0,0 +1,166 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/**
+ * Named collections are key => model maps.
+ *
+ * See also {@link core/Collection}.
+ *
+ * @class NamedCollection
+ * @mixins EventEmitter
+ */
+
+CKEDITOR.define( [ 'emittermixin', 'ckeditorerror', 'utils' ], function( EmitterMixin, CKEditorError, utils ) {
+	class NamedCollection {
+		/**
+		 * Creates a new NamedCollection instance.
+		 *
+		 * @constructor
+		 */
+		constructor() {
+			/**
+			 * The internal map of models in the collection.
+			 *
+			 * @property _models
+			 * @private
+			 */
+			this._models = new Map();
+		}
+
+		/**
+		 * The number of items available in the collection.
+		 *
+		 * @property length
+		 */
+		get length() {
+			return this._models.size;
+		}
+
+		/**
+		 * Adds an item into the collection.
+		 *
+		 * Throws exception if an item with this name already exists or if the item does not have a name.
+		 *
+		 * @param {Model} model The item to be added.
+		 */
+		add( model ) {
+			var name = model.name;
+
+			if ( !name || this._models.has( name ) ) {
+				/**
+				 * Model isn't named or such model already exists in this collection
+				 *
+				 * Thrown when:
+				 *
+				 * * Model without a name was given.
+				 * * Model with this name already exists in the collection.
+				 *
+				 * @error namedcollection-add
+				 * @param {String} name Name of the model.
+				 */
+				throw new CKEditorError(
+					'namedcollection-add: Model isn\'t named or such model already exists in this collection',
+					{ name: name }
+				);
+			}
+
+			this._models.set( name, model );
+
+			this.fire( 'add', model );
+		}
+
+		/**
+		 * Gets one item from the collection.
+		 *
+		 * @param {String} name The name of the item to take.
+		 * @returns {Model} The requested item.
+		 */
+		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;
+		}
+
+		/**
+		 * Removes an item from the collection.
+		 *
+		 * @param {Model|String} modelOrName Either the item itself (it must have a `name` property)
+		 * or its name inside the collection.
+		 * @returns {Model} The removed item.
+		 */
+		remove( modelOrName ) {
+			var nameGiven = typeof modelOrName == 'string';
+			var name = nameGiven ? modelOrName : modelOrName.name;
+			var model = this._models.get( name );
+
+			if ( nameGiven ? !model : ( model !== modelOrName ) ) {
+				/**
+				 * Model not found or other model exists under its name.
+				 *
+				 * Thrown when:
+				 *
+				 * * a model without a name was given,
+				 * * no model found when a name was given,
+				 * * a model was given and it does not exist in the collection or some other model was found under its name.
+				 *
+				 * @error namedcollection-remove
+				 * @param {String} name Name of the model to remove.
+				 * @param {Model} model The model which was found under the given name.
+				 */
+				throw new CKEditorError(
+					'namedcollection-remove: Model not found or other model exists under its name.',
+					{ name: name, model: model }
+				);
+			}
+
+			this._models.delete( name );
+
+			this.fire( 'remove', model );
+
+			return model;
+		}
+
+		/**
+		 * Executes the callback for each model in the collection.
+		 *
+		 * @param {Function} callback
+		 * @param {Model} callback.item
+		 * @param {String} callback.name
+		 */
+		forEach( callback ) {
+			this._models.forEach( callback );
+		}
+	}
+
+	utils.extend( NamedCollection.prototype, EmitterMixin );
+
+	return NamedCollection;
+} );
+
+/**
+ * Fired when an item is added to the collection.
+ *
+ * @event add
+ * @param {Model} model The added item.
+ */
+
+/**
+ * Fired when an item is removed from the collection.
+ *
+ * @event remove
+ * @param {Model} model The removed item.
+ */

+ 12 - 56
packages/ckeditor5-engine/src/plugincollection.js

@@ -13,12 +13,12 @@
  */
 
 CKEDITOR.define( [
-	'collection',
+	'namedcollection',
 	'plugin',
 	'ckeditorerror',
 	'log'
-], function( Collection, Plugin, CKEditorError, log ) {
-	class PluginCollection extends Collection {
+], function( NamedCollection, Plugin, CKEditorError, log ) {
+	class PluginCollection extends NamedCollection {
 		/**
 		 * Creates an instance of the PluginCollection class, initializing it with a set of plugins.
 		 *
@@ -28,9 +28,6 @@ CKEDITOR.define( [
 			super();
 
 			this._editor = editor;
-
-			// The hash table used to store pointers to loaded plugins by name.
-			this._names = {};
 		}
 
 		/**
@@ -39,25 +36,31 @@ CKEDITOR.define( [
 		 * @param {String} plugins A comma separated list of plugin names to get loaded.
 		 * @returns {Promise} A promise which gets resolved once all plugins are loaded and available into the
 		 * collection.
+		 * @param {core/Plugin[]} returns.loadedPlugins The array of loaded plugins.
 		 */
 		load( plugins ) {
 			var that = this;
 
 			// The list of plugins which are being loaded (to avoid circular references issues).
 			var loading = {};
+			// Plugins added to the collection (for the purpose of returning an array of loaded plugins).
+			var loaded = [];
 
 			// It may happen that an empty list was passed – don't fail.
 			plugins = plugins ? plugins.split( ',' ) : [];
 
 			// Creates a promise for the loading of each plugin and returns a main promise that resolves when all are
 			// done.
-			return Promise.all( plugins.map( pluginPromise ) );
+			return Promise.all( plugins.map( pluginPromise ) )
+				.then( function() {
+					return loaded;
+				} );
 
 			// Returns a promise that will load the plugin and add it to the collection before resolving.
 			function pluginPromise( plugin ) {
 				return new Promise( function( resolve, reject ) {
 					// Do nothing if the plugin is already loaded (or if is being loaded right now).
-					if ( that._names[ plugin ] || loading[ plugin ] ) {
+					if ( that._models.get( plugin ) || loading[ plugin ] ) {
 						return resolve();
 					}
 
@@ -100,6 +103,7 @@ CKEDITOR.define( [
 										// the collection. This guarantees that dependecies come first in the collection.
 										if ( !isPluginDep ) {
 											that.add( loadedPlugin );
+											loaded.push( loadedPlugin );
 										}
 									} )
 							);
@@ -136,54 +140,6 @@ CKEDITOR.define( [
 				return deps;
 			}
 		}
-
-		/**
-		 * Adds a plugin to the collection.
-		 *
-		 * The `name` property must be set to the plugin object before passing it to this function. Adding plugins
-		 * with the same name has no effect and silently fails.
-		 *
-		 * @param {Plugin} plugin The plugin to be added.
-		 */
-		add( plugin ) {
-			// Do nothing if the plugin is already loaded.
-			if ( this._names[ plugin.name ] ) {
-				return;
-			}
-
-			// Save a pointer to the plugin by its name.
-			this._names[ plugin.name ] = plugin;
-
-			// Call the original implementation.
-			super.add.apply( this, arguments );
-		}
-
-		/**
-		 * Gets a plugin from the collection.
-		 *
-		 * @param {String} name The plugin name.
-		 * @returns {Plugin} The requested plugin, if available in the collection.
-		 */
-		get( name ) {
-			if ( typeof name != 'string' ) {
-				return super.get.apply( this, arguments );
-			}
-
-			return this._names[ name ];
-		}
-
-		/**
-		 * Executes the callback for each model in the collection.
-		 *
-		 * @param {Function} callback
-		 * @param {Model} callback.item
-		 * @param {String} callback.name
-		 */
-		forEach( callback ) {
-			for ( var name in this._names ) {
-				callback( this._names[ name ], name );
-			}
-		}
 	}
 
 	return PluginCollection;

+ 6 - 2
packages/ckeditor5-engine/tests/mvc/collection/collection.js

@@ -94,21 +94,25 @@ describe( 'remove', function() {
 	} );
 
 	it( 'should throw an error on invalid index', function() {
+		var CKEditorError = modules.ckeditorerror;
+
 		var box = getCollection();
 		box.add( getItem() );
 
 		expect( function() {
 			box.remove( 1 );
-		} ).to.throw( Error, 'Index not found' );
+		} ).to.throw( CKEditorError, /^collection-index-404/ );
 	} );
 
 	it( 'should throw an error on invalid model', function() {
+		var CKEditorError = modules.ckeditorerror;
+
 		var box = getCollection();
 		box.add( getItem() );
 
 		expect( function() {
 			box.remove( getItem() );
-		} ).to.throw( Error, 'Model not found' );
+		} ).to.throw( CKEditorError, /^collection-model-404/ );
 	} );
 } );
 

+ 205 - 0
packages/ckeditor5-engine/tests/mvc/collection/namedcollection.js

@@ -0,0 +1,205 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+var modules = bender.amd.require( 'namedcollection', 'model', 'ckeditorerror' );
+
+describe( 'add', function() {
+	it( 'changes the length and enables get', function() {
+		var box = getCollection();
+
+		expect( box ).to.have.length( 0 );
+
+		var item = getItem( 'foo' );
+		box.add( item );
+
+		expect( box ).to.have.length( 1 );
+
+		expect( box.get( 'foo' ) ).to.equal( item );
+	} );
+
+	it( 'fires the "add" event', function() {
+		var spy = sinon.spy();
+
+		var box = getCollection();
+		box.on( 'add', spy );
+
+		var item = getItem( 'foo' );
+		box.add( item );
+
+		sinon.assert.calledWithExactly( spy, sinon.match.has( 'source', box ), item );
+	} );
+
+	it( 'throws an error if model is not named', function() {
+		var Model = modules.model;
+		var CKEditorError = modules.ckeditorerror;
+		var box = getCollection();
+
+		expect( box ).to.have.length( 0 );
+
+		var item = new Model();
+
+		expect( function() {
+			box.add( item );
+		} ).to.throw( CKEditorError, /^namedcollection-add/ );
+	} );
+
+	it( 'throws an error if some model already exists under the same name', function() {
+		var CKEditorError = modules.ckeditorerror;
+		var box = getCollection();
+
+		expect( box ).to.have.length( 0 );
+
+		box.add( getItem( 'foo' ) );
+
+		expect( function() {
+			box.add( getItem( 'foo' ) );
+		} ).to.throw( CKEditorError, /^namedcollection-add/ );
+	} );
+} );
+
+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/ );
+	} );
+} );
+
+describe( 'remove', function() {
+	it( 'should remove the model by name', function() {
+		var box = getCollection();
+		var item = getItem( 'foo' );
+
+		box.add( item );
+
+		expect( box ).to.have.length( 1 );
+
+		box.remove( 'foo' );
+
+		expect( box ).to.have.length( 0 );
+	} );
+
+	it( 'should remove the model by model', function() {
+		var box = getCollection();
+		var item = getItem( 'foo' );
+
+		box.add( item );
+
+		expect( box ).to.have.length( 1 );
+
+		box.remove( item );
+
+		expect( box ).to.have.length( 0 );
+	} );
+
+	it( 'should fire the "remove" event', function() {
+		var box = getCollection();
+		var item1 = getItem( 'foo' );
+		var item2 = getItem( 'bar' );
+
+		box.add( item1 );
+		box.add( item2 );
+
+		var spy = sinon.spy();
+
+		box.on( 'remove', spy );
+
+		box.remove( 'foo' ); // by name
+		box.remove( item2 ); // by model
+
+		sinon.assert.calledTwice( spy );
+		sinon.assert.calledWithExactly( spy, sinon.match.has( 'source', box ), item1 );
+		sinon.assert.calledWithExactly( spy, sinon.match.has( 'source', box ), item2 );
+	} );
+
+	it( 'should throw an error if model is not named', function() {
+		var CKEditorError = modules.ckeditorerror;
+		var Model = modules.model;
+		var box = getCollection();
+
+		expect( function() {
+			box.remove( new Model() );
+		} ).to.throw( CKEditorError, /^namedcollection-remove/ );
+	} );
+
+	it( 'should throw an error if model does not exist (by name)', function() {
+		var CKEditorError = modules.ckeditorerror;
+		var box = getCollection();
+
+		expect( function() {
+			box.remove( 'foo' );
+		} ).to.throw( CKEditorError, /^namedcollection-remove/ );
+	} );
+
+	it( 'should throw an error if model does not exist (by model)', function() {
+		var CKEditorError = modules.ckeditorerror;
+		var box = getCollection();
+
+		expect( function() {
+			box.remove( getItem( 'foo' ) );
+		} ).to.throw( CKEditorError, /^namedcollection-remove/ );
+	} );
+
+	it( 'should throw an error if model does not exist (by model)', function() {
+		var CKEditorError = modules.ckeditorerror;
+		var box = getCollection();
+
+		expect( function() {
+			box.remove( getItem( 'foo' ) );
+		} ).to.throw( CKEditorError, /^namedcollection-remove/ );
+	} );
+
+	it( 'should throw an error if a different model exists under the same name', function() {
+		var CKEditorError = modules.ckeditorerror;
+		var box = getCollection();
+		var item = getItem( 'foo' );
+
+		box.add( item );
+
+		expect( function() {
+			box.remove( getItem( 'foo' ) );
+		} ).to.throw( CKEditorError, /^namedcollection-remove/ );
+	} );
+} );
+
+describe( 'forEach', function() {
+	it( 'should iterate over the models', function() {
+		var box = getCollection();
+		var item1 = getItem( 'foo' );
+		var item2 = getItem( 'bar' );
+
+		box.add( item1 );
+		box.add( item2 );
+
+		expect( box ).to.have.length( 2 );
+
+		var spy = sinon.spy();
+		box.forEach( spy );
+
+		sinon.assert.callOrder( spy.withArgs( item1, 'foo' ), spy.withArgs( item2, 'bar' ) );
+	} );
+} );
+
+function getCollection() {
+	var NamedCollection = modules.namedcollection;
+
+	return new NamedCollection();
+}
+
+function getItem( name ) {
+	var Model = modules.model;
+
+	var model = new Model();
+	model.name = name;
+
+	return model;
+}

+ 28 - 87
packages/ckeditor5-engine/tests/plugincollection/plugincollection.js

@@ -117,8 +117,8 @@ describe( 'load', function() {
 			.then( function() {
 				expect( plugins.length ).to.equal( 2 );
 
-				expect( plugins.get( 0 ) ).to.be.an.instanceof( PluginA );
-				expect( plugins.get( 1 ) ).to.be.an.instanceof( PluginB );
+				expect( plugins.get( 'A' ) ).to.be.an.instanceof( PluginA );
+				expect( plugins.get( 'B' ) ).to.be.an.instanceof( PluginB );
 			} );
 	} );
 
@@ -126,15 +126,14 @@ describe( 'load', function() {
 		var PluginCollection = modules.plugincollection;
 
 		var plugins = new PluginCollection( editor );
+		var spy = sinon.spy( plugins, 'add' );
 
 		return plugins.load( 'A,C' )
-			.then( function() {
+			.then( function( loadedPlugins ) {
 				expect( plugins.length ).to.equal( 3 );
 
-				// The order must have dependencies first.
-				expect( plugins.get( 0 ).name ).to.equal( 'A' );
-				expect( plugins.get( 1 ).name ).to.equal( 'B' );
-				expect( plugins.get( 2 ).name ).to.equal( 'C' );
+				expect( getPluginNamesFromSpy( spy ) ).to.deep.equal( [ 'A', 'B', 'C' ], 'order by plugins.add()' );
+				expect( getPluginNames( loadedPlugins ) ).to.deep.equal( [ 'A', 'B', 'C' ], 'order by returned value' );
 			} );
 	} );
 
@@ -142,15 +141,14 @@ describe( 'load', function() {
 		var PluginCollection = modules.plugincollection;
 
 		var plugins = new PluginCollection( editor );
+		var spy = sinon.spy( plugins, 'add' );
 
 		return plugins.load( 'A,B,C' )
-			.then( function() {
+			.then( function( loadedPlugins ) {
 				expect( plugins.length ).to.equal( 3 );
 
-				// The order must have dependencies first.
-				expect( plugins.get( 0 ).name ).to.equal( 'A' );
-				expect( plugins.get( 1 ).name ).to.equal( 'B' );
-				expect( plugins.get( 2 ).name ).to.equal( 'C' );
+				expect( getPluginNamesFromSpy( spy ) ).to.deep.equal( [ 'A', 'B', 'C' ], 'order by plugins.add()' );
+				expect( getPluginNames( loadedPlugins ) ).to.deep.equal( [ 'A', 'B', 'C' ], 'order by returned value' );
 			} );
 	} );
 
@@ -158,16 +156,15 @@ describe( 'load', function() {
 		var PluginCollection = modules.plugincollection;
 
 		var plugins = new PluginCollection( editor );
+		var spy = sinon.spy( plugins, 'add' );
 
 		return plugins.load( 'D' )
-			.then( function() {
+			.then( function( loadedPlugins ) {
 				expect( plugins.length ).to.equal( 4 );
 
 				// The order must have dependencies first.
-				expect( plugins.get( 0 ).name ).to.equal( 'A' );
-				expect( plugins.get( 1 ).name ).to.equal( 'B' );
-				expect( plugins.get( 2 ).name ).to.equal( 'C' );
-				expect( plugins.get( 3 ).name ).to.equal( 'D' );
+				expect( getPluginNamesFromSpy( spy ) ).to.deep.equal( [ 'A', 'B', 'C', 'D' ], 'order by plugins.add()' );
+				expect( getPluginNames( loadedPlugins ) ).to.deep.equal( [ 'A', 'B', 'C', 'D' ], 'order by returned value' );
 			} );
 	} );
 
@@ -175,15 +172,15 @@ describe( 'load', function() {
 		var PluginCollection = modules.plugincollection;
 
 		var plugins = new PluginCollection( editor );
+		var spy = sinon.spy( plugins, 'add' );
 
 		return plugins.load( 'A,E' )
-			.then( function() {
+			.then( function( loadedPlugins ) {
 				expect( plugins.length ).to.equal( 3 );
 
 				// The order must have dependencies first.
-				expect( plugins.get( 0 ).name ).to.equal( 'A' );
-				expect( plugins.get( 1 ).name ).to.equal( 'F' );
-				expect( plugins.get( 2 ).name ).to.equal( 'E' );
+				expect( getPluginNamesFromSpy( spy ) ).to.deep.equal( [ 'A', 'F', 'E' ], 'order by plugins.add()' );
+				expect( getPluginNames( loadedPlugins ) ).to.deep.equal( [ 'A', 'F', 'E' ], 'order by returned value' );
 			} );
 	} );
 
@@ -194,8 +191,8 @@ describe( 'load', function() {
 
 		return plugins.load( 'A,B' )
 			.then( function() {
-				expect( plugins.get( 0 ).editor ).to.equal( editor );
-				expect( plugins.get( 1 ).editor ).to.equal( editor );
+				expect( plugins.get( 'A' ).editor ).to.equal( editor );
+				expect( plugins.get( 'B' ).editor ).to.equal( editor );
 			} );
 	} );
 
@@ -322,70 +319,14 @@ describe( 'load', function() {
 	} );
 } );
 
-describe( 'add', function() {
-	it( 'should add plugins to the collection', function() {
-		var PluginCollection = modules.plugincollection;
-
-		var plugins = new PluginCollection( editor );
-
-		var pluginA = new PluginA();
-		var pluginB = new PluginB();
-
-		// `add()` requires the `name` property to the defined.
-		pluginA.name = 'A';
-		pluginB.name = 'B';
-
-		plugins.add( pluginA );
-		plugins.add( pluginB );
-
-		expect( plugins.length ).to.equal( 2 );
-
-		expect( plugins.get( 0 ) ).to.be.an.instanceof( PluginA );
-		expect( plugins.get( 1 ) ).to.be.an.instanceof( PluginB );
-	} );
-
-	it( 'should do nothing if the plugin is already loaded', function() {
-		var PluginCollection = modules.plugincollection;
-
-		var plugins = new PluginCollection( editor );
-
-		return plugins.load( 'A,B' )
-			.then( function() {
-				// Check length before `add()`.
-				expect( plugins.length ).to.equal( 2 );
-
-				var pluginA = new PluginA();
-				pluginA.name = 'A';
-
-				plugins.add( pluginA );
-
-				// Length should not change after `add()`.
-				expect( plugins.length ).to.equal( 2 );
-			} );
-	} );
-} );
-
-describe( 'get', function() {
-	it( 'should get a plugin by name', function() {
-		var PluginCollection = modules.plugincollection;
-
-		var plugins = new PluginCollection( editor );
-
-		return plugins.load( 'A,B' )
-			.then( function() {
-				expect( plugins.get( 'A' ) ).to.be.an.instanceof( PluginA );
-				expect( plugins.get( 'B' ) ).to.be.an.instanceof( PluginB );
-			} );
+function getPluginNamesFromSpy( addSpy ) {
+	return addSpy.args.map( function( arg ) {
+		return arg[ 0 ].name;
 	} );
+}
 
-	it( 'should return undefined for non existing plugin', function() {
-		var PluginCollection = modules.plugincollection;
-
-		var plugins = new PluginCollection( editor );
-
-		return plugins.load( 'A,B' )
-			.then( function() {
-				expect( plugins.get( 'C' ) ).to.be.an( 'undefined' );
-			} );
+function getPluginNames( plugins ) {
+	return plugins.map( function( arg ) {
+		return arg.name;
 	} );
-} );
+}