Ver código fonte

Merge pull request #47 from ckeditor/t/27

Added Plugin.pluginName support
Szymon Kupś 9 anos atrás
pai
commit
551c3595e0

+ 22 - 1
packages/ckeditor5-core/src/plugin.js

@@ -42,7 +42,28 @@ export default class Plugin {
 	 *		}
 	 *
 	 * @static
-	 * @member {Function[]} core.Plugin.requires
+	 * @member {Function[]|undefined} core.Plugin.requires
+	 */
+
+	/**
+	 * Optional name of the plugin. If set, the plugin will be available in {@link PluginCollection#get} by its
+	 * name and its constructor. If not, then only by its constructor.
+	 *
+	 * The name should reflect the package name + path to that module. E.g. `ckeditor5-image/src/image.js` plugin
+	 * should be named `ckeditor5-image/image`.
+	 *
+	 * To keep a plugin class definition tight it's recommended to define this property as a static getter:
+	 *
+	 *		import Image from './image.js';
+	 *
+	 *		export default class ImageCaption extends Plugin {
+     *			static get pluginName() {
+     *				return 'ckeditor5-image/image';
+     *			}
+	 *		}
+	 *
+	 * @static
+	 * @member {String|undefined} core.Plugin.pluginName
 	 */
 
 	/**

+ 15 - 7
packages/ckeditor5-core/src/plugincollection.js

@@ -35,14 +35,18 @@ export default class PluginCollection {
 	/**
 	 * Collection iterator. Returns `[ PluginConstructor, pluginInstance ]` pairs.
 	 */
-	[ Symbol.iterator ]() {
-		return this._plugins[ Symbol.iterator ]();
+	*[ Symbol.iterator ]() {
+		for ( const entry of this._plugins ) {
+			if ( typeof entry[ 0 ] == 'function' ) {
+				yield entry;
+			}
+		}
 	}
 
 	/**
-	 * Gets the plugin instance by its constructor.
+	 * Gets the plugin instance by its constructor or name.
 	 *
-	 * @param {Function} key The plugin constructor.
+	 * @param {Function|String} key The plugin constructor or {@link core.Plugin.plugiName name}.
 	 * @returns {core.Plugin}
 	 */
 	get( key ) {
@@ -124,10 +128,14 @@ export default class PluginCollection {
 	 * Adds the plugin to the collection. Exposed mainly for testing purposes.
 	 *
 	 * @protected
-	 * @param {Function} key The plugin constructor.
+	 * @param {Function} PluginConstructor The plugin constructor.
 	 * @param {core.Plugin} plugin The instance of the plugin.
 	 */
-	_add( key, plugin ) {
-		this._plugins.set( key, plugin );
+	_add( PluginConstructor, plugin ) {
+		this._plugins.set( PluginConstructor, plugin );
+
+		if ( PluginConstructor.pluginName ) {
+			this._plugins.set( PluginConstructor.pluginName, plugin );
+		}
 	}
 }

+ 184 - 134
packages/ckeditor5-core/tests/plugincollection.js

@@ -47,161 +47,211 @@ before( () => {
 	editor = new Editor();
 } );
 
-describe( 'load', () => {
-	it( 'should not fail when trying to load 0 plugins (empty array)', () => {
-		let plugins = new PluginCollection( editor );
-
-		return plugins.load( [] )
-			.then( () => {
-				expect( getPlugins( plugins ) ).to.be.empty;
-			} );
+describe( 'PluginCollection', () => {
+	describe( 'load()', () => {
+		it( 'should not fail when trying to load 0 plugins (empty array)', () => {
+			let plugins = new PluginCollection( editor );
+
+			return plugins.load( [] )
+				.then( () => {
+					expect( getPlugins( plugins ) ).to.be.empty;
+				} );
+		} );
+
+		it( 'should add collection items for loaded plugins', () => {
+			let plugins = new PluginCollection( editor );
+
+			return plugins.load( [ PluginA, PluginB ] )
+				.then( () => {
+					expect( getPlugins( plugins ).length ).to.equal( 2 );
+
+					expect( plugins.get( PluginA ) ).to.be.an.instanceof( PluginA );
+					expect( plugins.get( PluginB ) ).to.be.an.instanceof( PluginB );
+				} );
+		} );
+
+		it( 'should load dependency plugins', () => {
+			let plugins = new PluginCollection( editor );
+			let spy = sinon.spy( plugins, '_add' );
+
+			return plugins.load( [ PluginA, PluginC ] )
+				.then( ( loadedPlugins ) => {
+					expect( getPlugins( plugins ).length ).to.equal( 3 );
+
+					expect( getPluginNames( getPluginsFromSpy( spy ) ) )
+						.to.deep.equal( [ 'A', 'B', 'C' ], 'order by plugins._add()' );
+					expect( getPluginNames( loadedPlugins ) )
+						.to.deep.equal( [ 'A', 'B', 'C' ], 'order by returned value' );
+				} );
+		} );
+
+		it( 'should be ok when dependencies are loaded first', () => {
+			let plugins = new PluginCollection( editor );
+			let spy = sinon.spy( plugins, '_add' );
+
+			return plugins.load( [ PluginA, PluginB, PluginC ] )
+				.then( ( loadedPlugins ) => {
+					expect( getPlugins( plugins ).length ).to.equal( 3 );
+
+					expect( getPluginNames( getPluginsFromSpy( spy ) ) )
+						.to.deep.equal( [ 'A', 'B', 'C' ], 'order by plugins._add()' );
+					expect( getPluginNames( loadedPlugins ) )
+						.to.deep.equal( [ 'A', 'B', 'C' ], 'order by returned value' );
+				} );
+		} );
+
+		it( 'should load deep dependency plugins', () => {
+			let plugins = new PluginCollection( editor );
+			let spy = sinon.spy( plugins, '_add' );
+
+			return plugins.load( [ PluginD ] )
+				.then( ( loadedPlugins ) => {
+					expect( getPlugins( plugins ).length ).to.equal( 4 );
+
+					// The order must have dependencies first.
+					expect( getPluginNames( getPluginsFromSpy( 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' );
+				} );
+		} );
+
+		it( 'should handle cross dependency plugins', () => {
+			let plugins = new PluginCollection( editor );
+			let spy = sinon.spy( plugins, '_add' );
+
+			return plugins.load( [ PluginA, PluginE ] )
+				.then( ( loadedPlugins ) => {
+					expect( getPlugins( plugins ).length ).to.equal( 3 );
+
+					// The order must have dependencies first.
+					expect( getPluginNames( getPluginsFromSpy( spy ) ) )
+						.to.deep.equal( [ 'A', 'F', 'E' ], 'order by plugins._add()' );
+					expect( getPluginNames( loadedPlugins ) )
+						.to.deep.equal( [ 'A', 'F', 'E' ], 'order by returned value' );
+				} );
+		} );
+
+		it( 'should load grand child classes', () => {
+			let plugins = new PluginCollection( editor );
+
+			return plugins.load( [ PluginG ] )
+				.then( () => {
+					expect( getPlugins( plugins ).length ).to.equal( 1 );
+				} );
+		} );
+
+		it( 'should set the `editor` property on loaded plugins', () => {
+			let plugins = new PluginCollection( editor );
+
+			return plugins.load( [ PluginA, PluginB ] )
+				.then( () => {
+					expect( plugins.get( PluginA ).editor ).to.equal( editor );
+					expect( plugins.get( PluginB ).editor ).to.equal( editor );
+				} );
+		} );
+
+		it( 'should reject on broken plugins (forward the error thrown in a plugin)', () => {
+			let logSpy = testUtils.sinon.stub( log, 'error' );
+
+			let plugins = new PluginCollection( editor );
+
+			return plugins.load( [ PluginA, PluginX, PluginB ] )
+				// Throw here, so if by any chance plugins.load() was resolved correctly catch() will be stil executed.
+				.then( () => {
+					throw new Error( 'Test error: this promise should not be resolved successfully' );
+				} )
+				.catch( ( err ) => {
+					expect( err ).to.be.an.instanceof( TestError );
+					expect( err ).to.have.property( 'message', 'Some error inside a plugin' );
+
+					sinon.assert.calledOnce( logSpy );
+					expect( logSpy.args[ 0 ][ 0 ] ).to.match( /^plugincollection-load:/ );
+				} );
+		} );
+
+		it( 'should reject when loading a module which is not a plugin', () => {
+			let logSpy = testUtils.sinon.stub( log, 'error' );
+
+			let plugins = new PluginCollection( editor );
+
+			class Y {}
+
+			return plugins.load( [ Y ] )
+				// Throw here, so if by any chance plugins.load() was resolved correctly catch() will be stil executed.
+				.then( () => {
+					throw new Error( 'Test error: this promise should not be resolved successfully' );
+				} )
+				.catch( ( err ) => {
+					expect( err ).to.be.an.instanceof( CKEditorError );
+					expect( err.message ).to.match( /^plugincollection-instance/ );
+
+					sinon.assert.calledOnce( logSpy );
+					expect( logSpy.args[ 0 ][ 0 ] ).to.match( /^plugincollection-load:/ );
+				} );
+		} );
 	} );
 
-	it( 'should add collection items for loaded plugins', () => {
-		let plugins = new PluginCollection( editor );
+	describe( 'get()', () => {
+		it( 'retrieves plugin by its constructor', () => {
+			let plugins = new PluginCollection( editor );
 
-		return plugins.load( [ PluginA, PluginB ] )
-			.then( () => {
-				expect( getPlugins( plugins ).length ).to.equal( 2 );
+			class SomePlugin extends Plugin {}
 
-				expect( plugins.get( PluginA ) ).to.be.an.instanceof( PluginA );
-				expect( plugins.get( PluginB ) ).to.be.an.instanceof( PluginB );
-			} );
-	} );
-
-	it( 'should load dependency plugins', () => {
-		let plugins = new PluginCollection( editor );
-		let spy = sinon.spy( plugins, '_add' );
-
-		return plugins.load( [ PluginA, PluginC ] )
-			.then( ( loadedPlugins ) => {
-				expect( getPlugins( plugins ).length ).to.equal( 3 );
-
-				expect( getPluginNames( getPluginsFromSpy( spy ) ) )
-					.to.deep.equal( [ 'A', 'B', 'C' ], 'order by plugins._add()' );
-				expect( getPluginNames( loadedPlugins ) )
-					.to.deep.equal( [ 'A', 'B', 'C' ], 'order by returned value' );
-			} );
-	} );
-
-	it( 'should be ok when dependencies are loaded first', () => {
-		let plugins = new PluginCollection( editor );
-		let spy = sinon.spy( plugins, '_add' );
-
-		return plugins.load( [ PluginA, PluginB, PluginC ] )
-			.then( ( loadedPlugins ) => {
-				expect( getPlugins( plugins ).length ).to.equal( 3 );
-
-				expect( getPluginNames( getPluginsFromSpy( spy ) ) )
-					.to.deep.equal( [ 'A', 'B', 'C' ], 'order by plugins._add()' );
-				expect( getPluginNames( loadedPlugins ) )
-					.to.deep.equal( [ 'A', 'B', 'C' ], 'order by returned value' );
-			} );
-	} );
-
-	it( 'should load deep dependency plugins', () => {
-		let plugins = new PluginCollection( editor );
-		let spy = sinon.spy( plugins, '_add' );
-
-		return plugins.load( [ PluginD ] )
-			.then( ( loadedPlugins ) => {
-				expect( getPlugins( plugins ).length ).to.equal( 4 );
-
-				// The order must have dependencies first.
-				expect( getPluginNames( getPluginsFromSpy( 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' );
-			} );
-	} );
-
-	it( 'should handle cross dependency plugins', () => {
-		let plugins = new PluginCollection( editor );
-		let spy = sinon.spy( plugins, '_add' );
-
-		return plugins.load( [ PluginA, PluginE ] )
-			.then( ( loadedPlugins ) => {
-				expect( getPlugins( plugins ).length ).to.equal( 3 );
-
-				// The order must have dependencies first.
-				expect( getPluginNames( getPluginsFromSpy( spy ) ) )
-					.to.deep.equal( [ 'A', 'F', 'E' ], 'order by plugins._add()' );
-				expect( getPluginNames( loadedPlugins ) )
-					.to.deep.equal( [ 'A', 'F', 'E' ], 'order by returned value' );
-			} );
-	} );
-
-	it( 'should load grand child classes', () => {
-		let plugins = new PluginCollection( editor );
+			return plugins.load( [ SomePlugin ] )
+				.then( () => {
+					expect( plugins.get( SomePlugin ) ).to.be.instanceOf( SomePlugin );
+				} );
+		} );
 
-		return plugins.load( [ PluginG ] )
-			.then( () => {
-				expect( getPlugins( plugins ).length ).to.equal( 1 );
-			} );
-	} );
+		it( 'retrieves plugin by its name and constructor', () => {
+			let plugins = new PluginCollection( editor );
 
-	it( 'should set the `editor` property on loaded plugins', () => {
-		let plugins = new PluginCollection( editor );
+			class SomePlugin extends Plugin {}
+			SomePlugin.pluginName = 'foo/bar';
 
-		return plugins.load( [ PluginA, PluginB ] )
-			.then( () => {
-				expect( plugins.get( PluginA ).editor ).to.equal( editor );
-				expect( plugins.get( PluginB ).editor ).to.equal( editor );
-			} );
+			return plugins.load( [ SomePlugin ] )
+				.then( () => {
+					expect( plugins.get( 'foo/bar' ) ).to.be.instanceOf( SomePlugin );
+					expect( plugins.get( SomePlugin ) ).to.be.instanceOf( SomePlugin );
+				} );
+		} );
 	} );
 
-	it( 'should reject on broken plugins (forward the error thrown in a plugin)', () => {
-		let logSpy = testUtils.sinon.stub( log, 'error' );
-
-		let plugins = new PluginCollection( editor );
-
-		return plugins.load( [ PluginA, PluginX, PluginB ] )
-			// Throw here, so if by any chance plugins.load() was resolved correctly catch() will be stil executed.
-			.then( () => {
-				throw new Error( 'Test error: this promise should not be resolved successfully' );
-			} )
-			.catch( ( err ) => {
-				expect( err ).to.be.an.instanceof( TestError );
-				expect( err ).to.have.property( 'message', 'Some error inside a plugin' );
+	describe( 'iterator', () => {
+		it( 'exists', () => {
+			let plugins = new PluginCollection( editor );
 
-				sinon.assert.calledOnce( logSpy );
-				expect( logSpy.args[ 0 ][ 0 ] ).to.match( /^plugincollection-load:/ );
-			} );
-	} );
-
-	it( 'should reject when loading a module which is not a plugin', () => {
-		let logSpy = testUtils.sinon.stub( log, 'error' );
+			expect( plugins ).to.have.property( Symbol.iterator );
+		} );
 
-		let plugins = new PluginCollection( editor );
+		it( 'returns only plugins by constructors', () => {
+			let plugins = new PluginCollection( editor );
 
-		class Y {}
+			class SomePlugin1 extends Plugin {}
+			class SomePlugin2 extends Plugin {}
+			SomePlugin2.pluginName = 'foo/bar';
 
-		return plugins.load( [ Y ] )
-			// Throw here, so if by any chance plugins.load() was resolved correctly catch() will be stil executed.
-			.then( () => {
-				throw new Error( 'Test error: this promise should not be resolved successfully' );
-			} )
-			.catch( ( err ) => {
-				expect( err ).to.be.an.instanceof( CKEditorError );
-				expect( err.message ).to.match( /^plugincollection-instance/ );
+			return plugins.load( [ SomePlugin1, SomePlugin2 ] )
+				.then( () => {
+					const pluginConstructors = Array.from( plugins )
+						.map( entry => entry[ 0 ] );
 
-				sinon.assert.calledOnce( logSpy );
-				expect( logSpy.args[ 0 ][ 0 ] ).to.match( /^plugincollection-load:/ );
-			} );
+					expect( pluginConstructors ).to.have.members( [ SomePlugin1, SomePlugin2 ] );
+				} );
+		} );
 	} );
 } );
 
-function createPlugin( name, baseClass ) {
-	baseClass = baseClass || Plugin;
-
-	const P = class extends baseClass {
+function createPlugin( name ) {
+	const P = class extends Plugin {
 		constructor( editor ) {
 			super( editor );
-			this._pluginName = name;
+			this.pluginName = name;
 		}
 	};
 
-	P._pluginName = name;
+	P.pluginName = name;
 
 	return P;
 }
@@ -220,5 +270,5 @@ function getPluginsFromSpy( addSpy ) {
 }
 
 function getPluginNames( plugins ) {
-	return plugins.map( ( plugin ) => plugin._pluginName );
+	return plugins.map( ( plugin ) => plugin.pluginName );
 }