8
0
Просмотр исходного кода

Plugins became a set of features and the creator. Plus, fixed the rest of the tests.

Piotrek Koszuliński 10 лет назад
Родитель
Сommit
94e5d685d1

+ 39 - 65
packages/ckeditor5-engine/src/editor.js

@@ -8,9 +8,8 @@
 import Model from './model.js';
 import EditorConfig from './editorconfig.js';
 import PluginCollection from './plugincollection.js';
-import Creator from './creator.js';
 import CKEditorError from './ckeditorerror.js';
-import utils from './utils.js';
+import langUtils from './lib/lodash/lang.js';
 
 /**
  * Represents a single editor instance.
@@ -67,14 +66,6 @@ export default class Editor extends Model {
 		 * @property {Creator} _creator
 		 * @protected
 		 */
-
-		/**
-		 * The list of detected creators.
-		 *
-		 * @property {Map}
-		 * @protected
-		 */
-		this._creators = new Map();
 	}
 
 	/**
@@ -82,25 +73,45 @@ export default class Editor extends Model {
 	 *
 	 * The initialization consists of the following procedures:
 	 *
-	 *  * Load and initialize the configured plugins.
-	 *  * Fires the editor creator.
+	 * * Loading and initializing the configured features and creator.
+	 * * Firing the editor creator.
 	 *
-	 * This method should be rarely used as `CKEDITOR.create` calls it one should never use the `Editor` constructor
-	 * directly.
+	 * This method should be rarely used as {@link CKEDITOR#create} calls it one should never use the `Editor`
+	 * constructor directly.
 	 *
 	 * @returns {Promise} A promise which resolves once the initialization is completed.
 	 */
 	init() {
 		const that = this;
 		const config = this.config;
+		let creatorName = config.creator;
+
+		if ( !creatorName ) {
+			/**
+			 * The `config.creator` option was not defined.
+			 *
+			 * @error editor-undefined-creator
+			 */
+			return Promise.reject(
+				new CKEditorError( 'editor-undefined-creator: The config.creator option was not defined.' )
+			);
+		}
 
 		return loadPlugins()
 			.then( initPlugins )
-			.then( findCreators )
 			.then( fireCreator );
 
 		function loadPlugins() {
-			return that.plugins.load( config.plugins );
+			let plugins = config.features || [];
+
+			// Handle features passed as a string.
+			if ( !langUtils.isArray( plugins ) ) {
+				plugins = plugins.split( ',' );
+			}
+
+			plugins.push( creatorName );
+
+			return that.plugins.load( plugins );
 		}
 
 		function initPlugins( loadedPlugins ) {
@@ -109,58 +120,13 @@ export default class Editor extends Model {
 			}, Promise.resolve() );
 		}
 
-		function findCreators() {
-			for ( let plugin of that.plugins ) {
-				if ( plugin instanceof Creator ) {
-					that._creators.set( plugin.name, plugin );
-				}
-			}
-		}
-
 		function fireCreator() {
-			// Take the name of the creator to use (config or any of the registered ones).
-			const creatorName = config.creator && ( 'creator-' + config.creator );
-			let creator;
-
-			if ( creatorName ) {
-				// Take the registered class for the given creator name.
-				creator = that._creators.get( creatorName );
-			} else if ( that._creators.size > 1 ) {
-				/**
-				 * The `config.creator` option was not defined.
-				 *
-				 * This error is thrown when more than one creator is available and the configuration does
-				 * not specify which one to use.
-				 *
-				 * @error editor-undefined-creator
-				 */
-				throw new CKEditorError( 'editor-undefined-creator: The config.creator option was not defined.' );
-			} else {
-				creator = utils.nth( 0, that._creators.values() );
-			}
-
-			if ( !creator ) {
-				/**
-				 * The creator has not been found.
-				 *
-				 * * If `creatorName` is defined it means that `config.creator` was configured, but such
-				 * plugin does not exist or it does not implement a creator.
-				 * * If `creatorName` is undefined it means that `config.creator` was not configured and
-				 * that none of the loaded plugins implement a creator.
-				 *
-				 * @error editor-creator-404
-				 * @param {String} creatorName
-				 */
-				throw new CKEditorError(
-					'editor-creator-404: The creator has not been found.',
-					{ creatorName: creatorName }
-				);
-			}
-
-			that._creator = creator;
+			// We can always get the creator by its name because config.creator (which is requried) is passed
+			// to PluginCollection.load().
+			that._creator = that.plugins.get( creatorName );
 
 			// Finally fire the creator. It may be asynchronous, returning a promise.
-			return creator.create();
+			return that._creator.create();
 		}
 	}
 
@@ -190,3 +156,11 @@ export default class Editor extends Model {
  *
  * @event destroy
  */
+
+/**
+ * @cfg {String[]} features
+ */
+
+/**
+ * @cfg {String} creator
+ */

+ 2 - 2
packages/ckeditor5-engine/src/plugincollection.js

@@ -11,7 +11,7 @@
  * @class PluginCollection
  */
 
-import CKEDITOR from '../ckeditor.js';
+import pathUtils from '../ckeditor5/path.js';
 import Plugin from './plugin.js';
 import CKEditorError from './ckeditorerror.js';
 import log from './log.js';
@@ -98,7 +98,7 @@ export default class PluginCollection {
 		}
 
 		function loadPluginByName( pluginName ) {
-			return load( CKEDITOR.getModulePath( pluginName ) )
+			return load( pathUtils.getModulePath( pluginName ) )
 				.then( ( PluginModule ) => {
 					return loadPluginByClass( PluginModule.default, pluginName );
 				} );

+ 2 - 2
packages/ckeditor5-engine/tests/ckeditor/ckeditor.js

@@ -10,7 +10,7 @@
 const modules = bender.amd.require( 'ckeditor', 'core/editor', 'core/config' );
 
 let content = document.getElementById( 'content' );
-let editorConfig = { plugins: 'creator-test' };
+let editorConfig = { creator: 'creator-test' };
 
 bender.tools.createSinonSandbox();
 bender.tools.core.defineEditorCreatorMock( 'test' );
@@ -52,7 +52,7 @@ describe( 'create', () => {
 	} );
 
 	it( 'should set configurations on the new editor', () => {
-		return CKEDITOR.create( content, { test: 1, plugins: 'creator-test' } ).then( ( editor ) => {
+		return CKEDITOR.create( content, { test: 1, creator: 'creator-test' } ).then( ( editor ) => {
 			expect( editor.config.test ).to.equal( 1 );
 		} );
 	} );

+ 12 - 25
packages/ckeditor5-engine/tests/editor/creator.js

@@ -76,7 +76,7 @@ afterEach( () => {
 describe( 'init', () => {
 	it( 'should instantiate the creator and call create()', () => {
 		return initEditor( {
-				plugins: 'creator-test1'
+				creator: 'creator-test1'
 			} )
 			.then( () => {
 				let creator = editor.plugins.get( 'creator-test1' );
@@ -88,10 +88,8 @@ describe( 'init', () => {
 			} );
 	} );
 
-	it( 'should throw if more than one creator is available but config.creator is not defined', () => {
-		return initEditor( {
-				plugins: 'creator-test-throw-on-many1,creator-test-throw-on-many2'
-			} )
+	it( 'should throw if creator is not defined', () => {
+		return initEditor( {} )
 			.then( () => {
 				throw new Error( 'This should not be executed.' );
 			} )
@@ -103,8 +101,8 @@ describe( 'init', () => {
 
 	it( 'should use the creator specified in config.creator', () => {
 		return initEditor( {
-				creator: 'test-config2',
-				plugins: 'creator-test-config1,creator-test-config2',
+				creator: 'creator-test-config2',
+				features: [ 'creator-test-config1', 'creator-test-config2' ],
 			} )
 			.then( () => {
 				let creator1 = editor.plugins.get( 'creator-test-config1' );
@@ -117,32 +115,21 @@ describe( 'init', () => {
 
 	it( 'should throw an error if the creator doesn\'t exist', () => {
 		return initEditor( {
-				creator: 'bad',
-				plugins: 'creator-test1'
+				creator: 'bad'
 			} )
 			.then( () => {
 				throw new Error( 'This should not be executed.' );
 			} )
 			.catch( ( err ) => {
-				expect( err ).to.be.instanceof( CKEditorError );
-				expect( err.message ).to.match( /^editor-creator-404:/ );
-			} );
-	} );
-
-	it( 'should throw an error if no creators are defined', () => {
-		return initEditor( {} )
-			.then( () => {
-				throw new Error( 'This should not be executed.' );
-			} )
-			.catch( ( err ) => {
-				expect( err ).to.be.instanceof( CKEditorError );
-				expect( err.message ).to.match( /^editor-creator-404:/ );
+				// It's the Require.JS error.
+				expect( err ).to.be.an.instanceof( Error );
+				expect( err.message ).to.match( /^Script error for/ );
 			} );
 	} );
 
 	it( 'should chain the promise from the creator (enables async creators)', () => {
 		return initEditor( {
-				plugins: 'creator-async-create'
+				creator: 'creator-async-create'
 			} )
 			.then( () => {
 				throw new Error( 'This should not be executed.' );
@@ -160,7 +147,7 @@ describe( 'destroy', () => {
 		let creator1;
 
 		return initEditor( {
-				plugins: 'creator-test1'
+				creator: 'creator-test1'
 			} )
 			.then( () => {
 				creator1 = editor.plugins.get( 'creator-test1' );
@@ -174,7 +161,7 @@ describe( 'destroy', () => {
 
 	it( 'should chain the promise from the creator (enables async creators)', () => {
 		return initEditor( {
-				plugins: 'creator-async-destroy'
+				creator: 'creator-async-destroy'
 			} )
 			.then( () => {
 				return editor.destroy();

+ 108 - 75
packages/ckeditor5-engine/tests/editor/editor.js

@@ -10,87 +10,52 @@
 const modules = bender.amd.require( 'core/editor', 'core/editorconfig', 'core/plugin' );
 let Editor, EditorConfig, Plugin;
 
-let editor;
+const pluginClasses = {};
 let element;
-let asyncSpy;
 
 before( () => {
 	Editor = modules[ 'core/editor' ];
 	EditorConfig = modules[ 'core/editorconfig' ];
 	Plugin = modules[ 'core/plugin' ];
-} );
-
-beforeEach( () => {
-	element = document.createElement( 'div' );
-	document.body.appendChild( element );
 
-	editor = new Editor( element );
-} );
-
-before( () => {
 	// Define fake plugins to be used in tests.
 	bender.tools.core.defineEditorCreatorMock( 'test', {
 		init: sinon.spy().named( 'creator-test' )
 	} );
 
-	bender.amd.define( 'A', [ 'core/plugin' ], pluginDefinition( 'A' ) );
-
-	bender.amd.define( 'B', [ 'core/plugin' ], pluginDefinition( 'B' ) );
-
-	bender.amd.define( 'C', [ 'core/plugin', 'B' ], pluginDefinition( 'C' ) );
-
-	bender.amd.define( 'D', [ 'core/plugin', 'C' ], pluginDefinition( 'D' ) );
-
-	bender.amd.define( 'E', [ 'core/plugin' ], pluginDefinition( 'E' ) );
-
-	// Synchronous plugin that depends on an asynchronous one.
-	bender.amd.define( 'F', [ 'core/plugin', 'async' ], pluginDefinition( 'F' ) );
-
-	asyncSpy = sinon.spy().named( 'async-call-spy' );
-
-	bender.amd.define( 'async', [ 'core/plugin' ], ( Plugin ) => {
-		class PluginAsync extends Plugin {}
-
-		PluginAsync.prototype.init = sinon.spy( () => {
-			return new Promise( ( resolve ) => {
-				setTimeout( () => {
-					asyncSpy();
-					resolve();
-				}, 0 );
-			} );
-		} );
-
-		return PluginAsync;
-	} );
+	pluginDefinition( 'A' );
+	pluginDefinition( 'B' );
+	pluginDefinition( 'C', [ 'B' ] );
+	pluginDefinition( 'D', [ 'C' ] );
 } );
 
-function pluginDefinition( name ) {
-	return ( Plugin ) => {
-		class NewPlugin extends Plugin {}
-		NewPlugin.prototype.init = sinon.spy().named( name );
-
-		return NewPlugin;
-	};
-}
+beforeEach( () => {
+	element = document.createElement( 'div' );
+	document.body.appendChild( element );
+} );
 
 ///////////////////
 
 describe( 'constructor', () => {
 	it( 'should create a new editor instance', () => {
+		const editor = new Editor( element );
+
 		expect( editor ).to.have.property( 'element' ).to.equal( element );
 	} );
 } );
 
 describe( 'config', () => {
 	it( 'should be an instance of EditorConfig', () => {
+		const editor = new Editor( element );
+
 		expect( editor.config ).to.be.an.instanceof( EditorConfig );
 	} );
 } );
 
 describe( 'init', () => {
 	it( 'should return a promise that resolves properly', () => {
-		editor = new Editor( element, {
-			plugins: 'creator-test'
+		const editor = new Editor( element, {
+			creator: 'creator-test'
 		} );
 
 		let promise = editor.init();
@@ -100,15 +65,16 @@ describe( 'init', () => {
 		return promise;
 	} );
 
-	it( 'should fill `plugins`', () => {
-		editor = new Editor( element, {
-			plugins: 'A,B,creator-test'
+	it( 'should load features and creator', () => {
+		const editor = new Editor( element, {
+			features: [ 'A', 'B' ],
+			creator: 'creator-test'
 		} );
 
-		expect( editor.plugins.length ).to.equal( 0 );
+		expect( getPlugins( editor ) ).to.be.empty;
 
 		return editor.init().then( () => {
-			expect( editor.plugins.length ).to.equal( 3 );
+			expect( getPlugins( editor ).length ).to.equal( 3 );
 
 			expect( editor.plugins.get( 'A' ) ).to.be.an.instanceof( Plugin );
 			expect( editor.plugins.get( 'B' ) ).to.be.an.instanceof( Plugin );
@@ -116,56 +82,88 @@ describe( 'init', () => {
 		} );
 	} );
 
+	it( 'should load features passed as a string', () => {
+		const editor = new Editor( element, {
+			features: 'A,B',
+			creator: 'creator-test'
+		} );
+
+		expect( getPlugins( editor ) ).to.be.empty;
+
+		return editor.init().then( () => {
+			expect( getPlugins( editor ).length ).to.equal( 3 );
+
+			expect( editor.plugins.get( 'A' ) ).to.be.an.instanceof( Plugin );
+			expect( editor.plugins.get( 'B' ) ).to.be.an.instanceof( Plugin );
+		} );
+	} );
+
 	it( 'should initialize plugins in the right order', () => {
-		editor = new Editor( element, {
-			plugins: 'creator-test,A,D'
+		const editor = new Editor( element, {
+			features: [ 'A', 'D' ],
+			creator: 'creator-test'
 		} );
 
 		return editor.init().then( () => {
 			sinon.assert.callOrder(
 				editor.plugins.get( 'creator-test' ).init,
-				editor.plugins.get( 'A' ).init,
-				editor.plugins.get( 'B' ).init,
-				editor.plugins.get( 'C' ).init,
-				editor.plugins.get( 'D' ).init
+				editor.plugins.get( pluginClasses.A ).init,
+				editor.plugins.get( pluginClasses.B ).init,
+				editor.plugins.get( pluginClasses.C ).init,
+				editor.plugins.get( pluginClasses.D ).init
 			);
 		} );
 	} );
 
 	it( 'should initialize plugins in the right order, waiting for asynchronous ones', () => {
-		editor = new Editor( element, {
-			plugins: 'creator-test,A,F'
+		class PluginAsync extends Plugin {}
+		const asyncSpy = sinon.spy().named( 'async-call-spy' );
+
+		// Synchronous plugin that depends on an asynchronous one.
+		pluginDefinition( 'sync', [ 'async' ] );
+
+		bender.amd.define( 'async', () => {
+			PluginAsync.prototype.init = sinon.spy( () => {
+				return new Promise( ( resolve ) => {
+					setTimeout( () => {
+						asyncSpy();
+						resolve();
+					}, 0 );
+				} );
+			} );
+
+			return PluginAsync;
+		} );
+
+		const editor = new Editor( element, {
+			features: [ 'A', 'sync' ],
+			creator: 'creator-test'
 		} );
 
 		return editor.init().then( () => {
 			sinon.assert.callOrder(
 				editor.plugins.get( 'creator-test' ).init,
-				editor.plugins.get( 'A' ).init,
-				editor.plugins.get( 'async' ).init,
+				editor.plugins.get( pluginClasses.A ).init,
+				editor.plugins.get( PluginAsync ).init,
 				// This one is called with delay by the async init.
 				asyncSpy,
-				editor.plugins.get( 'F' ).init
+				editor.plugins.get( pluginClasses.sync ).init
 			);
 		} );
 	} );
-
-	it( 'should not fail if loading a plugin that doesn\'t define init()', () => {
-		editor = new Editor( element, {
-			plugins: 'E,creator-test'
-		} );
-
-		return editor.init();
-	} );
 } );
 
 describe( 'plugins', () => {
 	it( 'should be empty on new editor', () => {
-		expect( editor.plugins.length ).to.equal( 0 );
+		const editor = new Editor( element );
+
+		expect( getPlugins( editor ) ).to.be.empty;
 	} );
 } );
 
 describe( 'destroy', () => {
 	it( 'should fire "destroy"', () => {
+		const editor = new Editor( element );
 		let spy = sinon.spy();
 
 		editor.on( 'destroy', spy );
@@ -176,8 +174,43 @@ describe( 'destroy', () => {
 	} );
 
 	it( 'should delete the "element" property', () => {
+		const editor = new Editor( element );
+
 		return editor.destroy().then( () => {
 			expect( editor ).to.not.have.property( 'element' );
 		} );
 	} );
 } );
+
+/**
+ * @param {String} name Name of the plugin.
+ * @param {String[]} deps Dependencies of the plugin (only other plugins).
+ */
+function pluginDefinition( name, deps ) {
+	bender.amd.define( name, deps || [], function() {
+		class NewPlugin extends Plugin {}
+
+		NewPlugin.prototype.init = sinon.spy().named( name );
+		NewPlugin.requires = Array.from( arguments );
+
+		pluginClasses[ name ] = NewPlugin;
+
+		return NewPlugin;
+	} );
+}
+
+/**
+ * Returns an array of loaded plugins.
+ */
+function getPlugins( editor ) {
+	const plugins = [];
+
+	for ( let entry of editor.plugins ) {
+		// Keep only plugins kept under their classes.
+		if ( typeof entry[ 0 ] == 'function' ) {
+			plugins.push( entry[ 1 ] );
+		}
+	}
+
+	return plugins;
+}

+ 1 - 0
packages/ckeditor5-engine/tests/plugincollection.js

@@ -269,6 +269,7 @@ function getPlugins( pluginCollection ) {
 	const plugins = [];
 
 	for ( let entry of pluginCollection ) {
+		// Keep only plugins kept under their classes.
 		if ( typeof entry[ 0 ] == 'function' ) {
 			plugins.push( entry[ 1 ] );
 		}