浏览代码

Merge pull request #87 from ckeditor/t/86

Feature: `Editor#destroy()` will destroy all loaded plugins. Closes #86.

Added default implementation for `Plugin#destroy()`. Introduced `PluginCollection#destroy()` method which calls `Plugin#destroy()` for every loaded plugin.
Piotrek Koszuliński 8 年之前
父节点
当前提交
4f18fa38c3

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

@@ -150,7 +150,7 @@ export default class Editor {
 
 		this.commands.destroy();
 
-		return Promise.resolve()
+		return this.plugins.destroy()
 			.then( () => {
 				this.document.destroy();
 				this.data.destroy();

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

@@ -34,6 +34,7 @@ export default class Plugin {
 	 * @inheritDoc
 	 */
 	destroy() {
+		this.stopListening();
 	}
 }
 

+ 14 - 0
packages/ckeditor5-core/src/plugincollection.js

@@ -208,6 +208,20 @@ export default class PluginCollection {
 	}
 
 	/**
+	 * Destroys all loaded plugins.
+	 *
+	 * @returns {Promise}
+	 */
+	destroy() {
+		const promises = Array.from( this )
+			.map( ( [ , pluginInstance ] ) => pluginInstance )
+			.filter( pluginInstance => typeof pluginInstance.destroy == 'function' )
+			.map( pluginInstance => pluginInstance.destroy() );
+
+		return Promise.all( promises );
+	}
+
+	/**
 	 * Adds the plugin to the collection. Exposed mainly for testing purposes.
 	 *
 	 * @protected

+ 2 - 2
packages/ckeditor5-core/tests/_utils/classictesteditor.js

@@ -43,8 +43,8 @@ export default class ClassicTestEditor extends StandardEditor {
 	destroy() {
 		this._elementReplacer.restore();
 
-		return this.ui.destroy()
-			.then( () => super.destroy() );
+		return super.destroy()
+			.then( () => this.ui.destroy() );
 	}
 
 	/**

+ 2 - 0
packages/ckeditor5-core/tests/editor/editor.js

@@ -175,11 +175,13 @@ describe( 'Editor', () => {
 
 			const spy1 = sinon.spy( editor.data, 'destroy' );
 			const spy2 = sinon.spy( editor.document, 'destroy' );
+			const spy3 = sinon.spy( editor.plugins, 'destroy' );
 
 			return editor.destroy()
 				.then( () => {
 					expect( spy1.calledOnce ).to.be.true;
 					expect( spy2.calledOnce ).to.be.true;
+					expect( spy3.calledOnce ).to.be.true;
 				} );
 		} );
 	} );

+ 9 - 0
packages/ckeditor5-core/tests/plugin.js

@@ -25,5 +25,14 @@ describe( 'constructor()', () => {
 
 			expect( plugin.destroy ).to.be.a( 'function' );
 		} );
+
+		it( 'should stop listening', () => {
+			const plugin = new Plugin( editor );
+			const stopListeningSpy = sinon.spy( plugin, 'stopListening' );
+
+			plugin.destroy();
+
+			expect( stopListeningSpy.calledOnce ).to.equal( true );
+		} );
 	} );
 } );

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

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* globals setTimeout */
+
 import testUtils from '../tests/_utils/utils';
 import Editor from '../src/editor/editor';
 import PluginCollection from '../src/plugincollection';
@@ -381,6 +383,81 @@ describe( 'PluginCollection', () => {
 		} );
 	} );
 
+	describe( 'destroy()', () => {
+		it( 'calls Plugin#destroy() method on every loaded plugin', () => {
+			let destroySpyForPluginA, destroySpyForPluginB;
+
+			const plugins = new PluginCollection( editor, [] );
+
+			return plugins.load( [ PluginA, PluginB ] )
+				.then( () => {
+					destroySpyForPluginA = sinon.spy( plugins.get( PluginA ), 'destroy' );
+					destroySpyForPluginB = sinon.spy( plugins.get( PluginB ), 'destroy' );
+
+					return plugins.destroy();
+				} )
+				.then( () => {
+					expect( destroySpyForPluginA.calledOnce ).to.equal( true );
+					expect( destroySpyForPluginB.calledOnce ).to.equal( true );
+				} );
+		} );
+
+		it( 'waits until all plugins are destroyed', () => {
+			const destroyedPlugins = [];
+
+			class AsynchronousPluginA extends Plugin {
+				destroy() {
+					return new Promise( resolve => {
+						setTimeout( () => {
+							super.destroy();
+
+							destroyedPlugins.push( 'AsynchronousPluginA.destroy()' );
+							resolve();
+						} );
+					} );
+				}
+			}
+
+			class AsynchronousPluginB extends Plugin {
+				destroy() {
+					return new Promise( resolve => {
+						setTimeout( () => {
+							super.destroy();
+
+							destroyedPlugins.push( 'AsynchronousPluginB.destroy()' );
+							resolve();
+						} );
+					} );
+				}
+			}
+
+			const plugins = new PluginCollection( editor, [] );
+
+			return plugins.load( [ AsynchronousPluginA, AsynchronousPluginB ] )
+				.then( () => plugins.destroy() )
+				.then( () => {
+					expect( destroyedPlugins ).to.contain( 'AsynchronousPluginB.destroy()' );
+					expect( destroyedPlugins ).to.contain( 'AsynchronousPluginA.destroy()' );
+				} );
+		} );
+
+		it( 'does not execute Plugin#destroy() for plugins which do not have this method', () => {
+			class FooPlugin {
+				constructor( editor ) {
+					this.editor = editor;
+				}
+			}
+
+			const plugins = new PluginCollection( editor, [] );
+
+			return plugins.load( [ PluginA, FooPlugin ] )
+				.then( () => plugins.destroy() )
+				.then( destroyedPlugins => {
+					expect( destroyedPlugins.length ).to.equal( 1 );
+				} );
+		} );
+	} );
+
 	describe( 'iterator', () => {
 		it( 'exists', () => {
 			const plugins = new PluginCollection( editor, availablePlugins );