Explorar el Código

Test improvements.

Oskar Wróbel hace 6 años
padre
commit
21fca0156e

+ 12 - 9
packages/ckeditor5-core/src/context.js

@@ -51,6 +51,14 @@ export default class Context {
 			contentLanguage: this.config.get( 'language.content' )
 		} );
 
+		/**
+		 * Shorthand for {@link module:utils/locale~Locale#t}.
+		 *
+		 * @see module:utils/locale~Locale#t
+		 * @method #t
+		 */
+		this.t = this.locale.t;
+
 		/**
 		 * List of editors to which this context instance is injected.
 		 *
@@ -61,9 +69,11 @@ export default class Context {
 	}
 
 	/**
-	 * Adds a reference to the to which context is injected.
+	 * Adds a reference to the editor to which context is injected.
 	 *
 	 * @param {module:core/editor/editor~Editor} editor
+	 * @param {Boolean} isHost Flag defines if context was created by this editor. It is used to decide if the context
+	 * should be destroyed along with the editor instance.
 	 */
 	addEditor( editor ) {
 		this._editors.add( editor );
@@ -106,14 +116,7 @@ export default class Context {
 	 * @returns {Promise} A promise that resolves once the context instance is fully destroyed.
 	 */
 	destroy() {
-		const promises = [];
-
-		for ( const editor of Array.from( this._editors ) ) {
-			editor.context = null;
-			promises.push( editor.destroy() );
-		}
-
-		return Promise.all( promises )
+		return Promise.all( Array.from( this._editors, editor => editor.destroy() ) )
 			.then( () => this.plugins.destroy() );
 	}
 

+ 28 - 28
packages/ckeditor5-core/src/editor/editor.js

@@ -58,6 +58,14 @@ export default class Editor {
 		this.context = config.context || new Context( config );
 		this.context.addEditor( this );
 
+		/**
+		 * `true` when context is created by this editor or `false` when context is injected to the editor.
+		 *
+		 * @private
+		 * @type {Boolean}
+		 */
+		this._isContextHost = !config.context;
+
 		const availablePlugins = this.constructor.builtinPlugins;
 
 		/**
@@ -82,6 +90,20 @@ export default class Editor {
 		 */
 		this.plugins = new PluginCollection( this, availablePlugins, this.context.plugins );
 
+		/**
+		 * @readonly
+		 * @type {module:utils/locale~Locale}
+		 */
+		this.locale = this.context.locale;
+
+		/**
+		 * Shorthand for {@link module:utils/locale~Locale#t}.
+		 *
+		 * @see module:utils/locale~Locale#t
+		 * @method #t
+		 */
+		this.t = this.locale.t;
+
 		/**
 		 * Commands registered to the editor.
 		 *
@@ -201,24 +223,6 @@ export default class Editor {
 		this.keystrokes.listenTo( this.editing.view.document );
 	}
 
-	/**
-	 * @readonly
-	 * @type {module:utils/locale~Locale}
-	 */
-	get locale() {
-		return this.context.locale;
-	}
-
-	/**
-	 * Shorthand for {@link module:utils/locale~Locale#t}.
-	 *
-	 * @see module:utils/locale~Locale#t
-	 * @method #t
-	 */
-	get t() {
-		return this.locale.t;
-	}
-
 	/**
 	 * Loads and initializes plugins specified in the config.
 	 *
@@ -244,16 +248,6 @@ export default class Editor {
 	 * @returns {Promise} A promise that resolves once the editor instance is fully destroyed.
 	 */
 	destroy() {
-		// If editor context is created by this editor
-		// it is enough to destroy the context, editor will be destroyed along with it.
-		if ( this.context ) {
-			if ( this.config.context ) {
-				return this.context.destroy();
-			}
-
-			this.context.removeEditor( this );
-		}
-
 		let readyPromise = Promise.resolve();
 
 		if ( this.state == 'initializing' ) {
@@ -262,6 +256,12 @@ export default class Editor {
 
 		return readyPromise
 			.then( () => {
+				this.context.removeEditor( this );
+
+				if ( this._isContextHost ) {
+					return this.context.destroy();
+				}
+			} ).then( () => {
 				this.fire( 'destroy' );
 				this.stopListening();
 				this.commands.destroy();

+ 1 - 1
packages/ckeditor5-core/src/plugincollection.js

@@ -63,7 +63,7 @@ export default class PluginCollection {
 		/**
 		 * Map of external plugins which can be retrieved by their constructors or instance.
 		 *
-		 * @private
+		 * @protected
 		 * @type {Map<Function,Function>}
 		 */
 		this._externalPlugins = new Map();

+ 172 - 153
packages/ckeditor5-core/tests/context.js

@@ -6,214 +6,233 @@
 import Context from '../src/context';
 import ContextPlugin from '../src/contextplugin';
 import Plugin from '../src/plugin';
+import Config from '@ckeditor/ckeditor5-utils/src/config';
+import Locale from '@ckeditor/ckeditor5-utils/src/locale';
 import VirtualTestEditor from './_utils/virtualtesteditor';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
 describe( 'Context', () => {
-	it( 'should share the same instance of plugin within editors using the same context', async () => {
-		class ContextPluginA extends ContextPlugin {}
-		class ContextPluginB extends ContextPlugin {}
-		class EditorPluginA extends Plugin {}
+	describe( 'config', () => {
+		it( 'should be created', () => {
+			const context = new Context();
 
-		const context = await Context.create( { plugins: [ ContextPluginA, ContextPluginB ] } );
-		const editorA = await VirtualTestEditor.create( { context, plugins: [ ContextPluginA, EditorPluginA ] } );
-		const editorB = await VirtualTestEditor.create( { context, plugins: [ ContextPluginB, EditorPluginA ] } );
+			expect( context.config ).to.instanceof( Config );
+		} );
 
-		expect( editorA.plugins.get( ContextPluginA ) ).to.equal( context.plugins.get( ContextPluginA ) );
-		expect( editorA.plugins.has( ContextPluginB ) ).to.equal( false );
-		expect( editorB.plugins.get( ContextPluginB ) ).to.equal( context.plugins.get( ContextPluginB ) );
-		expect( editorB.plugins.has( ContextPluginA ) ).to.equal( false );
+		it( 'should be with given configuration', () => {
+			const context = new Context( { foo: 'bar' } );
 
-		expect( context.plugins.has( EditorPluginA ) ).to.equal( false );
-		expect( editorA.plugins.get( EditorPluginA ) ).to.not.equal( editorB.plugins.get( EditorPluginA ) );
-
-		await context.destroy();
+			expect( context.config.get( 'foo' ) ).to.equal( 'bar' );
+		} );
 	} );
 
-	it( 'should share the same instance of plugin (dependencies) within editors using the same context', async () => {
-		class ContextPluginA extends ContextPlugin {}
-		class ContextPluginB extends ContextPlugin {}
-		class EditorPluginA extends Plugin {
-			static get requires() {
-				return [ ContextPluginA ];
-			}
-		}
-		class EditorPluginB extends Plugin {
-			static get requires() {
-				return [ ContextPluginB ];
-			}
-		}
+	describe( 'locale', () => {
+		it( 'is instantiated and t() is exposed', () => {
+			const context = new Context();
+
+			expect( context.locale ).to.be.instanceof( Locale );
+			expect( context.t ).to.equal( context.locale.t );
+		} );
+
+		it( 'is configured with the config.language (UI and the content)', () => {
+			const context = new Context( { language: 'pl' } );
+
+			expect( context.locale.uiLanguage ).to.equal( 'pl' );
+			expect( context.locale.contentLanguage ).to.equal( 'pl' );
+		} );
+
+		it( 'is configured with the config.language (different for UI and the content)', () => {
+			const context = new Context( { language: { ui: 'pl', content: 'ar' } } );
 
-		const context = await Context.create( { plugins: [ ContextPluginA, ContextPluginB ] } );
-		const editorA = await VirtualTestEditor.create( { context, plugins: [ EditorPluginA ] } );
-		const editorB = await VirtualTestEditor.create( { context, plugins: [ EditorPluginB ] } );
+			expect( context.locale.uiLanguage ).to.equal( 'pl' );
+			expect( context.locale.contentLanguage ).to.equal( 'ar' );
+		} );
 
-		expect( context.plugins.get( ContextPluginA ) ).to.equal( editorA.plugins.get( ContextPluginA ) );
-		expect( context.plugins.get( ContextPluginB ) ).to.equal( editorB.plugins.get( ContextPluginB ) );
+		it( 'is configured with the config.language (just the content)', () => {
+			const context = new Context( { language: { content: 'ar' } } );
 
-		await context.destroy();
+			expect( context.locale.uiLanguage ).to.equal( 'en' );
+			expect( context.locale.contentLanguage ).to.equal( 'ar' );
+		} );
 	} );
 
-	it( 'should not initialize twice plugin added to the context and the editor', async () => {
-		const initSpy = sinon.spy();
-		const afterInitSpy = sinon.spy();
+	describe( 'plugins', () => {
+		it( 'should throw when plugin added to the context is not marked as a ContextPlugin #1', async () => {
+			class EditorPlugin extends Plugin {}
 
-		class ContextPluginA extends ContextPlugin {
-			init() {
-				initSpy();
-			}
+			let caughtError;
 
-			afterInit() {
-				afterInitSpy();
+			try {
+				await Context.create( { plugins: [ EditorPlugin ] } );
+			} catch ( error ) {
+				caughtError = error;
 			}
-		}
-
-		const context = await Context.create( { plugins: [ ContextPluginA ] } );
-		const editor = await VirtualTestEditor.create( { context, plugins: [ ContextPluginA ] } );
 
-		expect( context.plugins.get( ContextPluginA ) ).to.equal( editor.plugins.get( ContextPluginA ) );
-		sinon.assert.calledOnce( initSpy );
-		sinon.assert.calledOnce( afterInitSpy );
+			expect( caughtError ).to.instanceof( CKEditorError );
+			expect( caughtError.message ).match( /^context-initplugins: Only plugins marked as a ContextPlugin are allowed./ );
+		} );
 
-		await context.destroy();
-	} );
+		it( 'should throw when plugin added to the context is not marked as a ContextPlugin #2', async () => {
+			function EditorPlugin() {}
 
-	it( 'should not destroy context along with the editor when context is injected to the editor', async () => {
-		const contextPluginDestroySpy = sinon.spy();
-		const editorPluginDestroySpy = sinon.spy();
+			let caughtError;
 
-		class ContextPluginA extends ContextPlugin {
-			destroy() {
-				contextPluginDestroySpy();
+			try {
+				await Context.create( { plugins: [ EditorPlugin ] } );
+			} catch ( error ) {
+				caughtError = error;
 			}
-		}
 
-		class EditorPlugin extends Plugin {
-			destroy() {
-				editorPluginDestroySpy();
+			expect( caughtError ).to.instanceof( CKEditorError );
+			expect( caughtError.message ).match( /^context-initplugins: Only plugins marked as a ContextPlugin are allowed./ );
+		} );
+
+		it( 'should throw when plugin is added to the context by name', async () => {
+			let caughtError;
+
+			try {
+				await Context.create( { plugins: [ 'ContextPlugin' ] } );
+			} catch ( error ) {
+				caughtError = error;
 			}
-		}
 
-		const context = await Context.create( { plugins: [ ContextPluginA ] } );
-		const editor = await VirtualTestEditor.create( { context, plugins: [ ContextPluginA, EditorPlugin ] } );
+			expect( caughtError ).to.instanceof( CKEditorError );
+			expect( caughtError.message ).match( /^context-initplugins: Only constructor is allowed as a Context plugin./ );
+		} );
 
-		await editor.destroy();
+		it( 'should not throw when plugin as a function, marked as a ContextPlugin is added to the context', async () => {
+			function EditorPlugin() {}
+			EditorPlugin.isContextPlugin = true;
 
-		sinon.assert.calledOnce( editorPluginDestroySpy );
-		sinon.assert.notCalled( contextPluginDestroySpy );
-	} );
+			let caughtError;
+
+			try {
+				await Context.create( { plugins: [ EditorPlugin ] } );
+			} catch ( error ) {
+				caughtError = error;
+			}
 
-	it( 'should destroy all editors with injected context when context is destroyed', async () => {
-		const context = await Context.create();
-		const editorA = await VirtualTestEditor.create( { context } );
-		const editorB = await VirtualTestEditor.create( { context } );
-		const editorC = await VirtualTestEditor.create();
+			expect( caughtError ).to.equal( undefined );
+		} );
 
-		sinon.spy( editorA, 'destroy' );
-		sinon.spy( editorB, 'destroy' );
-		sinon.spy( editorC, 'destroy' );
+		it( 'should share the same instance of plugin within editors using the same context', async () => {
+			class ContextPluginA extends ContextPlugin {}
+			class ContextPluginB extends ContextPlugin {}
+			class EditorPluginA extends Plugin {}
 
-		await context.destroy();
+			const context = await Context.create( { plugins: [ ContextPluginA, ContextPluginB ] } );
+			const editorA = await VirtualTestEditor.create( { context, plugins: [ ContextPluginA, EditorPluginA ] } );
+			const editorB = await VirtualTestEditor.create( { context, plugins: [ ContextPluginB, EditorPluginA ] } );
 
-		sinon.assert.calledOnce( editorA.destroy );
-		sinon.assert.calledOnce( editorB.destroy );
-		sinon.assert.notCalled( editorC.destroy );
-	} );
+			expect( editorA.plugins.get( ContextPluginA ) ).to.equal( context.plugins.get( ContextPluginA ) );
+			expect( editorA.plugins.has( ContextPluginB ) ).to.equal( false );
+			expect( editorB.plugins.get( ContextPluginB ) ).to.equal( context.plugins.get( ContextPluginB ) );
+			expect( editorB.plugins.has( ContextPluginA ) ).to.equal( false );
 
-	it( 'should be able to add context plugin to the editor using pluginName property', async () => {
-		class ContextPluginA extends ContextPlugin {
-			static get pluginName() {
-				return 'ContextPluginA';
-			}
-		}
+			expect( context.plugins.has( EditorPluginA ) ).to.equal( false );
+			expect( editorA.plugins.get( EditorPluginA ) ).to.not.equal( editorB.plugins.get( EditorPluginA ) );
 
-		class ContextPluginB extends ContextPlugin {
-			static get pluginName() {
-				return 'ContextPluginB';
-			}
+			await context.destroy();
+		} );
 
-			static get requires() {
-				return [ ContextPluginA ];
+		it( 'should share the same instance of plugin (dependencies) within editors using the same context', async () => {
+			class ContextPluginA extends ContextPlugin {}
+			class ContextPluginB extends ContextPlugin {}
+			class EditorPluginA extends Plugin {
+				static get requires() {
+					return [ ContextPluginA ];
+				}
+			}
+			class EditorPluginB extends Plugin {
+				static get requires() {
+					return [ ContextPluginB ];
+				}
 			}
-		}
 
-		const context = await Context.create( { plugins: [ ContextPluginB ] } );
-		const editor = await VirtualTestEditor.create( { context, plugins: [ 'ContextPluginA' ] } );
+			const context = await Context.create( { plugins: [ ContextPluginA, ContextPluginB ] } );
+			const editorA = await VirtualTestEditor.create( { context, plugins: [ EditorPluginA ] } );
+			const editorB = await VirtualTestEditor.create( { context, plugins: [ EditorPluginB ] } );
 
-		expect( editor.plugins.has( ContextPluginA ) ).to.equal( true );
-		expect( editor.plugins.has( ContextPluginB ) ).to.equal( false );
-	} );
+			expect( context.plugins.get( ContextPluginA ) ).to.equal( editorA.plugins.get( ContextPluginA ) );
+			expect( context.plugins.get( ContextPluginB ) ).to.equal( editorB.plugins.get( ContextPluginB ) );
 
-	it( 'should throw when plugin is added to the context by name', async () => {
-		let caughtError;
+			await context.destroy();
+		} );
 
-		try {
-			await Context.create( { plugins: [ 'ContextPlugin' ] } );
-		} catch ( error ) {
-			caughtError = error;
-		}
+		it( 'should not initialize twice plugin added to the context and the editor', async () => {
+			const initSpy = sinon.spy();
+			const afterInitSpy = sinon.spy();
 
-		expect( caughtError ).to.instanceof( CKEditorError );
-		expect( caughtError.message ).match( /^context-initplugins: Only constructor is allowed as a Context plugin./ );
-	} );
+			class ContextPluginA extends ContextPlugin {
+				init() {
+					initSpy();
+				}
 
-	it( 'should throw when plugin added to the context is not marked as a ContextPlugin #1', async () => {
-		class EditorPlugin extends Plugin {}
+				afterInit() {
+					afterInitSpy();
+				}
+			}
 
-		let caughtError;
+			const context = await Context.create( { plugins: [ ContextPluginA ] } );
+			const editor = await VirtualTestEditor.create( { context, plugins: [ ContextPluginA ] } );
 
-		try {
-			await Context.create( { plugins: [ EditorPlugin ] } );
-		} catch ( error ) {
-			caughtError = error;
-		}
+			expect( context.plugins.get( ContextPluginA ) ).to.equal( editor.plugins.get( ContextPluginA ) );
+			sinon.assert.calledOnce( initSpy );
+			sinon.assert.calledOnce( afterInitSpy );
 
-		expect( caughtError ).to.instanceof( CKEditorError );
-		expect( caughtError.message ).match( /^context-initplugins: Only plugins marked as a ContextPlugin are allowed./ );
-	} );
+			await context.destroy();
+		} );
 
-	it( 'should throw when plugin added to the context is not marked as a ContextPlugin #2', async () => {
-		function EditorPlugin() {}
+		it( 'should be able to add context plugin to the editor using pluginName property', async () => {
+			class ContextPluginA extends ContextPlugin {
+				static get pluginName() {
+					return 'ContextPluginA';
+				}
+			}
 
-		let caughtError;
+			class ContextPluginB extends ContextPlugin {
+				static get pluginName() {
+					return 'ContextPluginB';
+				}
 
-		try {
-			await Context.create( { plugins: [ EditorPlugin ] } );
-		} catch ( error ) {
-			caughtError = error;
-		}
+				static get requires() {
+					return [ ContextPluginA ];
+				}
+			}
 
-		expect( caughtError ).to.instanceof( CKEditorError );
-		expect( caughtError.message ).match( /^context-initplugins: Only plugins marked as a ContextPlugin are allowed./ );
-	} );
+			const context = await Context.create( { plugins: [ ContextPluginB ] } );
+			const editor = await VirtualTestEditor.create( { context, plugins: [ 'ContextPluginA' ] } );
 
-	it( 'should not throw when plugin added to the context has not a ContextPlugin proto but is marked as a ContextPlugin', async () => {
-		function EditorPlugin() {}
-		EditorPlugin.isContextPlugin = true;
+			expect( editor.plugins.has( ContextPluginA ) ).to.equal( true );
+			expect( editor.plugins.has( ContextPluginB ) ).to.equal( false );
+		} );
+	} );
 
-		let caughtError;
+	describe( 'destroy()', () => {
+		it( 'should destroy plugins', async () => {
+			const context = await Context.create();
+			const spy = sinon.spy( context.plugins, 'destroy' );
 
-		try {
-			await Context.create( { plugins: [ EditorPlugin ] } );
-		} catch ( error ) {
-			caughtError = error;
-		}
+			await context.destroy();
 
-		expect( caughtError ).to.equal( undefined );
-	} );
+			sinon.assert.calledOnce( spy );
+		} );
 
-	it( 'should throw when plugin added to the context is not marked as a ContextPlugin #2', async () => {
-		function EditorPlugin() {}
+		it( 'should destroy all editors with injected context', async () => {
+			const context = await Context.create();
+			const editorA = await VirtualTestEditor.create( { context } );
+			const editorB = await VirtualTestEditor.create( { context } );
+			const editorC = await VirtualTestEditor.create();
 
-		let caughtError;
+			sinon.spy( editorA, 'destroy' );
+			sinon.spy( editorB, 'destroy' );
+			sinon.spy( editorC, 'destroy' );
 
-		try {
-			await Context.create( { plugins: [ EditorPlugin ] } );
-		} catch ( error ) {
-			caughtError = error;
-		}
+			await context.destroy();
 
-		expect( caughtError ).to.instanceof( CKEditorError );
-		expect( caughtError.message ).match( /^context-initplugins: Only plugins marked as a ContextPlugin are allowed./ );
+			sinon.assert.calledOnce( editorA.destroy );
+			sinon.assert.calledOnce( editorB.destroy );
+			sinon.assert.notCalled( editorC.destroy );
+		} );
 	} );
 } );

+ 48 - 21
packages/ckeditor5-core/tests/editor/editor.js

@@ -6,6 +6,7 @@
 /* globals window, setTimeout */
 
 import Editor from '../../src/editor/editor';
+import Context from '../../src/context';
 import Plugin from '../../src/plugin';
 import Config from '@ckeditor/ckeditor5-utils/src/config';
 import EditingController from '@ckeditor/ckeditor5-engine/src/controller/editingcontroller';
@@ -180,41 +181,67 @@ describe( 'Editor', () => {
 		} );
 	} );
 
-	describe( 'plugins', () => {
-		it( 'should be empty on new editor', () => {
+	describe( 'context integration', () => {
+		it( 'should create a new context when it is not provided through config', () => {
 			const editor = new TestEditor();
 
-			expect( getPlugins( editor ) ).to.be.empty;
+			expect( editor.context ).to.be.an.instanceof( Context );
 		} );
-	} );
 
-	describe( 'locale', () => {
-		it( 'is instantiated and t() is exposed', () => {
-			const editor = new TestEditor();
+		it( 'should use context given through configuration when is defined', async () => {
+			const context = await Context.create();
+			const editor = new TestEditor( { context } );
 
-			expect( editor.locale ).to.be.instanceof( Locale );
-			expect( editor.t ).to.equal( editor.locale.t );
+			expect( editor.context ).to.equal( context );
 		} );
 
-		it( 'is configured with the config.language (UI and the content)', () => {
-			const editor = new TestEditor( { language: 'pl' } );
+		it( 'should destroy context created by the editor on editor destroy', async () => {
+			const editor = await TestEditor.create();
+			const contextDestroySpy = sinon.spy( editor.context, 'destroy' );
+
+			await editor.destroy();
 
-			expect( editor.locale.uiLanguage ).to.equal( 'pl' );
-			expect( editor.locale.contentLanguage ).to.equal( 'pl' );
+			sinon.assert.calledOnce( contextDestroySpy );
 		} );
 
-		it( 'is configured with the config.language (different for UI and the content)', () => {
-			const editor = new TestEditor( { language: { ui: 'pl', content: 'ar' } } );
+		it( 'should not destroy context along with the editor when context was injected to the editor', async () => {
+			const context = await Context.create();
+			const editor = await TestEditor.create( { context } );
+			const contextDestroySpy = sinon.spy( editor.context, 'destroy' );
 
-			expect( editor.locale.uiLanguage ).to.equal( 'pl' );
-			expect( editor.locale.contentLanguage ).to.equal( 'ar' );
+			await editor.destroy();
+
+			sinon.assert.notCalled( contextDestroySpy );
 		} );
 
-		it( 'is configured with the config.language (just the content)', () => {
-			const editor = new TestEditor( { language: { content: 'ar' } } );
+		it( 'should add context plugins as an external editor plugins', async () => {
+			class ContextPlugin {
+				static get isContextPlugin() {
+					return true;
+				}
+			}
+
+			const context = await Context.create( { plugins: [ ContextPlugin ] } );
+			const editor = new TestEditor( { context } );
+
+			expect( editor.plugins._externalPlugins.has( ContextPlugin ) ).to.equal( true );
+		} );
+	} );
+
+	describe( 'plugins', () => {
+		it( 'should be empty on new editor', () => {
+			const editor = new TestEditor();
+
+			expect( getPlugins( editor ) ).to.be.empty;
+		} );
+	} );
+
+	describe( 'locale', () => {
+		it( 'should use Context#locale and Context#t', () => {
+			const editor = new TestEditor();
 
-			expect( editor.locale.uiLanguage ).to.equal( 'en' );
-			expect( editor.locale.contentLanguage ).to.equal( 'ar' );
+			expect( editor.locale ).to.equal( editor.context.locale ).to.instanceof( Locale );
+			expect( editor.t ).to.equal( editor.context.t );
 		} );
 	} );
 

+ 34 - 0
packages/ckeditor5-core/tests/plugincollection.js

@@ -405,6 +405,40 @@ describe( 'PluginCollection', () => {
 					sinon.assert.calledOnce( consoleErrorStub );
 				} );
 		} );
+
+		it( 'should get plugin from external plugins instead of creating new instance', async () => {
+			const externalPlugins = new PluginCollection( editor );
+			await externalPlugins.init( [ PluginA, PluginB ] );
+
+			const plugins = new PluginCollection( editor, [], Array.from( externalPlugins ) );
+			await plugins.init( [ PluginA ] );
+
+			expect( getPlugins( plugins ) ).to.length( 1 );
+			expect( plugins.get( PluginA ) ).to.equal( externalPlugins.get( PluginA ) ).to.instanceof( PluginA );
+		} );
+
+		it( 'should get plugin by name from external plugins instead of creating new instance', async () => {
+			const externalPlugins = new PluginCollection( editor );
+			await externalPlugins.init( [ PluginA, PluginB ] );
+
+			const plugins = new PluginCollection( editor, [], Array.from( externalPlugins ) );
+			await plugins.init( [ 'A' ] );
+
+			expect( getPlugins( plugins ) ).to.length( 1 );
+			expect( plugins.get( PluginA ) ).to.equal( externalPlugins.get( PluginA ) ).to.instanceof( PluginA );
+		} );
+
+		it( 'should get dependency of plugin from external plugins instead of creating new instance', async () => {
+			const externalPlugins = new PluginCollection( editor );
+			await externalPlugins.init( [ PluginA, PluginB ] );
+
+			const plugins = new PluginCollection( editor, [], Array.from( externalPlugins ) );
+			await plugins.init( [ PluginC ] );
+
+			expect( getPlugins( plugins ) ).to.length( 2 );
+			expect( plugins.get( PluginB ) ).to.equal( externalPlugins.get( PluginB ) ).to.instanceof( PluginB );
+			expect( plugins.get( PluginC ) ).to.instanceof( PluginC );
+		} );
 	} );
 
 	describe( 'get()', () => {