Browse Source

Merge pull request #7506 from ckeditor/i/7444-2SCM-refactor

Other (engine): The engine's util `bindTwoStepCaretToAttribute()` was removed. See #7444.
Feature (typing): Introduced `TwoStepCaretMovement` plugin. See #7444.

MINOR BREAKING CHANGE (engine): The `bindTwoStepCaretToAttribute()` utility function has been removed. Use `editor.plugins.get( TwoStepCaretMovement ).registerAttribute()` instead.
Maciej 5 years ago
parent
commit
d40bd58320

+ 1 - 1
packages/ckeditor5-link/package.json

@@ -13,6 +13,7 @@
     "@ckeditor/ckeditor5-core": "^20.0.0",
     "@ckeditor/ckeditor5-engine": "^20.0.0",
     "@ckeditor/ckeditor5-image": "^20.0.0",
+    "@ckeditor/ckeditor5-typing": "^20.0.0",
     "@ckeditor/ckeditor5-ui": "^20.0.0",
     "@ckeditor/ckeditor5-utils": "^20.0.0",
     "lodash-es": "^4.17.15"
@@ -26,7 +27,6 @@
     "@ckeditor/ckeditor5-enter": "^20.0.0",
     "@ckeditor/ckeditor5-paragraph": "^20.0.0",
     "@ckeditor/ckeditor5-theme-lark": "^20.0.0",
-    "@ckeditor/ckeditor5-typing": "^20.0.0",
     "@ckeditor/ckeditor5-undo": "^20.0.0"
   },
   "engines": {

+ 10 - 9
packages/ckeditor5-link/src/linkediting.js

@@ -9,7 +9,7 @@
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import MouseObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver';
-import bindTwoStepCaretToAttribute from '@ckeditor/ckeditor5-engine/src/utils/bindtwostepcarettoattribute';
+import TwoStepCaretMovement from '@ckeditor/ckeditor5-typing/src/twostepcaretmovement';
 import LinkCommand from './linkcommand';
 import UnlinkCommand from './unlinkcommand';
 import AutomaticDecorators from './utils/automaticdecorators';
@@ -43,6 +43,13 @@ export default class LinkEditing extends Plugin {
 	/**
 	 * @inheritDoc
 	 */
+	static get requires() {
+		return [ TwoStepCaretMovement ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
 	constructor( editor ) {
 		super( editor );
 
@@ -56,7 +63,6 @@ export default class LinkEditing extends Plugin {
 	 */
 	init() {
 		const editor = this.editor;
-		const locale = editor.locale;
 
 		// Allow link attribute on all inline nodes.
 		editor.model.schema.extend( '$text', { allowAttributes: 'linkHref' } );
@@ -93,13 +99,8 @@ export default class LinkEditing extends Plugin {
 		this._enableManualDecorators( linkDecorators.filter( item => item.mode === DECORATOR_MANUAL ) );
 
 		// Enable two-step caret movement for `linkHref` attribute.
-		bindTwoStepCaretToAttribute( {
-			view: editor.editing.view,
-			model: editor.model,
-			emitter: this,
-			attribute: 'linkHref',
-			locale
-		} );
+		const twoStepCaretMovementPlugin = editor.plugins.get( TwoStepCaretMovement );
+		twoStepCaretMovementPlugin.registerAttribute( 'linkHref' );
 
 		// Setup highlight over selected link.
 		this._setupLinkHighlight();

+ 18 - 9
packages/ckeditor5-link/tests/linkediting.js

@@ -75,15 +75,17 @@ describe( 'LinkEditing', () => {
 		expect( model.schema.checkAttribute( [ '$block' ], 'linkHref' ) ).to.be.false;
 	} );
 
-	// Let's check only the minimum to not duplicate `bindTwoStepCaretToAttribute()` tests.
+	// Let's check only the minimum to not duplicate `TwoStepCaretMovement` tests.
 	// Testing minimum is better than testing using spies that might give false positive results.
 	describe( 'two-step caret movement', () => {
-		it( 'should be bound to th `linkHref` attribute (LTR)', () => {
+		it( 'should be bound to the `linkHref` attribute (LTR)', () => {
+			const selection = editor.model.document.selection;
+
 			// Put selection before the link element.
 			setModelData( editor.model, '<paragraph>foo[]<$text linkHref="url">b</$text>ar</paragraph>' );
 
-			// The selection's gravity is not overridden because selection landed here not because of `keydown`.
-			expect( editor.model.document.selection.isGravityOverridden ).to.false;
+			// The selection's gravity should read attributes from the left.
+			expect( selection.hasAttribute( 'linkHref' ), 'hasAttribute( \'linkHref\' )' ).to.be.false;
 
 			// So let's simulate the `keydown` event.
 			editor.editing.view.document.fire( 'keydown', {
@@ -92,10 +94,13 @@ describe( 'LinkEditing', () => {
 				domTarget: document.body
 			} );
 
-			expect( editor.model.document.selection.isGravityOverridden ).to.true;
+			expect( getModelData( model ) ).to.equal( '<paragraph>foo<$text linkHref="url">[]b</$text>ar</paragraph>' );
+			// Selection should get the attributes from the right.
+			expect( selection.hasAttribute( 'linkHref' ), 'hasAttribute( \'linkHref\' )' ).to.be.true;
+			expect( selection.getAttribute( 'linkHref' ), 'linkHref attribute' ).to.equal( 'url' );
 		} );
 
-		it( 'should be bound to th `linkHref` attribute (RTL)', async () => {
+		it( 'should be bound to the `linkHref` attribute (RTL)', async () => {
 			const editor = await ClassicTestEditor.create( element, {
 				plugins: [ Paragraph, LinkEditing, Enter ],
 				language: {
@@ -105,12 +110,13 @@ describe( 'LinkEditing', () => {
 
 			model = editor.model;
 			view = editor.editing.view;
+			const selection = editor.model.document.selection;
 
 			// Put selection before the link element.
 			setModelData( editor.model, '<paragraph>foo[]<$text linkHref="url">b</$text>ar</paragraph>' );
 
-			// The selection's gravity is not overridden because selection landed here not because of `keydown`.
-			expect( editor.model.document.selection.isGravityOverridden ).to.false;
+			// The selection's gravity should read attributes from the left.
+			expect( selection.hasAttribute( 'linkHref' ), 'hasAttribute( \'linkHref\' )' ).to.be.false;
 
 			// So let's simulate the `keydown` event.
 			editor.editing.view.document.fire( 'keydown', {
@@ -119,7 +125,10 @@ describe( 'LinkEditing', () => {
 				domTarget: document.body
 			} );
 
-			expect( editor.model.document.selection.isGravityOverridden ).to.true;
+			expect( getModelData( model ) ).to.equal( '<paragraph>foo<$text linkHref="url">[]b</$text>ar</paragraph>' );
+			// Selection should get the attributes from the right.
+			expect( selection.hasAttribute( 'linkHref' ), 'hasAttribute( \'linkHref\' )' ).to.be.true;
+			expect( selection.getAttribute( 'linkHref' ), 'linkHref attribute' ).to.equal( 'url' );
 
 			await editor.destroy();
 		} );

+ 101 - 59
packages/ckeditor5-engine/src/utils/bindtwostepcarettoattribute.js

@@ -4,27 +4,30 @@
  */
 
 /**
- * @module engine/utils/bindtwostepcarettoattribute
+ * @module typing/twostepcaretmovement
  */
 
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
 import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
 import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
 
 /**
- * This helper enables the two-step caret (phantom) movement behavior for the given {@link module:engine/model/model~Model}
- * attribute on arrow right (<kbd>→</kbd>) and left (<kbd>←</kbd>) key press.
+ * This plugin enables the two-step caret (phantom) movement behavior for
+ * {@link module:typing/twostepcaretmovement~TwoStepCaretMovement#registerAttribute registered attributes}
+ * on arrow right (<kbd>→</kbd>) and left (<kbd>←</kbd>) key press.
  *
  * Thanks to this (phantom) caret movement the user is able to type before/after as well as at the
  * beginning/end of an attribute.
  *
- * **Note:** This helper support right–to–left (Arabic, Hebrew, etc.) content by mirroring its behavior
+ * **Note:** This plugin support right–to–left (Arabic, Hebrew, etc.) content by mirroring its behavior
  * but for the sake of simplicity examples showcase only left–to–right use–cases.
  *
  * # Forward movement
  *
  * ## "Entering" an attribute:
  *
- * When this behavior is enabled for the `a` attribute and the selection is right before it
+ * When this plugin is enabled and registered for the `a` attribute and the selection is right before it
  * (at the attribute boundary), pressing the right arrow key will not move the selection but update its
  * attributes accordingly:
  *
@@ -80,70 +83,109 @@ import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
  *   <kbd>←</kbd>
  *
  *   		<$text a="true">ba{}r</$text>b{}az
- *
- * @param {Object} options Helper options.
- * @param {module:engine/view/view~View} options.view View controller instance.
- * @param {module:engine/model/model~Model} options.model Data model instance.
- * @param {module:utils/dom/emittermixin~Emitter} options.emitter The emitter to which this behavior should be added
- * (e.g. a plugin instance).
- * @param {String} options.attribute Attribute for which this behavior will be added.
- * @param {module:utils/locale~Locale} options.locale The {@link module:core/editor/editor~Editor#locale} instance.
  */
-export default function bindTwoStepCaretToAttribute( { view, model, emitter, attribute, locale } ) {
-	const twoStepCaretHandler = new TwoStepCaretHandler( model, emitter, attribute );
-	const modelSelection = model.document.selection;
-
-	// Listen to keyboard events and handle the caret movement according to the 2-step caret logic.
-	//
-	// Note: This listener has the "high+1" priority:
-	// * "high" because of the filler logic implemented in the renderer which also engages on #keydown.
-	// When the gravity is overridden the attributes of the (model) selection attributes are reset.
-	// It may end up with the filler kicking in and breaking the selection.
-	// * "+1" because we would like to avoid collisions with other features (like Widgets), which
-	// take over the keydown events with the "high" priority. Two-step caret movement takes precedence
-	// over Widgets in that matter.
-	//
-	// Find out more in https://github.com/ckeditor/ckeditor5-engine/issues/1301.
-	emitter.listenTo( view.document, 'keydown', ( evt, data ) => {
-		// This implementation works only for collapsed selection.
-		if ( !modelSelection.isCollapsed ) {
-			return;
-		}
+export default class TwoStepCaretMovement extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'TwoStepCaretMovement';
+	}
 
-		// When user tries to expand the selection or jump over the whole word or to the beginning/end then
-		// two-steps movement is not necessary.
-		if ( data.shiftKey || data.altKey || data.ctrlKey ) {
-			return;
-		}
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
 
-		const arrowRightPressed = data.keyCode == keyCodes.arrowright;
-		const arrowLeftPressed = data.keyCode == keyCodes.arrowleft;
+		/**
+		 * A map of handlers for each attribute.
+		 *
+		 * @protected
+		 * @property {module:typing/twostepcaretmovement~TwoStepCaretMovement}
+		 */
+		this._handlers = new Map();
+	}
 
-		// When neither left or right arrow has been pressed then do noting.
-		if ( !arrowRightPressed && !arrowLeftPressed ) {
-			return;
-		}
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+		const model = editor.model;
+		const view = editor.editing.view;
+		const locale = editor.locale;
 
-		const position = modelSelection.getFirstPosition();
-		const contentDirection = locale.contentLanguageDirection;
-		let isMovementHandled;
+		const modelSelection = model.document.selection;
 
-		if ( ( contentDirection === 'ltr' && arrowRightPressed ) || ( contentDirection === 'rtl' && arrowLeftPressed ) ) {
-			isMovementHandled = twoStepCaretHandler.handleForwardMovement( position, data );
-		} else {
-			isMovementHandled = twoStepCaretHandler.handleBackwardMovement( position, data );
-		}
+		// Listen to keyboard events and handle the caret movement according to the 2-step caret logic.
+		//
+		// Note: This listener has the "high+1" priority:
+		// * "high" because of the filler logic implemented in the renderer which also engages on #keydown.
+		// When the gravity is overridden the attributes of the (model) selection attributes are reset.
+		// It may end up with the filler kicking in and breaking the selection.
+		// * "+1" because we would like to avoid collisions with other features (like Widgets), which
+		// take over the keydown events with the "high" priority. Two-step caret movement takes precedence
+		// over Widgets in that matter.
+		//
+		// Find out more in https://github.com/ckeditor/ckeditor5-engine/issues/1301.
+		this.listenTo( view.document, 'keydown', ( evt, data ) => {
+			// This implementation works only for collapsed selection.
+			if ( !modelSelection.isCollapsed ) {
+				return;
+			}
 
-		// Stop the keydown event if the two-step caret movement handled it. Avoid collisions
-		// with other features which may also take over the caret movement (e.g. Widget).
-		if ( isMovementHandled ) {
-			evt.stop();
-		}
-	}, { priority: priorities.get( 'high' ) + 1 } );
+			// When user tries to expand the selection or jump over the whole word or to the beginning/end then
+			// two-steps movement is not necessary.
+			if ( data.shiftKey || data.altKey || data.ctrlKey ) {
+				return;
+			}
+
+			const arrowRightPressed = data.keyCode == keyCodes.arrowright;
+			const arrowLeftPressed = data.keyCode == keyCodes.arrowleft;
+
+			// When neither left or right arrow has been pressed then do noting.
+			if ( !arrowRightPressed && !arrowLeftPressed ) {
+				return;
+			}
+
+			const position = modelSelection.getFirstPosition();
+			const contentDirection = locale.contentLanguageDirection;
+			let isMovementHandled = false;
+
+			if ( ( contentDirection === 'ltr' && arrowRightPressed ) || ( contentDirection === 'rtl' && arrowLeftPressed ) ) {
+				for ( const [ , handler ] of this._handlers ) {
+					isMovementHandled = isMovementHandled || handler.handleForwardMovement( position, data );
+				}
+			} else {
+				for ( const [ , handler ] of this._handlers ) {
+					isMovementHandled = isMovementHandled || handler.handleBackwardMovement( position, data );
+				}
+			}
+
+			// Stop the keydown event if the two-step caret movement handled it. Avoid collisions
+			// with other features which may also take over the caret movement (e.g. Widget).
+			if ( isMovementHandled ) {
+				evt.stop();
+			}
+		}, { priority: priorities.get( 'high' ) + 1 } );
+	}
+
+	/**
+	 * Registers a given attribute for the two-step caret movement.
+	 *
+	 * @param {String} attribute Name of the attribute to handle.
+	 */
+	registerAttribute( attribute ) {
+		this._handlers.set(
+			attribute,
+			new TwoStepCaretHandler( this.editor.model, this, attribute )
+		);
+	}
 }
 
 /**
- * This is a protected helper–class for {@link module:engine/utils/bindtwostepcarettoattribute}.
+ * This is a protected helper–class for {@link module:typing/twostepcaretmovement}.
  * It handles the state of the 2-step caret movement for a single {@link module:engine/model/model~Model}
  * attribute upon the `keypress` in the {@link module:engine/view/view~View}.
  *

packages/ckeditor5-engine/tests/manual/tickets/1301/1.html → packages/ckeditor5-typing/tests/manual/1301/1.html


+ 4 - 10
packages/ckeditor5-engine/tests/manual/tickets/1301/1.js

@@ -11,23 +11,17 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
 
-import bindTwoStepCaretToAttribute from '../../../../src/utils/bindtwostepcarettoattribute';
+import TwoStepCaretMovement from '../../../src/twostepcaretmovement';
 
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
-		plugins: [ Essentials, Paragraph, Bold, Italic ],
+		plugins: [ Essentials, Paragraph, Bold, Italic, TwoStepCaretMovement ],
 		toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ]
 	} )
 	.then( editor => {
-		const bold = editor.plugins.get( Bold );
+		const twoStepCaretMovement = editor.plugins.get( TwoStepCaretMovement );
 
-		bindTwoStepCaretToAttribute( {
-			view: editor.editing.view,
-			model: editor.model,
-			emitter: bold,
-			attribute: 'bold',
-			locale: editor.locale
-		} );
+		twoStepCaretMovement.registerAttribute( 'bold' );
 	} )
 	.catch( err => {
 		console.error( err.stack );

+ 1 - 1
packages/ckeditor5-engine/tests/manual/tickets/1301/1.md

@@ -1,4 +1,4 @@
-## Two-steps caret movement [#1301](https://github.com/ckeditor/ckeditor5-engine/issues/1301)
+## Two-steps caret movement [ckeditor5-engine#1301](https://github.com/ckeditor/ckeditor5-engine/issues/1301)
 
 1. Put the selection at the end of the content.
 2. Press the <kbd>←</kbd> key 3 times.

packages/ckeditor5-engine/tests/manual/two-step-caret.html → packages/ckeditor5-typing/tests/manual/two-step-caret.html


+ 9 - 35
packages/ckeditor5-engine/tests/manual/two-step-caret.js

@@ -12,31 +12,18 @@ import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
 import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
 
-import bindTwoStepCaretToAttribute from '../../src/utils/bindtwostepcarettoattribute';
+import TwoStepCaretMovement from '../../src/twostepcaretmovement';
 
 ClassicEditor
 	.create( document.querySelector( '#editor-ltr' ), {
-		plugins: [ Essentials, Paragraph, Underline, Bold, Italic ],
+		plugins: [ Essentials, Paragraph, Underline, Bold, Italic, TwoStepCaretMovement ],
 		toolbar: [ 'undo', 'redo', '|', 'bold', 'underline', 'italic' ]
 	} )
 	.then( editor => {
-		const bold = editor.plugins.get( Italic );
-		const underline = editor.plugins.get( Underline );
+		const twoStepCaretMovement = editor.plugins.get( TwoStepCaretMovement );
 
-		bindTwoStepCaretToAttribute( {
-			view: editor.editing.view,
-			model: editor.model,
-			emitter: bold,
-			attribute: 'italic',
-			locale: editor.locale
-		} );
-		bindTwoStepCaretToAttribute( {
-			view: editor.editing.view,
-			model: editor.model,
-			emitter: underline,
-			attribute: 'underline',
-			locale: editor.locale
-		} );
+		twoStepCaretMovement.registerAttribute( 'italic' );
+		twoStepCaretMovement.registerAttribute( 'underline' );
 	} )
 	.catch( err => {
 		console.error( err.stack );
@@ -47,27 +34,14 @@ ClassicEditor
 		language: {
 			content: 'he'
 		},
-		plugins: [ Essentials, Paragraph, Underline, Bold, Italic ],
+		plugins: [ Essentials, Paragraph, Underline, Bold, Italic, TwoStepCaretMovement ],
 		toolbar: [ 'undo', 'redo', '|', 'bold', 'underline', 'italic' ]
 	} )
 	.then( editor => {
-		const bold = editor.plugins.get( Italic );
-		const underline = editor.plugins.get( Underline );
+		const twoStepCaretMovement = editor.plugins.get( TwoStepCaretMovement );
 
-		bindTwoStepCaretToAttribute( {
-			view: editor.editing.view,
-			model: editor.model,
-			emitter: bold,
-			attribute: 'italic',
-			locale: editor.locale
-		} );
-		bindTwoStepCaretToAttribute( {
-			view: editor.editing.view,
-			model: editor.model,
-			emitter: underline,
-			attribute: 'underline',
-			locale: editor.locale
-		} );
+		twoStepCaretMovement.registerAttribute( 'italic' );
+		twoStepCaretMovement.registerAttribute( 'underline' );
 	} )
 	.catch( err => {
 		console.error( err.stack );

+ 1 - 1
packages/ckeditor5-engine/tests/manual/two-step-caret.md

@@ -1,4 +1,4 @@
-## Two-steps caret movement [#1286](https://github.com/ckeditor/ckeditor5-engine/issues/1289)
+## Two-steps caret movement [ckeditor5-engine#1286](https://github.com/ckeditor/ckeditor5-engine/issues/1289)
 
 ### Moving right
 1. Put selection one character before the underline

+ 12 - 30
packages/ckeditor5-engine/tests/utils/bindtwostepcarettoattribute.js

@@ -7,16 +7,16 @@
 
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
-import DomEventData from '../../src/view/observer/domeventdata';
+import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
 import EventInfo from '@ckeditor/ckeditor5-utils/src/eventinfo';
-import bindTwoStepCaretToAttribute, { TwoStepCaretHandler } from '../../src/utils/bindtwostepcarettoattribute';
-import Position from '../../src/model/position';
+import TwoStepCaretMovement, { TwoStepCaretHandler } from '../src/twostepcaretmovement';
+import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
-import { setData } from '../../src/dev-utils/model';
+import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
-describe( 'bindTwoStepCaretToAttribute()', () => {
-	let editor, model, emitter, selection, view, locale;
+describe( 'TwoStepCaretMovement()', () => {
+	let editor, model, emitter, selection, view, plugin;
 	let preventDefaultSpy, evtStopSpy;
 
 	testUtils.createSinonSandbox();
@@ -24,12 +24,12 @@ describe( 'bindTwoStepCaretToAttribute()', () => {
 	beforeEach( () => {
 		emitter = Object.create( DomEmitterMixin );
 
-		return VirtualTestEditor.create().then( newEditor => {
+		return VirtualTestEditor.create( { plugins: [ TwoStepCaretMovement ] } ).then( newEditor => {
 			editor = newEditor;
 			model = editor.model;
 			selection = model.document.selection;
 			view = editor.editing.view;
-			locale = editor.locale;
+			plugin = editor.plugins.get( TwoStepCaretMovement );
 
 			preventDefaultSpy = sinon.spy();
 			evtStopSpy = sinon.spy();
@@ -45,13 +45,7 @@ describe( 'bindTwoStepCaretToAttribute()', () => {
 			editor.conversion.for( 'upcast' ).elementToAttribute( { view: 'c', model: 'c' } );
 			editor.conversion.elementToElement( { model: 'paragraph', view: 'p' } );
 
-			bindTwoStepCaretToAttribute( {
-				view: editor.editing.view,
-				model: editor.model,
-				emitter,
-				attribute: 'a',
-				locale
-			} );
+			plugin.registerAttribute( 'a' );
 		} );
 	} );
 
@@ -560,13 +554,7 @@ describe( 'bindTwoStepCaretToAttribute()', () => {
 
 	describe( 'multiple attributes', () => {
 		beforeEach( () => {
-			bindTwoStepCaretToAttribute( {
-				view: editor.editing.view,
-				model: editor.model,
-				emitter,
-				attribute: 'c',
-				locale
-			} );
+			plugin.registerAttribute( 'c' );
 		} );
 
 		it( 'should work with the two-step caret movement (moving right)', () => {
@@ -786,12 +774,12 @@ describe( 'bindTwoStepCaretToAttribute()', () => {
 		it( 'should use the opposite helper methods (RTL content direction)', () => {
 			const forwardStub = testUtils.sinon.stub( TwoStepCaretHandler.prototype, 'handleForwardMovement' );
 			const backwardStub = testUtils.sinon.stub( TwoStepCaretHandler.prototype, 'handleBackwardMovement' );
-			const emitter = Object.create( DomEmitterMixin );
 
 			let model;
 
 			return VirtualTestEditor
 				.create( {
+					plugins: [ TwoStepCaretMovement ],
 					language: {
 						content: 'ar'
 					}
@@ -812,13 +800,7 @@ describe( 'bindTwoStepCaretToAttribute()', () => {
 					newEditor.conversion.for( 'upcast' ).elementToAttribute( { view: 'c', model: 'c' } );
 					newEditor.conversion.elementToElement( { model: 'paragraph', view: 'p' } );
 
-					bindTwoStepCaretToAttribute( {
-						view: newEditor.editing.view,
-						model: newEditor.model,
-						emitter,
-						attribute: 'a',
-						locale: newEditor.locale
-					} );
+					newEditor.plugins.get( TwoStepCaretMovement ).registerAttribute( 'a' );
 
 					return newEditor;
 				} )