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

Extract MentionCommand tests and refactor mention editing tests.

Maciej Gołaszewski 6 лет назад
Родитель
Сommit
a32d6d3767

+ 36 - 16
packages/ckeditor5-mention/src/mentioncommand.js

@@ -13,6 +13,22 @@ import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
 /**
  * The mention command.
  *
+ * The command is registered by the {@link module:mention/mentionediting~MentionEditing} as `'mention'`.
+ *
+ * To insert mention on range, execute the command and specify, mention object and range to replace:
+ *
+ *		const focus = editor.model.document.selection.focus;
+ *
+ *		editor.execute( 'mention', {
+ *			mention: {
+ *				name: 'Foo',
+ *				id: '1234',
+ *				title: 'Big Foo'
+ *			},
+ *			marker: '#',
+ *			range: model.createRange( focus, focus.getShiftedBy( -1 ) )
+ *		} );
+ *
  * @extends module:core/command~Command
  */
 export default class MentionCommand extends Command {
@@ -20,42 +36,46 @@ export default class MentionCommand extends Command {
 	 * @inheritDoc
 	 */
 	refresh() {
-		// @todo implement refresh
-		this.isEnabled = true;
+		const model = this.editor.model;
+		const doc = model.document;
+
+		this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, 'mention' );
 	}
 
 	/**
 	 * Executes the command.
 	 *
-	 * @protected
 	 * @param {Object} [options] Options for the executed command.
-	 * @param {String} [options.marker='@'] The mention marker.
-	 * @param {String} options.mention.
-	 * @param {String} [options.range].
+	 * @param {Object|String} options.mention Mention object to insert. If passed a string it will be used to create a plain object with
+	 * name attribute equal to passed string.
+	 * @param {String} [options.marker='@'] The mention marker to insert.
+	 * @param {String} [options.range] Range to replace. Note that replace range might be shorter then inserted text with mention attribute.
 	 * @fires execute
 	 */
-	execute( options = {} ) {
+	execute( options ) {
 		const model = this.editor.model;
 		const document = model.document;
 		const selection = document.selection;
 
 		const marker = options.marker || '@';
 
-		const mention = options.mention;
-		const range = options.range || selection.getFirstRange();
+		const mention = typeof options.mention == 'string' ? { name: options.mention } : options.mention;
 
-		const name = mention.name || mention;
+		const range = options.range || selection.getFirstRange();
 
 		model.change( writer => {
-			writer.remove( range );
+			const currentAttributes = toMap( selection.getAttributes() );
+			const attributesWithMention = new Map( currentAttributes.entries() );
+			attributesWithMention.set( 'mention', mention );
 
-			const selectionAttributes = toMap( selection.getAttributes() );
-			const attributes = new Map( selectionAttributes.entries() );
+			const mentionText = `${ marker }${ mention.name }`;
 
-			attributes.set( 'mention', mention );
+			// Replace range with a text with mention.
+			writer.remove( range );
+			writer.insertText( mentionText, attributesWithMention, range.start );
 
-			writer.insertText( `${ marker }${ name }`, attributes, range.start );
-			writer.insertText( ' ', selectionAttributes, model.document.selection.focus );
+			// Insert space after a mention.
+			writer.insertText( ' ', currentAttributes, model.document.selection.focus );
 		} );
 	}
 }

+ 123 - 0
packages/ckeditor5-mention/tests/mentioncommand.js

@@ -0,0 +1,123 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
+import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+import MentionCommand from '../src/mentioncommand';
+
+describe( 'MentionCommand', () => {
+	let editor, command, model, doc, selection;
+
+	beforeEach( () => {
+		return ModelTestEditor
+			.create()
+			.then( newEditor => {
+				editor = newEditor;
+				model = editor.model;
+				doc = model.document;
+				selection = doc.selection;
+
+				model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+				model.schema.register( 'x', { inheritAllFrom: '$block' } );
+				model.schema.extend( '$text', { allowAttributes: [ 'mention' ] } );
+
+				command = new MentionCommand( editor );
+			} );
+	} );
+
+	afterEach( () => {
+		command.destroy();
+
+		return editor.destroy();
+	} );
+
+	describe( 'isEnabled', () => {
+		it( 'should return true if characters with the attribute can be placed at caret position', () => {
+			setData( model, '<paragraph>f[]oo</paragraph>' );
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'should return false if characters with the attribute cannot be placed at caret position', () => {
+			model.schema.addAttributeCheck( ( ctx, attributeName ) => {
+				// Allow 'bold' on p>$text.
+				if ( ctx.endsWith( 'x $text' ) && attributeName == 'mention' ) {
+					return false;
+				}
+			} );
+
+			setData( model, '<x>fo[]o</x>' );
+			expect( command.isEnabled ).to.be.false;
+		} );
+	} );
+
+	describe( 'execute()', () => {
+		it( 'inserts mention attribute for given range', () => {
+			setData( model, '<paragraph>foo @Jo[]bar</paragraph>' );
+
+			command.execute( {
+				mention: { name: 'John' },
+				range: model.createRange( selection.focus.getShiftedBy( -3 ), selection.focus )
+			} );
+
+			expect( getData( model ) ).to.equal( '<paragraph>foo <$text mention="{"name":"John"}">@John</$text> []bar</paragraph>' );
+		} );
+
+		it( 'inserts mention object if mention was passed as string', () => {
+			setData( model, '<paragraph>foo @Jo[]bar</paragraph>' );
+
+			command.execute( {
+				mention: 'John',
+				range: model.createRange( selection.focus.getShiftedBy( -3 ), selection.focus )
+			} );
+
+			expect( getData( model ) ).to.equal( '<paragraph>foo <$text mention="{"name":"John"}">@John</$text> []bar</paragraph>' );
+		} );
+
+		it( 'inserts mention attribute with passed marker for given range', () => {
+			setData( model, '<paragraph>foo @Jo[]bar</paragraph>' );
+
+			const end = model.createPositionAt( selection.focus );
+			const start = end.getShiftedBy( -3 );
+
+			command.execute( {
+				mention: { name: 'John' },
+				range: model.createRange( start, end ),
+				marker: '#'
+			} );
+
+			expect( getData( model ) ).to.equal( '<paragraph>foo <$text mention="{"name":"John"}">#John</$text> []bar</paragraph>' );
+		} );
+
+		it( 'inserts mention attribute at current selection if no range was passed', () => {
+			setData( model, '<paragraph>foo []bar</paragraph>' );
+
+			command.execute( {
+				mention: { name: 'John' }
+			} );
+
+			expect( getData( model ) ).to.equal( '<paragraph>foo <$text mention="{"name":"John"}">@John</$text> []bar</paragraph>' );
+		} );
+
+		it( 'should set also other styles in inserted text', () => {
+			model.schema.extend( '$text', { allowAttributes: [ 'bold' ] } );
+
+			setData( model, '<paragraph><$text bold="true">foo@John[]bar</$text></paragraph>' );
+
+			command.execute( {
+				mention: { name: 'John' },
+				range: model.createRange( selection.focus.getShiftedBy( -5 ), selection.focus )
+			} );
+
+			expect( getData( model ) ).to.equal(
+				'<paragraph>' +
+					'<$text bold="true">foo</$text>' +
+					'<$text bold="true" mention="{"name":"John"}">@John</$text>' +
+					'<$text bold="true"> []bar</$text>' +
+				'</paragraph>'
+			);
+		} );
+	} );
+} );

+ 204 - 214
packages/ckeditor5-mention/tests/mentionediting.js

@@ -6,321 +6,311 @@
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import MentionEditing from '../src/mentionediting';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
-import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import { getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
+import MentionCommand from '../src/mentioncommand';
 
 describe( 'MentionEditing', () => {
 	testUtils.createSinonSandbox();
+	let editor, model, doc;
+
+	afterEach( () => {
+		if ( editor ) {
+			return editor.destroy();
+		}
+	} );
 
 	it( 'should be named', () => {
 		expect( MentionEditing.pluginName ).to.equal( 'MentionEditing' );
 	} );
 
-	describe( 'init()', () => {
-		let editor, model, doc;
+	it( 'should be loaded', () => {
+		return createTestEditor()
+			.then( newEditor => {
+				expect( newEditor.plugins.get( MentionEditing ) ).to.be.instanceOf( MentionEditing );
+			} );
+	} );
 
-		afterEach( () => {
-			if ( editor ) {
-				return editor.destroy();
-			}
-		} );
+	it( 'should set proper schema rules', () => {
+		return createTestEditor()
+			.then( newEditor => {
+				model = newEditor.model;
+
+				expect( model.schema.checkAttribute( [ '$root', '$text' ], 'mention' ) ).to.be.true;
 
-		it( 'should be loaded', () => {
+				expect( model.schema.checkAttribute( [ '$block', '$text' ], 'mention' ) ).to.be.true;
+				expect( model.schema.checkAttribute( [ '$clipboardHolder', '$text' ], 'mention' ) ).to.be.true;
+
+				expect( model.schema.checkAttribute( [ '$block' ], 'mention' ) ).to.be.false;
+			} );
+	} );
+
+	it( 'should register mention command', () => {
+		return createTestEditor()
+			.then( newEditor => {
+				const command = newEditor.commands.get( 'mention' );
+
+				expect( command ).to.be.instanceof( MentionCommand );
+			} );
+	} );
+
+	describe( 'conversion in the data pipeline', () => {
+		beforeEach( () => {
 			return createTestEditor()
 				.then( newEditor => {
-					expect( newEditor.plugins.get( MentionEditing ) ).to.be.instanceOf( MentionEditing );
+					editor = newEditor;
+					model = editor.model;
+					doc = model.document;
 				} );
 		} );
 
-		it( 'should set proper schema rules', () => {
-			return createTestEditor()
-				.then( newEditor => {
-					model = newEditor.model;
+		it( 'should convert <span class="mention" data-mention="John"> to mention attribute', () => {
+			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 
-					expect( model.schema.checkAttribute( [ '$root', '$text' ], 'mention' ) ).to.be.true;
+			const textNode = doc.getRoot().getChild( 0 ).getChild( 1 );
 
-					expect( model.schema.checkAttribute( [ '$block', '$text' ], 'mention' ) ).to.be.true;
-					expect( model.schema.checkAttribute( [ '$clipboardHolder', '$text' ], 'mention' ) ).to.be.true;
+			expect( textNode ).to.not.be.null;
+			expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_id' );
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', 'John' );
 
-					expect( model.schema.checkAttribute( [ '$block' ], 'mention' ) ).to.be.false;
-				} );
+			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 		} );
 
-		describe( 'conversion in the data pipeline', () => {
-			beforeEach( () => {
-				return createTestEditor()
-					.then( newEditor => {
-						editor = newEditor;
-						model = editor.model;
-						doc = model.document;
-					} );
-			} );
+		it( 'should convert consecutive mentions spans as two text nodes and two spans in the view', () => {
+			editor.setData(
+				'<p>' +
+				'<span class="mention" data-mention="John">@John</span>' +
+				'<span class="mention" data-mention="John">@John</span>' +
+				'</p>'
+			);
 
-			it( 'should convert <span class="mention" data-mention="John"> to mention attribute', () => {
-				editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			// getModelData() merges text blocks with "same" attributes:
+			// So expected: <$text mention="{"name":"John"}">@John</$text><$text mention="{"name":"John"}">@John</$text>'
+			// Is returned as: <$text mention="{"name":"John"}">@John@John</$text>'
+			const paragraph = doc.getRoot().getChild( 0 );
 
-				const textNode = doc.getRoot().getChild( 0 ).getChild( 1 );
+			expect( paragraph.childCount ).to.equal( 2 );
 
+			assertTextNode( paragraph.getChild( 0 ) );
+			assertTextNode( paragraph.getChild( 1 ) );
+
+			const firstMentionId = paragraph.getChild( 0 ).getAttribute( 'mention' )._id;
+			const secondMentionId = paragraph.getChild( 1 ).getAttribute( 'mention' )._id;
+
+			expect( firstMentionId ).to.not.equal( secondMentionId );
+
+			expect( editor.getData() ).to.equal(
+				'<p>' +
+				'<span class="mention" data-mention="John">@John</span>' +
+				'<span class="mention" data-mention="John">@John</span>' +
+				'</p>'
+			);
+
+			function assertTextNode( textNode ) {
 				expect( textNode ).to.not.be.null;
 				expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
 				expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_id' );
 				expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', 'John' );
+			}
+		} );
 
-				expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
-			} );
-
-			it( 'should convert consecutive mentions spans as two text nodes and two spans in the view', () => {
-				editor.setData(
-					'<p>' +
-					'<span class="mention" data-mention="John">@John</span>' +
-					'<span class="mention" data-mention="John">@John</span>' +
-					'</p>'
-				);
-
-				// getModelData() merges text blocks with "same" attributes:
-				// So expected: <$text mention="{"name":"John"}">@John</$text><$text mention="{"name":"John"}">@John</$text>'
-				// Is returned as: <$text mention="{"name":"John"}">@John@John</$text>'
-				const paragraph = doc.getRoot().getChild( 0 );
+		it( 'should not convert partial mentions', () => {
+			editor.setData( '<p><span class="mention" data-mention="John">@Jo</span></p>' );
 
-				expect( paragraph.childCount ).to.equal( 2 );
+			expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>@Jo</paragraph>' );
 
-				assertTextNode( paragraph.getChild( 0 ) );
-				assertTextNode( paragraph.getChild( 1 ) );
+			expect( editor.getData() ).to.equal( '<p>@Jo</p>' );
+		} );
+	} );
 
-				const firstMentionId = paragraph.getChild( 0 ).getAttribute( 'mention' )._id;
-				const secondMentionId = paragraph.getChild( 1 ).getAttribute( 'mention' )._id;
+	describe( 'selection post fixer', () => {
+		beforeEach( () => {
+			return createTestEditor()
+				.then( newEditor => {
+					editor = newEditor;
+					model = editor.model;
+					doc = model.document;
+				} );
+		} );
 
-				expect( firstMentionId ).to.not.equal( secondMentionId );
+		it( 'should remove mention attribute from a selection if selection is on right side of a mention', () => {
+			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span>bar</p>' );
 
-				expect( editor.getData() ).to.equal(
-					'<p>' +
-					'<span class="mention" data-mention="John">@John</span>' +
-					'<span class="mention" data-mention="John">@John</span>' +
-					'</p>'
-				);
+			model.change( writer => {
+				const paragraph = doc.getRoot().getChild( 0 );
 
-				function assertTextNode( textNode ) {
-					expect( textNode ).to.not.be.null;
-					expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
-					expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_id' );
-					expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', 'John' );
-				}
+				writer.setSelection( paragraph, 9 );
 			} );
 
-			it( 'should remove mention on adding a text inside mention', () => {
-				editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			expect( Array.from( doc.selection.getAttributes() ) ).to.deep.equal( [] );
+		} );
 
-				const textNode = doc.getRoot().getChild( 0 ).getChild( 1 );
+		it( 'should allow to type after a mention', () => {
+			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span>bar</p>' );
 
-				expect( textNode ).to.not.be.null;
-				expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
-				expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_id' );
-				expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', 'John' );
+			model.change( writer => {
+				const paragraph = doc.getRoot().getChild( 0 );
 
-				model.change( writer => {
-					const paragraph = doc.getRoot().getChild( 0 );
+				writer.setSelection( paragraph, 9 );
 
-					writer.setSelection( paragraph, 6 );
+				writer.insertText( ' ', paragraph, 9 );
+			} );
 
-					writer.insertText( 'a', doc.selection.getAttributes(), writer.createPositionAt( paragraph, 6 ) );
+			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+		} );
+	} );
+
+	describe( 'removing partial mention post fixer', () => {
+		beforeEach( () => {
+			return createTestEditor()
+				.then( newEditor => {
+					editor = newEditor;
+					model = editor.model;
+					doc = model.document;
 				} );
+		} );
 
-				expect( getModelData( model, { withoutSelection: true } ) )
-					.to.equal( '<paragraph>foo @Jaohn bar</paragraph>' );
+		it( 'should remove mention on adding a text inside mention', () => {
+			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 
-				expect( editor.getData() ).to.equal( '<p>foo @Jaohn bar</p>' );
-			} );
+			const textNode = doc.getRoot().getChild( 0 ).getChild( 1 );
 
-			it( 'should remove mention on removing a text inside mention', () => {
-				editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			expect( textNode ).to.not.be.null;
+			expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_id' );
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', 'John' );
 
+			model.change( writer => {
 				const paragraph = doc.getRoot().getChild( 0 );
 
-				model.change( writer => {
-					writer.setSelection( paragraph, 6 );
-				} );
+				writer.setSelection( paragraph, 6 );
 
-				model.enqueueChange( () => {
-					model.modifySelection( doc.selection, { direction: 'backward', unit: 'codepoint' } );
-					model.deleteContent( doc.selection );
-				} );
-
-				expect( editor.getData() ).to.equal( '<p>foo @ohn bar</p>' );
+				writer.insertText( 'a', doc.selection.getAttributes(), writer.createPositionAt( paragraph, 6 ) );
 			} );
 
-			it( 'should remove mention on removing a text at the and of a mention', () => {
-				editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			expect( getModelData( model, { withoutSelection: true } ) )
+				.to.equal( '<paragraph>foo @Jaohn bar</paragraph>' );
 
-				const paragraph = doc.getRoot().getChild( 0 );
+			expect( editor.getData() ).to.equal( '<p>foo @Jaohn bar</p>' );
+		} );
 
-				// Set selection at the end of a John.
-				model.change( writer => {
-					writer.setSelection( paragraph, 9 );
-				} );
+		it( 'should remove mention on removing a text inside mention', () => {
+			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 
-				model.enqueueChange( () => {
-					model.modifySelection( doc.selection, { direction: 'backward', unit: 'codepoint' } );
-					model.deleteContent( doc.selection );
-				} );
+			const paragraph = doc.getRoot().getChild( 0 );
 
-				expect( editor.getData() ).to.equal( '<p>foo @Joh bar</p>' );
+			model.change( writer => {
+				writer.setSelection( paragraph, 6 );
 			} );
 
-			it( 'should not remove mention on removing a text just after a mention', () => {
-				editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			model.enqueueChange( () => {
+				model.modifySelection( doc.selection, { direction: 'backward', unit: 'codepoint' } );
+				model.deleteContent( doc.selection );
+			} );
 
-				const paragraph = doc.getRoot().getChild( 0 );
+			expect( editor.getData() ).to.equal( '<p>foo @ohn bar</p>' );
+		} );
 
-				// Set selection before bar.
-				model.change( writer => {
-					writer.setSelection( paragraph, 10 );
-				} );
+		it( 'should remove mention on removing a text at the and of a mention', () => {
+			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 
-				model.enqueueChange( () => {
-					model.modifySelection( doc.selection, { direction: 'backward', unit: 'codepoint' } );
-					model.deleteContent( doc.selection );
-				} );
+			const paragraph = doc.getRoot().getChild( 0 );
 
-				expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span>bar</p>' );
+			// Set selection at the end of a John.
+			model.change( writer => {
+				writer.setSelection( paragraph, 9 );
 			} );
 
-			it( 'should set also other styles in inserted text', () => {
-				model.schema.extend( '$text', { allowAttributes: [ 'bold' ] } );
-				editor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );
+			model.enqueueChange( () => {
+				model.modifySelection( doc.selection, { direction: 'backward', unit: 'codepoint' } );
+				model.deleteContent( doc.selection );
+			} );
 
-				setModelData( model, '<paragraph><$text bold="true">foo@John[]bar</$text></paragraph>' );
+			expect( editor.getData() ).to.equal( '<p>foo @Joh bar</p>' );
+		} );
 
-				const start = model.createPositionAt( doc.getRoot().getChild( 0 ), 3 );
+		it( 'should not remove mention on removing a text just after a mention', () => {
+			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 
-				editor.execute( 'mention', {
-					mention: { name: 'John' },
-					range: model.createRange( start, start.getShiftedBy( 5 ) )
-				} );
+			const paragraph = doc.getRoot().getChild( 0 );
 
-				expect( editor.getData() ).to.equal(
-					'<p>' +
-					'<strong>foo</strong>' +
-					'<span class="mention" data-mention="John">' +
-						'<strong>@John</strong>' +
-					'</span>' +
-					'<strong> bar</strong>' +
-					'</p>'
-				);
+			// Set selection before bar.
+			model.change( writer => {
+				writer.setSelection( paragraph, 10 );
 			} );
 
-			it( 'should not convert partial mentions', () => {
-				editor.setData( '<p><span class="mention" data-mention="John">@Jo</span></p>' );
-
-				expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>@Jo</paragraph>' );
-
-				expect( editor.getData() ).to.equal( '<p>@Jo</p>' );
+			model.enqueueChange( () => {
+				model.modifySelection( doc.selection, { direction: 'backward', unit: 'codepoint' } );
+				model.deleteContent( doc.selection );
 			} );
+
+			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span>bar</p>' );
 		} );
+	} );
 
-		describe( 'typing integration', () => {
+	describe( 'integration', () => {
+		describe( 'undo', () => {
 			beforeEach( () => {
-				return createTestEditor()
-					.then( newEditor => {
+				return VirtualTestEditor
+					.create( {
+						plugins: [ Paragraph, MentionEditing, UndoEditing ]
+					} ).then( newEditor => {
 						editor = newEditor;
 						model = editor.model;
 						doc = model.document;
 					} );
 			} );
 
-			it( 'should remove mention attribute from a selection if selection is on right side of a mention', () => {
-				editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span>bar</p>' );
-
-				model.change( writer => {
-					const paragraph = doc.getRoot().getChild( 0 );
-
-					writer.setSelection( paragraph, 9 );
-				} );
-
-				expect( Array.from( doc.selection.getAttributes() ) ).to.deep.equal( [] );
-			} );
+			// Failing test. See ckeditor/ckeditor5#1645.
+			it( 'should restore removed mention on adding a text inside mention', () => {
+				editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 
-			it( 'should allow to type after a mention', () => {
-				editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span>bar</p>' );
+				expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 
 				model.change( writer => {
 					const paragraph = doc.getRoot().getChild( 0 );
 
-					writer.setSelection( paragraph, 9 );
-
-					writer.insertText( ' ', paragraph, 9 );
-				} );
-
-				expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
-			} );
-		} );
-
-		describe( 'postFixer', () => {
-			it( 'should..', () => {} );
-		} );
+					writer.setSelection( paragraph, 6 );
 
-		describe( 'integration', () => {
-			describe( 'undo', () => {
-				beforeEach( () => {
-					return VirtualTestEditor
-						.create( {
-							plugins: [ Paragraph, MentionEditing, UndoEditing ]
-						} ).then( newEditor => {
-							editor = newEditor;
-							model = editor.model;
-							doc = model.document;
-						} );
+					writer.insertText( 'a', doc.selection.getAttributes(), writer.createPositionAt( paragraph, 6 ) );
 				} );
 
-				// Failing test. See ckeditor/ckeditor5#1645.
-				it( 'should restore removed mention on adding a text inside mention', () => {
-					editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+				expect( editor.getData() ).to.equal( '<p>foo @Jaohn bar</p>' );
+				expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo @Jaohn bar</p>' );
 
-					expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+				editor.execute( 'undo' );
 
-					model.change( writer => {
-						const paragraph = doc.getRoot().getChild( 0 );
+				expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+				expect( getViewData( editor.editing.view ) )
+					.to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			} );
 
-						writer.setSelection( paragraph, 6 );
+			// Failing test. See ckeditor/ckeditor5#1645.
+			it( 'should restore removed mention on removing a text inside mention', () => {
+				editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 
-						writer.insertText( 'a', doc.selection.getAttributes(), writer.createPositionAt( paragraph, 6 ) );
-					} );
+				expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 
-					expect( editor.getData() ).to.equal( '<p>foo @Jaohn bar</p>' );
-					expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo @Jaohn bar</p>' );
+				model.change( writer => {
+					const paragraph = doc.getRoot().getChild( 0 );
 
-					editor.execute( 'undo' );
+					writer.setSelection( paragraph, 7 );
 
-					expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
-					expect( getViewData( editor.editing.view ) )
-						.to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+					model.modifySelection( doc.selection, { direction: 'backward', unit: 'codepoint' } );
+					model.deleteContent( doc.selection );
 				} );
 
-				// Failing test. See ckeditor/ckeditor5#1645.
-				it( 'should restore removed mention on removing a text inside mention', () => {
-					editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+				expect( editor.getData() ).to.equal( '<p>foo @Jhn bar</p>' );
+				expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo @Jhn bar</p>' );
 
-					expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+				editor.execute( 'undo' );
 
-					model.change( writer => {
-						const paragraph = doc.getRoot().getChild( 0 );
-
-						writer.setSelection( paragraph, 7 );
-
-						model.modifySelection( doc.selection, { direction: 'backward', unit: 'codepoint' } );
-						model.deleteContent( doc.selection );
-					} );
-
-					expect( editor.getData() ).to.equal( '<p>foo @Jhn bar</p>' );
-					expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( '<p>foo @Jhn bar</p>' );
-
-					editor.execute( 'undo' );
-
-					expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
-					expect( getViewData( editor.editing.view ) )
-						.to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
-				} );
+				expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+				expect( getViewData( editor.editing.view ) )
+					.to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
 			} );
 		} );
 	} );