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

Merge pull request #179 from ckeditor/t/ckeditor5/1636

Feature: Introduced `MultiCommand` which acts as a composite command – can group multiple commands under the hood.
Piotrek Koszuliński 6 лет назад
Родитель
Сommit
dfeaafb8e9

+ 99 - 0
packages/ckeditor5-core/src/multicommand.js

@@ -0,0 +1,99 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import Command from './command';
+
+/**
+ * @module core/multicommand
+ */
+
+/**
+ * A CKEditor command that aggregates other commands.
+ *
+ * This command is used to proxy multiple commands. The multi-command is enabled when
+ * at least one of its registered child commands is enabled.
+ * When executing a multi-command the first command that is enabled will be executed.
+ *
+ *		const multiCommand = new MultiCommand( editor );
+ *
+ *		const commandFoo = new Command( editor );
+ *		const commandBar = new Command( editor );
+ *
+ *		// Register child commands.
+ *		multiCommand.registerChildCommand( commandFoo );
+ *		multiCommand.registerChildCommand( commandBar );
+ *
+ *		// Enable one of the commands.
+ *		commandBar.isEnabled = true;
+ *
+ *		multiCommand.execute(); // Will execute commandBar.
+ *
+ * @extends module:core/command~Command
+ */
+export default class MultiCommand extends Command {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		/**
+		 * Registered child commands.
+		 *
+		 * @type {Array.<module:core/command~Command>}
+		 * @private
+		 */
+		this._childCommands = [];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	refresh() {
+		// Override base command refresh(): the command's state is changed when one of child commands changes states.
+	}
+
+	/**
+	 * Executes the first of it registered child commands.
+	 */
+	execute( ...args ) {
+		const command = this._getFirstEnabledCommand();
+
+		command.execute( args );
+	}
+
+	/**
+	 * Registers a child command.
+	 *
+	 * @param {module:core/command~Command} command
+	 */
+	registerChildCommand( command ) {
+		this._childCommands.push( command );
+
+		// Change multi command enabled state when one of registered commands changes state.
+		command.on( 'change:isEnabled', () => this._checkEnabled() );
+
+		this._checkEnabled();
+	}
+
+	/**
+	 * Checks if any of child commands is enabled.
+	 *
+	 * @private
+	 */
+	_checkEnabled() {
+		this.isEnabled = !!this._getFirstEnabledCommand();
+	}
+
+	/**
+	 * Returns a first enabled command or undefined if none of them is enabled.
+	 *
+	 * @returns {module:core/command~Command|undefined}
+	 * @private
+	 */
+	_getFirstEnabledCommand() {
+		return this._childCommands.find( command => command.isEnabled );
+	}
+}

+ 124 - 0
packages/ckeditor5-core/tests/multicommand.js

@@ -0,0 +1,124 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import MultiCommand from '../src/multicommand';
+import ModelTestEditor from './_utils/modeltesteditor';
+import Command from '../src/command';
+
+describe( 'MultiCommand', () => {
+	let editor, multiCommand;
+
+	beforeEach( () => {
+		return ModelTestEditor
+			.create()
+			.then( newEditor => {
+				editor = newEditor;
+				multiCommand = new MultiCommand( editor );
+			} );
+	} );
+
+	afterEach( () => {
+		multiCommand.destroy();
+
+		return editor.destroy();
+	} );
+
+	describe( 'isEnabled', () => {
+		it( 'is always falsy when no child commands are registered', () => {
+			expect( multiCommand.isEnabled ).to.false;
+
+			multiCommand.refresh();
+
+			expect( multiCommand.isEnabled ).to.false;
+		} );
+
+		it( 'is set to true if one of registered commands is true', () => {
+			expect( multiCommand.isEnabled ).to.false;
+
+			const commandA = new Command( editor );
+			const commandB = new Command( editor );
+
+			multiCommand.registerChildCommand( commandA );
+			multiCommand.registerChildCommand( commandB );
+
+			expect( multiCommand.isEnabled ).to.false;
+
+			commandA.isEnabled = true;
+
+			expect( multiCommand.isEnabled ).to.be.true;
+
+			commandA.isEnabled = false;
+
+			expect( multiCommand.isEnabled ).to.be.false;
+
+			commandB.isEnabled = true;
+
+			expect( multiCommand.isEnabled ).to.be.true;
+		} );
+	} );
+
+	describe( 'execute()', () => {
+		it( 'does not call any command if all are disabled', () => {
+			const commandA = new Command( editor );
+			const commandB = new Command( editor );
+
+			multiCommand.registerChildCommand( commandA );
+			multiCommand.registerChildCommand( commandB );
+
+			const spyA = sinon.spy( commandA, 'execute' );
+			const spyB = sinon.spy( commandB, 'execute' );
+
+			multiCommand.execute();
+
+			sinon.assert.notCalled( spyA );
+			sinon.assert.notCalled( spyB );
+		} );
+
+		it( 'executes enabled command', () => {
+			const commandA = new Command( editor );
+			const commandB = new Command( editor );
+			const commandC = new Command( editor );
+
+			multiCommand.registerChildCommand( commandA );
+			multiCommand.registerChildCommand( commandB );
+			multiCommand.registerChildCommand( commandC );
+
+			const spyA = sinon.spy( commandA, 'execute' );
+			const spyB = sinon.spy( commandB, 'execute' );
+			const spyC = sinon.spy( commandC, 'execute' );
+
+			commandC.isEnabled = true;
+
+			multiCommand.execute();
+
+			sinon.assert.notCalled( spyA );
+			sinon.assert.notCalled( spyB );
+			sinon.assert.calledOnce( spyC );
+		} );
+
+		it( 'executes first registered command if many are enabled', () => {
+			const commandA = new Command( editor );
+			const commandB = new Command( editor );
+			const commandC = new Command( editor );
+
+			multiCommand.registerChildCommand( commandA );
+			multiCommand.registerChildCommand( commandB );
+			multiCommand.registerChildCommand( commandC );
+
+			const spyA = sinon.spy( commandA, 'execute' );
+			const spyB = sinon.spy( commandB, 'execute' );
+			const spyC = sinon.spy( commandC, 'execute' );
+
+			commandC.isEnabled = true;
+			commandB.isEnabled = true;
+
+			multiCommand.execute();
+
+			sinon.assert.notCalled( spyA );
+			sinon.assert.calledOnce( spyB );
+			sinon.assert.notCalled( spyC );
+		} );
+	} );
+} );