Răsfoiți Sursa

Split the HeadingCommand.

Aleksander Nowodzinski 9 ani în urmă
părinte
comite
29e262cab3

+ 32 - 16
packages/ckeditor5-heading/src/heading.js

@@ -8,12 +8,9 @@
  */
  */
 
 
 import HeadingEngine from './headingengine';
 import HeadingEngine from './headingengine';
-
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-
 import Model from '@ckeditor/ckeditor5-ui/src/model';
 import Model from '@ckeditor/ckeditor5-ui/src/model';
 import createListDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/list/createlistdropdown';
 import createListDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/list/createlistdropdown';
-
 import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 
 
 /**
 /**
@@ -35,34 +32,49 @@ export default class Heading extends Plugin {
 	 */
 	 */
 	init() {
 	init() {
 		const editor = this.editor;
 		const editor = this.editor;
-		const command = editor.commands.get( 'heading' );
-		const options = command.options;
-		const collection = new Collection();
+		const headingEngine = editor.plugins.get( HeadingEngine );
+		const commands = headingEngine.commands;
+		const dropdownItems = new Collection();
 
 
-		// Add options to collection.
-		for ( const { id, label } of options ) {
-			collection.add( new Model( {
-				id, label
+		for ( let { name, label } of commands ) {
+			// Add the option to the collection.
+			dropdownItems.add( new Model( {
+				name, label
 			} ) );
 			} ) );
 		}
 		}
 
 
 		// Create dropdown model.
 		// Create dropdown model.
 		const dropdownModel = new Model( {
 		const dropdownModel = new Model( {
 			withText: true,
 			withText: true,
-			items: collection
+			items: dropdownItems
 		} );
 		} );
 
 
-		// Bind dropdown model to command.
-		dropdownModel.bind( 'isEnabled' ).to( command, 'isEnabled' );
-		dropdownModel.bind( 'label' ).to( command, 'value', option => option.label );
+		dropdownModel.bind( 'isEnabled' ).to(
+			// Bind to #isEnabled of each command...
+			...getCommandsBindingTargets( commands, 'isEnabled' ),
+			// ...and set it true if any command #isEnabled is true.
+			( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled )
+		);
+
+		dropdownModel.bind( 'label' ).to(
+			// Bind to #value of each command...
+			...getCommandsBindingTargets( commands, 'value' ),
+			// ...and chose the label of the first one which #value is true.
+			( ...areActive ) => {
+				const index = areActive.findIndex( value => value );
+
+				// If none of the commands is active, display the first one.
+				return commands.get( index > -1 ? index : 0 ).label;
+			}
+		);
 
 
 		// Register UI component.
 		// Register UI component.
 		editor.ui.componentFactory.add( 'headings', ( locale ) => {
 		editor.ui.componentFactory.add( 'headings', ( locale ) => {
 			const dropdown = createListDropdown( dropdownModel, locale );
 			const dropdown = createListDropdown( dropdownModel, locale );
 
 
 			// Execute command when an item from the dropdown is selected.
 			// Execute command when an item from the dropdown is selected.
-			this.listenTo( dropdown, 'execute', ( { source: { id } } ) => {
-				editor.execute( 'heading', { id } );
+			this.listenTo( dropdown, 'execute', ( { source: { name } } ) => {
+				editor.execute( name );
 				editor.editing.view.focus();
 				editor.editing.view.focus();
 			} );
 			} );
 
 
@@ -70,3 +82,7 @@ export default class Heading extends Plugin {
 		} );
 		} );
 	}
 	}
 }
 }
+
+function getCommandsBindingTargets( commands, attribute ) {
+	return Array.prototype.concat( ...commands.map( c => [ c, attribute ] ) );
+}

+ 34 - 54
packages/ckeditor5-heading/src/headingcommand.js

@@ -9,6 +9,7 @@
 
 
 import Command from '@ckeditor/ckeditor5-core/src/command/command';
 import Command from '@ckeditor/ckeditor5-core/src/command/command';
 import RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement';
 import RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement';
+import camelCase from '@ckeditor/ckeditor5-utils/src/lib/lodash/camelCase';
 
 
 /**
 /**
  * The heading command. It is used by the {@link module:heading/heading~Heading heading feature} to apply headings.
  * The heading command. It is used by the {@link module:heading/heading~Heading heading feature} to apply headings.
@@ -20,49 +21,55 @@ export default class HeadingCommand extends Command {
 	 * Creates an instance of the command.
 	 * Creates an instance of the command.
 	 *
 	 *
 	 * @param {module:core/editor/editor~Editor} editor Editor instance.
 	 * @param {module:core/editor/editor~Editor} editor Editor instance.
-	 * @param {Array.<module:heading/headingcommand~HeadingOption>} options Heading options to be used by the command instance.
+	 * @param {module:heading/headingcommand~HeadingOption} option An option to be used by the command instance.
 	 */
 	 */
-	constructor( editor, options, defaultOptionId ) {
+	constructor( editor, option ) {
 		super( editor );
 		super( editor );
 
 
+		Object.assign( this, option );
+
 		/**
 		/**
-		 * Heading options used by this command.
+		 * Name of the command
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @member {module:heading/headingcommand~HeadingOption}
+		 * @member {String}
 		 */
 		 */
-		this.options = options;
+		this.name = camelCase( 'heading ' + this.id );
 
 
 		/**
 		/**
-		 * The id of the default option among {@link #options}.
+		 * TODO
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @private
-		 * @member {module:heading/headingcommand~HeadingOption#id}
+		 * @member {}
 		 */
 		 */
-		this._defaultOptionId = defaultOptionId;
+		this.set( 'value', false );
+
+		// Update current value each time changes are done on document.
+		this.listenTo( editor.document, 'changesDone', () => this._updateValue() );
 
 
 		/**
 		/**
-		 * The currently selected heading option.
+		 * Unique identifier of the command, also element's name in the model.
+		 * See {@link module:heading/headingcommand~HeadingOption#id}.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @observable
-		 * @member {module:heading/headingcommand~HeadingOption} #value
+		 * @member {String} #id
 		 */
 		 */
-		this.set( 'value', this.defaultOption );
 
 
-		// Update current value each time changes are done on document.
-		this.listenTo( editor.document, 'changesDone', () => this._updateValue() );
-	}
+		/**
+		 * Element this command creates in the view.
+		 * See {@link module:heading/headingcommand~HeadingOption#element}.
+		 *
+		 * @readonly
+		 * @member {String} #element
+		 */
 
 
-	/**
-	 * The default option.
-	 *
-	 * @member {module:heading/headingcommand~HeadingOption} #defaultOption
-	 */
-	get defaultOption() {
-		// See https://github.com/ckeditor/ckeditor5/issues/98.
-		return this._getOptionById( this._defaultOptionId );
+		/**
+		 * Label of this command.
+		 * See {@link module:heading/headingcommand~HeadingOption#label}.
+		 *
+		 * @readonly
+		 * @member {String} #label
+		 */
 	}
 	}
 
 
 	/**
 	/**
@@ -70,16 +77,11 @@ export default class HeadingCommand extends Command {
 	 *
 	 *
 	 * @protected
 	 * @protected
 	 * @param {Object} [options] Options for executed command.
 	 * @param {Object} [options] Options for executed command.
-	 * @param {String} [options.id] The identifier of the heading option that should be applied. It should be one of the
-	 * {@link module:heading/headingcommand~HeadingOption heading options} provided to the command constructor. If this parameter is not
-	 * provided,
-	 * the value from {@link #defaultOption defaultOption} will be used.
 	 * @param {module:engine/model/batch~Batch} [options.batch] Batch to collect all the change steps.
 	 * @param {module:engine/model/batch~Batch} [options.batch] Batch to collect all the change steps.
 	 * New batch will be created if this option is not set.
 	 * New batch will be created if this option is not set.
 	 */
 	 */
 	_doExecute( options = {} ) {
 	_doExecute( options = {} ) {
-		// TODO: What should happen if option is not found?
-		const id = options.id || this.defaultOption.id;
+		const id = this.id;
 		const doc = this.editor.document;
 		const doc = this.editor.document;
 		const selection = doc.selection;
 		const selection = doc.selection;
 		const startPosition = selection.getFirstPosition();
 		const startPosition = selection.getFirstPosition();
@@ -87,8 +89,6 @@ export default class HeadingCommand extends Command {
 		// Storing selection ranges and direction to fix selection after renaming. See ckeditor5-engine#367.
 		// Storing selection ranges and direction to fix selection after renaming. See ckeditor5-engine#367.
 		const ranges = [ ...selection.getRanges() ];
 		const ranges = [ ...selection.getRanges() ];
 		const isSelectionBackward = selection.isBackward;
 		const isSelectionBackward = selection.isBackward;
-		// If current option is same as new option - toggle already applied option back to default one.
-		const shouldRemove = ( id === this.value.id );
 
 
 		// Collect elements to change option.
 		// Collect elements to change option.
 		// This implementation may not be future proof but it's satisfactory at this stage.
 		// This implementation may not be future proof but it's satisfactory at this stage.
@@ -116,16 +116,7 @@ export default class HeadingCommand extends Command {
 			const batch = options.batch || doc.batch();
 			const batch = options.batch || doc.batch();
 
 
 			for ( let element of elements ) {
 			for ( let element of elements ) {
-				// When removing applied option.
-				if ( shouldRemove ) {
-					if ( element.name === id ) {
-						batch.rename( element, this.defaultOption.id );
-					}
-				}
-				// When applying new option.
-				else {
-					batch.rename( element, id );
-				}
+				batch.rename( element, id );
 			}
 			}
 
 
 			// If range's selection start/end is placed directly in renamed block - we need to restore it's position
 			// If range's selection start/end is placed directly in renamed block - we need to restore it's position
@@ -134,17 +125,6 @@ export default class HeadingCommand extends Command {
 		} );
 		} );
 	}
 	}
 
 
-	/**
-	 * Returns the option by a given ID.
-	 *
-	 * @private
-	 * @param {String} id
-	 * @returns {module:heading/headingcommand~HeadingOption}
-	 */
-	_getOptionById( id ) {
-		return this.options.find( item => item.id === id ) || this.defaultOption;
-	}
-
 	/**
 	/**
 	 * Updates command's {@link #value value} based on current selection.
 	 * Updates command's {@link #value value} based on current selection.
 	 *
 	 *
@@ -155,7 +135,7 @@ export default class HeadingCommand extends Command {
 		const block = findTopmostBlock( position );
 		const block = findTopmostBlock( position );
 
 
 		if ( block ) {
 		if ( block ) {
-			this.value = this._getOptionById( block.name );
+			this.value = this.id == block.name;
 		}
 		}
 	}
 	}
 }
 }

+ 16 - 7
packages/ckeditor5-heading/src/headingengine.js

@@ -8,6 +8,7 @@
  */
  */
 
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
 import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
 import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter';
 import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
@@ -28,6 +29,14 @@ export default class HeadingEngine extends Plugin {
 	constructor( editor ) {
 	constructor( editor ) {
 		super( editor );
 		super( editor );
 
 
+		/**
+		 * A collection of heading commands associated with heading engine.
+		 *
+		 * @readonly
+		 * @member {module:utils/collection~Collection.<module:heading/headingcommand~HeadingCommand>}
+		 */
+		this.commands = new Collection();
+
 		editor.config.define( 'heading', {
 		editor.config.define( 'heading', {
 			options: [
 			options: [
 				{ id: 'paragraph', element: 'p', label: 'Paragraph' },
 				{ id: 'paragraph', element: 'p', label: 'Paragraph' },
@@ -70,11 +79,12 @@ export default class HeadingEngine extends Plugin {
 					.fromElement( option.element )
 					.fromElement( option.element )
 					.toElement( option.id );
 					.toElement( option.id );
 			}
 			}
-		}
 
 
-		// Register the heading command.
-		const command = new HeadingCommand( editor, options, defaultOptionId );
-		editor.commands.set( 'heading', command );
+			// Register the heading command for this option.
+			const command = new HeadingCommand( editor, option );
+			this.commands.add( command );
+			editor.commands.set( command.name, command );
+		}
 	}
 	}
 
 
 	/**
 	/**
@@ -84,7 +94,6 @@ export default class HeadingEngine extends Plugin {
 		// If the enter command is added to the editor, alter its behavior.
 		// If the enter command is added to the editor, alter its behavior.
 		// Enter at the end of a heading element should create a paragraph.
 		// Enter at the end of a heading element should create a paragraph.
 		const editor = this.editor;
 		const editor = this.editor;
-		const command = editor.commands.get( 'heading' );
 		const enterCommand = editor.commands.get( 'enter' );
 		const enterCommand = editor.commands.get( 'enter' );
 		const options = this._getLocalizedOptions();
 		const options = this._getLocalizedOptions();
 
 
@@ -94,8 +103,8 @@ export default class HeadingEngine extends Plugin {
 				const batch = data.batch;
 				const batch = data.batch;
 				const isHeading = options.some( option => option.id == positionParent.name );
 				const isHeading = options.some( option => option.id == positionParent.name );
 
 
-				if ( isHeading && positionParent.name != command.defaultOption.id && positionParent.childCount === 0 ) {
-					batch.rename( positionParent, command.defaultOption.id );
+				if ( isHeading && positionParent.name != defaultOptionId && positionParent.childCount === 0 ) {
+					batch.rename( positionParent, defaultOptionId );
 				}
 				}
 			} );
 			} );
 		}
 		}

+ 22 - 11
packages/ckeditor5-heading/tests/heading.js

@@ -63,44 +63,55 @@ describe( 'Heading', () => {
 			const executeSpy = testUtils.sinon.spy( editor, 'execute' );
 			const executeSpy = testUtils.sinon.spy( editor, 'execute' );
 			const dropdown = editor.ui.componentFactory.create( 'headings' );
 			const dropdown = editor.ui.componentFactory.create( 'headings' );
 
 
-			dropdown.id = 'foo';
+			dropdown.name = 'headingParagraph';
 			dropdown.fire( 'execute' );
 			dropdown.fire( 'execute' );
 
 
 			sinon.assert.calledOnce( executeSpy );
 			sinon.assert.calledOnce( executeSpy );
-			sinon.assert.calledWithExactly( executeSpy, 'heading', { id: 'foo' } );
+			sinon.assert.calledWithExactly( executeSpy, 'headingParagraph' );
 		} );
 		} );
 
 
 		it( 'should focus view after command execution', () => {
 		it( 'should focus view after command execution', () => {
 			const focusSpy = testUtils.sinon.spy( editor.editing.view, 'focus' );
 			const focusSpy = testUtils.sinon.spy( editor.editing.view, 'focus' );
 			const dropdown = editor.ui.componentFactory.create( 'headings' );
 			const dropdown = editor.ui.componentFactory.create( 'headings' );
 
 
+			dropdown.name = 'headingParagraph';
 			dropdown.fire( 'execute' );
 			dropdown.fire( 'execute' );
 
 
 			sinon.assert.calledOnce( focusSpy );
 			sinon.assert.calledOnce( focusSpy );
 		} );
 		} );
 
 
 		describe( 'model to command binding', () => {
 		describe( 'model to command binding', () => {
-			let command;
+			let commands;
 
 
 			beforeEach( () => {
 			beforeEach( () => {
-				command = editor.commands.get( 'heading' );
+				commands = editor.plugins.get( HeadingEngine ).commands;
 			} );
 			} );
 
 
 			it( 'isEnabled', () => {
 			it( 'isEnabled', () => {
-				expect( dropdown.buttonView.isEnabled ).to.be.true;
-				command.isEnabled = false;
+				for ( let command of commands ) {
+					command.isEnabled = false;
+				}
+
 				expect( dropdown.buttonView.isEnabled ).to.be.false;
 				expect( dropdown.buttonView.isEnabled ).to.be.false;
+
+				commands.get( 'heading2' ).isEnabled = true;
+				expect( dropdown.buttonView.isEnabled ).to.be.true;
 			} );
 			} );
 
 
 			it( 'label', () => {
 			it( 'label', () => {
+				for ( let command of commands ) {
+					command.value = false;
+				}
+
 				expect( dropdown.buttonView.label ).to.equal( 'Paragraph' );
 				expect( dropdown.buttonView.label ).to.equal( 'Paragraph' );
-				command.value = command.options[ 1 ];
-				expect( dropdown.buttonView.label ).to.equal( 'Heading 1' );
+
+				commands.get( 'heading2' ).value = true;
+				expect( dropdown.buttonView.label ).to.equal( 'Heading 2' );
 			} );
 			} );
 		} );
 		} );
 
 
 		describe( 'localization', () => {
 		describe( 'localization', () => {
-			let command;
+			let commands;
 
 
 			beforeEach( () => {
 			beforeEach( () => {
 				const editorElement = document.createElement( 'div' );
 				const editorElement = document.createElement( 'div' );
@@ -120,7 +131,7 @@ describe( 'Heading', () => {
 				.then( newEditor => {
 				.then( newEditor => {
 					editor = newEditor;
 					editor = newEditor;
 					dropdown = editor.ui.componentFactory.create( 'headings' );
 					dropdown = editor.ui.componentFactory.create( 'headings' );
-					command = editor.commands.get( 'heading' );
+					commands = editor.plugins.get( HeadingEngine ).commands;
 				} );
 				} );
 			} );
 			} );
 
 
@@ -136,7 +147,7 @@ describe( 'Heading', () => {
 				const buttonView = dropdown.buttonView;
 				const buttonView = dropdown.buttonView;
 
 
 				expect( buttonView.label ).to.equal( 'Akapit' );
 				expect( buttonView.label ).to.equal( 'Akapit' );
-				command.value = command.options[ 1 ];
+				commands.get( 'heading1' ).value = true;
 				expect( buttonView.label ).to.equal( 'Nagłówek 1' );
 				expect( buttonView.label ).to.equal( 'Nagłówek 1' );
 			} );
 			} );
 
 

+ 53 - 41
packages/ckeditor5-heading/tests/headingcommand.js

@@ -9,23 +9,24 @@ import Range from '@ckeditor/ckeditor5-engine/src/model/range';
 import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 
 const options = [
 const options = [
-	{ id: 'paragraph', element: 'p' },
-	{ id: 'heading1', element: 'h2' },
-	{ id: 'heading2', element: 'h3' },
-	{ id: 'heading3', element: 'h4' }
+	{ id: 'paragraph', element: 'p', label: 'P' },
+	{ id: 'heading1', element: 'h2', label: 'H2' },
+	{ id: 'heading2', element: 'h3', label: 'H3' },
+	{ id: 'heading3', element: 'h4', label: 'H4' }
 ];
 ];
 
 
 describe( 'HeadingCommand', () => {
 describe( 'HeadingCommand', () => {
-	let editor, document, command, root, schema;
+	let editor, document, commands, root, schema;
 
 
 	beforeEach( () => {
 	beforeEach( () => {
 		return ModelTestEditor.create().then( newEditor => {
 		return ModelTestEditor.create().then( newEditor => {
 			editor = newEditor;
 			editor = newEditor;
 			document = editor.document;
 			document = editor.document;
-			command = new HeadingCommand( editor, options, 'paragraph' );
+			commands = {};
 			schema = document.schema;
 			schema = document.schema;
 
 
 			for ( let option of options ) {
 			for ( let option of options ) {
+				commands[ option.id ] = new HeadingCommand( editor, option );
 				schema.registerItem( option.id, '$block' );
 				schema.registerItem( option.id, '$block' );
 			}
 			}
 
 
@@ -34,7 +35,23 @@ describe( 'HeadingCommand', () => {
 	} );
 	} );
 
 
 	afterEach( () => {
 	afterEach( () => {
-		command.destroy();
+		for ( let id in commands ) {
+			commands[ id ].destroy();
+		}
+	} );
+
+	describe( 'basic properties', () => {
+		for ( let option of options ) {
+			test( option );
+		}
+
+		function test( { id, element, label } ) {
+			it( `are set for option.id = ${ id }`, () => {
+				expect( commands[ id ].id ).to.equal( id );
+				expect( commands[ id ].element ).to.equal( element );
+				expect( commands[ id ].label ).to.equal( label );
+			} );
+		}
 	} );
 	} );
 
 
 	describe( 'value', () => {
 	describe( 'value', () => {
@@ -42,40 +59,33 @@ describe( 'HeadingCommand', () => {
 			test( option );
 			test( option );
 		}
 		}
 
 
-		function test( option ) {
-			it( `equals ${ option.id } when collapsed selection is placed inside ${ option.id } element`, () => {
-				setData( document, `<${ option.id }>foobar</${ option.id }>` );
+		function test( { id } ) {
+			it( `equals ${ id } when collapsed selection is placed inside ${ id } element`, () => {
+				setData( document, `<${ id }>foobar</${ id }>` );
 				const element = root.getChild( 0 );
 				const element = root.getChild( 0 );
 				document.selection.addRange( Range.createFromParentsAndOffsets( element, 3, element, 3 ) );
 				document.selection.addRange( Range.createFromParentsAndOffsets( element, 3, element, 3 ) );
 
 
-				expect( command.value ).to.equal( option );
+				expect( commands[ id ].value ).to.be.true;
 			} );
 			} );
 		}
 		}
-
-		it( 'should be equal to #defaultOption if option has not been found', () => {
-			schema.registerItem( 'div', '$block' );
-			setData( document, '<div>xyz</div>' );
-			const element = root.getChild( 0 );
-			document.selection.addRange( Range.createFromParentsAndOffsets( element, 1, element, 1 ) );
-
-			expect( command.value ).to.equal( command.defaultOption );
-		} );
 	} );
 	} );
 
 
 	describe( '_doExecute', () => {
 	describe( '_doExecute', () => {
 		it( 'should update value after execution', () => {
 		it( 'should update value after execution', () => {
+			const command = commands.heading1;
+
 			setData( document, '<paragraph>[]</paragraph>' );
 			setData( document, '<paragraph>[]</paragraph>' );
-			command._doExecute( { id: 'heading1' } );
+			command._doExecute();
 
 
 			expect( getData( document ) ).to.equal( '<heading1>[]</heading1>' );
 			expect( getData( document ) ).to.equal( '<heading1>[]</heading1>' );
-			expect( command.value ).to.be.object;
-			expect( command.value.id ).to.equal( 'heading1' );
-			expect( command.value.element ).to.equal( 'h2' );
+			expect( command.value ).to.be.true;
 		} );
 		} );
 
 
 		describe( 'custom options', () => {
 		describe( 'custom options', () => {
 			it( 'should use provided batch', () => {
 			it( 'should use provided batch', () => {
 				const batch = editor.document.batch();
 				const batch = editor.document.batch();
+				const command = commands.heading1;
+
 				setData( document, '<paragraph>foo[]bar</paragraph>' );
 				setData( document, '<paragraph>foo[]bar</paragraph>' );
 
 
 				expect( batch.deltas.length ).to.equal( 0 );
 				expect( batch.deltas.length ).to.equal( 0 );
@@ -96,24 +106,26 @@ describe( 'HeadingCommand', () => {
 
 
 			it( 'uses paragraph as default value', () => {
 			it( 'uses paragraph as default value', () => {
 				setData( document, '<heading1>foo[]bar</heading1>' );
 				setData( document, '<heading1>foo[]bar</heading1>' );
-				command._doExecute();
+				commands.paragraph._doExecute();
 
 
 				expect( getData( document ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
 				expect( getData( document ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
 			} );
 			} );
 
 
-			it( 'converts to default option when executed with already applied option', () => {
-				setData( document, '<heading1>foo[]bar</heading1>' );
-				command._doExecute( { id: 'heading1' } );
+			// it( 'converts to default option when executed with already applied option', () => {
+			// 	const command = commands.paragraph;
 
 
-				expect( getData( document ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
-			} );
+			// 	setData( document, '<heading1>foo[]bar</heading1>' );
+			// 	command._doExecute( { id: 'heading1' } );
+
+			// 	expect( getData( document ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
+			// } );
 
 
 			it( 'converts topmost blocks', () => {
 			it( 'converts topmost blocks', () => {
 				schema.registerItem( 'inlineImage', '$inline' );
 				schema.registerItem( 'inlineImage', '$inline' );
 				schema.allow( { name: '$text', inside: 'inlineImage' } );
 				schema.allow( { name: '$text', inside: 'inlineImage' } );
 
 
 				setData( document, '<heading1><inlineImage>foo[]</inlineImage>bar</heading1>' );
 				setData( document, '<heading1><inlineImage>foo[]</inlineImage>bar</heading1>' );
-				command._doExecute( { id: 'heading1' } );
+				commands.paragraph._doExecute();
 
 
 				expect( getData( document ) ).to.equal( '<paragraph><inlineImage>foo[]</inlineImage>bar</paragraph>' );
 				expect( getData( document ) ).to.equal( '<paragraph><inlineImage>foo[]</inlineImage>bar</paragraph>' );
 			} );
 			} );
@@ -121,7 +133,7 @@ describe( 'HeadingCommand', () => {
 			function test( from, to ) {
 			function test( from, to ) {
 				it( `converts ${ from.id } to ${ to.id } on collapsed selection`, () => {
 				it( `converts ${ from.id } to ${ to.id } on collapsed selection`, () => {
 					setData( document, `<${ from.id }>foo[]bar</${ from.id }>` );
 					setData( document, `<${ from.id }>foo[]bar</${ from.id }>` );
-					command._doExecute( { id: to.id } );
+					commands[ to.id ]._doExecute();
 
 
 					expect( getData( document ) ).to.equal( `<${ to.id }>foo[]bar</${ to.id }>` );
 					expect( getData( document ) ).to.equal( `<${ to.id }>foo[]bar</${ to.id }>` );
 				} );
 				} );
@@ -138,26 +150,26 @@ describe( 'HeadingCommand', () => {
 
 
 			it( 'converts all elements where selection is applied', () => {
 			it( 'converts all elements where selection is applied', () => {
 				setData( document, '<heading1>foo[</heading1><heading2>bar</heading2><heading2>]baz</heading2>' );
 				setData( document, '<heading1>foo[</heading1><heading2>bar</heading2><heading2>]baz</heading2>' );
-				command._doExecute( { id: 'paragraph' } );
+				commands.paragraph._doExecute();
 
 
 				expect( getData( document ) ).to.equal(
 				expect( getData( document ) ).to.equal(
 					'<paragraph>foo[</paragraph><paragraph>bar</paragraph><paragraph>]baz</paragraph>'
 					'<paragraph>foo[</paragraph><paragraph>bar</paragraph><paragraph>]baz</paragraph>'
 				);
 				);
 			} );
 			} );
 
 
-			it( 'resets to default value all elements with same option', () => {
-				setData( document, '<heading1>foo[</heading1><heading1>bar</heading1><heading2>baz</heading2>]' );
-				command._doExecute( { id: 'heading1' } );
+			// it( 'resets to default value all elements with same option', () => {
+			// 	setData( document, '<heading1>foo[</heading1><heading1>bar</heading1><heading2>baz</heading2>]' );
+			// 	commands.heading1._doExecute();
 
 
-				expect( getData( document ) ).to.equal(
-					'<paragraph>foo[</paragraph><paragraph>bar</paragraph><heading2>baz</heading2>]'
-				);
-			} );
+			// 	expect( getData( document ) ).to.equal(
+			// 		'<paragraph>foo[</paragraph><paragraph>bar</paragraph><heading2>baz</heading2>]'
+			// 	);
+			// } );
 
 
 			function test( from, to ) {
 			function test( from, to ) {
 				it( `converts ${ from.id } to ${ to.id } on non-collapsed selection`, () => {
 				it( `converts ${ from.id } to ${ to.id } on non-collapsed selection`, () => {
 					setData( document, `<${ from.id }>foo[bar</${ from.id }><${ from.id }>baz]qux</${ from.id }>` );
 					setData( document, `<${ from.id }>foo[bar</${ from.id }><${ from.id }>baz]qux</${ from.id }>` );
-					command._doExecute( { id: to.id } );
+					commands[ to.id ]._doExecute();
 
 
 					expect( getData( document ) ).to.equal( `<${ to.id }>foo[bar</${ to.id }><${ to.id }>baz]qux</${ to.id }>` );
 					expect( getData( document ) ).to.equal( `<${ to.id }>foo[bar</${ to.id }><${ to.id }>baz]qux</${ to.id }>` );
 				} );
 				} );

+ 16 - 6
packages/ckeditor5-heading/tests/headingengine.js

@@ -54,11 +54,11 @@ describe( 'HeadingEngine', () => {
 		expect( document.schema.check( { name: '$inline', inside: 'heading3' } ) ).to.be.true;
 		expect( document.schema.check( { name: '$inline', inside: 'heading3' } ) ).to.be.true;
 	} );
 	} );
 
 
-	it( 'should register option command', () => {
-		expect( editor.commands.has( 'heading' ) ).to.be.true;
-		const command = editor.commands.get( 'heading' );
-
-		expect( command ).to.be.instanceOf( HeadingCommand );
+	it( 'should register #commands', () => {
+		expect( editor.commands.get( 'headingParagraph' ) ).to.be.instanceOf( HeadingCommand );
+		expect( editor.commands.get( 'headingHeading1' ) ).to.be.instanceOf( HeadingCommand );
+		expect( editor.commands.get( 'headingHeading2' ) ).to.be.instanceOf( HeadingCommand );
+		expect( editor.commands.get( 'headingHeading3' ) ).to.be.instanceOf( HeadingCommand );
 	} );
 	} );
 
 
 	it( 'should convert heading1', () => {
 	it( 'should convert heading1', () => {
@@ -101,6 +101,12 @@ describe( 'HeadingEngine', () => {
 		expect( getData( document ) ).to.equal( '<heading1>foo</heading1><heading1>[]bar</heading1>' );
 		expect( getData( document ) ).to.equal( '<heading1>foo</heading1><heading1>[]bar</heading1>' );
 	} );
 	} );
 
 
+	it( 'should not blow up if there\'s no enter command in the editor', () => {
+		return VirtualTestEditor.create( {
+			plugins: [ HeadingEngine ]
+		} );
+	} );
+
 	describe( 'config', () => {
 	describe( 'config', () => {
 		describe( 'options', () => {
 		describe( 'options', () => {
 			describe( 'default value', () => {
 			describe( 'default value', () => {
@@ -129,7 +135,11 @@ describe( 'HeadingEngine', () => {
 				.then( editor => {
 				.then( editor => {
 					document = editor.document;
 					document = editor.document;
 
 
-					expect( editor.commands.get( 'heading' ).options ).to.deep.equal( options );
+					const commands = editor.plugins.get( HeadingEngine ).commands;
+
+					expect( commands ).to.have.length( 2 );
+					expect( commands.get( 0 ).id ).to.equal( options[ 0 ].id );
+					expect( commands.get( 1 ).id ).to.equal( options[ 1 ].id );
 
 
 					expect( document.schema.hasItem( 'paragraph' ) ).to.be.true;
 					expect( document.schema.hasItem( 'paragraph' ) ).to.be.true;
 					expect( document.schema.hasItem( 'h4' ) ).to.be.true;
 					expect( document.schema.hasItem( 'h4' ) ).to.be.true;