浏览代码

Merge pull request #13 from ckeditor/t/7

Fix: Improve mentions panel positioning. Closes #5. Closes #7.
Piotrek Koszuliński 6 年之前
父节点
当前提交
61842f37de

+ 82 - 24
packages/ckeditor5-mention/src/mentionui.js

@@ -21,7 +21,7 @@ import MentionsView from './ui/mentionsview';
 import DomWrapperView from './ui/domwrapperview';
 import MentionListItemView from './ui/mentionlistitemview';
 
-const VERTICAL_SPACING = 5;
+const VERTICAL_SPACING = 3;
 
 /**
  * The mention UI feature.
@@ -91,7 +91,7 @@ export default class MentionUI extends Plugin {
 				}
 
 				if ( data.keyCode == keyCodes.esc ) {
-					this._hidePanel();
+					this._hidePanelAndRemoveMarker();
 				}
 			}
 		}, { priority: 'highest' } ); // Required to override enter.
@@ -101,7 +101,7 @@ export default class MentionUI extends Plugin {
 			emitter: this.panelView,
 			contextElements: [ this.panelView.element ],
 			activator: () => this.panelView.isVisible,
-			callback: () => this._hidePanel()
+			callback: () => this._hidePanelAndRemoveMarker()
 		} );
 
 		const feeds = this.editor.config.get( 'mention.feeds' );
@@ -205,7 +205,7 @@ export default class MentionUI extends Plugin {
 			const start = end.getShiftedBy( -matchedTextLength );
 			const range = model.createRange( start, end );
 
-			this._hidePanel();
+			this._hidePanelAndRemoveMarker();
 
 			editor.execute( 'mention', {
 				mention: item,
@@ -273,6 +273,26 @@ export default class MentionUI extends Plugin {
 
 			const { feedText, marker } = matched;
 
+			const matchedTextLength = marker.length + feedText.length;
+
+			// create marker range
+			const start = selection.focus.getShiftedBy( -matchedTextLength );
+			const end = selection.focus.getShiftedBy( -feedText.length );
+
+			const markerRange = editor.model.createRange( start, end );
+
+			let mentionMarker;
+
+			if ( editor.model.markers.has( 'mention' ) ) {
+				mentionMarker = editor.model.markers.get( 'mention' );
+			} else {
+				mentionMarker = editor.model.change( writer => writer.addMarker( 'mention', {
+					range: markerRange,
+					usingOperation: false,
+					affectsData: false
+				} ) );
+			}
+
 			this._getFeed( marker, feedText )
 				.then( feed => {
 					this._items.clear();
@@ -284,15 +304,15 @@ export default class MentionUI extends Plugin {
 					}
 
 					if ( this._items.length ) {
-						this._showPanel();
+						this._showPanel( mentionMarker );
 					} else {
-						this._hidePanel();
+						this._hidePanelAndRemoveMarker();
 					}
 				} );
 		} );
 
 		watcher.on( 'unmatched', () => {
-			this._hidePanel();
+			this._hidePanelAndRemoveMarker();
 		} );
 
 		return watcher;
@@ -316,19 +336,25 @@ export default class MentionUI extends Plugin {
 	 *
 	 * @private
 	 */
-	_showPanel() {
-		this.panelView.pin( this._getBalloonPanelPositionData() );
+	_showPanel( markerMarker ) {
+		this.panelView.pin( this._getBalloonPanelPositionData( markerMarker, this.panelView.position ) );
 		this.panelView.show();
 		this._mentionsView.selectFirst();
 	}
 
 	/**
-	 * Hides the {@link #panelView}.
+	 * Hides the {@link #panelView} and remove 'mention' marker from markers collection.
 	 *
 	 * @private
 	 */
-	_hidePanel() {
+	_hidePanelAndRemoveMarker() {
+		if ( this.editor.model.markers.has( 'mention' ) ) {
+			this.editor.model.change( writer => writer.removeMarker( 'mention' ) );
+		}
+
 		this.panelView.unpin();
+		// Make last matched position on panel view undefined so the #_getBalloonPanelPositionData() will return all positions on next call.
+		this.panelView.position = undefined;
 		this.panelView.hide();
 	}
 
@@ -364,22 +390,39 @@ export default class MentionUI extends Plugin {
 	}
 
 	/**
+	 * Creates position options object used to position the balloon panel.
+	 *
+	 * @param {module:engine/model/markercollection~Marker} mentionMarker
+	 * @param {String|undefined} positionName Name of last matched position name.
 	 * @returns {module:utils/dom/position~Options}
 	 * @private
 	 */
-	_getBalloonPanelPositionData() {
-		const view = this.editor.editing.view;
-		const domConverter = view.domConverter;
-		const viewSelection = view.document.selection;
+	_getBalloonPanelPositionData( mentionMarker, positionName ) {
+		const editing = this.editor.editing;
+		const domConverter = editing.view.domConverter;
+		const mapper = editing.mapper;
 
 		return {
 			target: () => {
-				const range = viewSelection.getLastRange();
-				const rangeRects = Rect.getDomRangeRects( domConverter.viewRangeToDom( range ) );
+				const viewRange = mapper.toViewRange( mentionMarker.getRange() );
+
+				const rangeRects = Rect.getDomRangeRects( domConverter.viewRangeToDom( viewRange ) );
 
 				return rangeRects.pop();
 			},
-			positions: getBalloonPanelPositions()
+			limiter: () => {
+				const view = this.editor.editing.view;
+				const viewDocument = view.document;
+				const editableElement = viewDocument.selection.editableElement;
+
+				if ( editableElement ) {
+					return view.domConverter.mapViewToDom( editableElement.root );
+				}
+
+				return null;
+			},
+			positions: getBalloonPanelPositions( positionName ),
+			fitInViewport: true
 		};
 	}
 }
@@ -387,10 +430,10 @@ export default class MentionUI extends Plugin {
 // Returns balloon positions data callbacks.
 //
 // @returns {Array.<module:utils/dom/position~Position>}
-function getBalloonPanelPositions() {
-	return [
+function getBalloonPanelPositions( positionName ) {
+	const positions = {
 		// Positions panel to the south of caret rect.
-		targetRect => {
+		'caret_se': targetRect => {
 			return {
 				top: targetRect.bottom + VERTICAL_SPACING,
 				left: targetRect.right,
@@ -399,7 +442,7 @@ function getBalloonPanelPositions() {
 		},
 
 		// Positions panel to the north of caret rect.
-		( targetRect, balloonRect ) => {
+		'caret_ne': ( targetRect, balloonRect ) => {
 			return {
 				top: targetRect.top - balloonRect.height - VERTICAL_SPACING,
 				left: targetRect.right,
@@ -408,7 +451,7 @@ function getBalloonPanelPositions() {
 		},
 
 		// Positions panel to the south of caret rect.
-		( targetRect, balloonRect ) => {
+		'caret_sw': ( targetRect, balloonRect ) => {
 			return {
 				top: targetRect.bottom + VERTICAL_SPACING,
 				left: targetRect.right - balloonRect.width,
@@ -417,13 +460,28 @@ function getBalloonPanelPositions() {
 		},
 
 		// Positions panel to the north of caret rect.
-		( targetRect, balloonRect ) => {
+		'caret_nw': ( targetRect, balloonRect ) => {
 			return {
 				top: targetRect.top - balloonRect.height - VERTICAL_SPACING,
 				left: targetRect.right - balloonRect.width,
 				name: 'caret_nw'
 			};
 		}
+	};
+
+	// Return only last position if it was matched to prevent panel from jumping after first match.
+	if ( positions.hasOwnProperty( positionName ) ) {
+		return [
+			positions[ positionName ]
+		];
+	}
+
+	// As default return all positions callbacks.
+	return [
+		positions.caret_se,
+		positions.caret_ne,
+		positions.caret_sw,
+		positions.caret_nw
 	];
 }
 

+ 45 - 17
packages/ckeditor5-mention/src/textwatcher.js

@@ -48,6 +48,15 @@ export default class TextWatcher {
 	_startListening() {
 		const editor = this.editor;
 
+		editor.model.document.selection.on( 'change', ( evt, { directChange } ) => {
+			// The indirect changes (ie on typing) are handled in document's change event.
+			if ( !directChange ) {
+				return;
+			}
+
+			this._evaluateTextBeforeSelection();
+		} );
+
 		editor.model.document.on( 'change', ( evt, batch ) => {
 			if ( batch.type == 'transparent' ) {
 				return;
@@ -58,29 +67,49 @@ export default class TextWatcher {
 
 			// Typing is represented by only a single change.
 			const isTypingChange = changes.length == 1 && entry.name == '$text' && entry.length == 1;
-			// Selection is represented by empty changes.
-			const isSelectionChange = changes.length == 0;
 
-			if ( !isTypingChange && !isSelectionChange ) {
+			if ( !isTypingChange ) {
 				return;
 			}
 
-			const text = this._getText();
-
-			const textHasMatch = this.testCallback( text );
+			this._evaluateTextBeforeSelection();
+		} );
+	}
 
-			if ( !textHasMatch && this.hasMatch ) {
-				this.fire( 'unmatched' );
-			}
+	/**
+	 * Checks the editor content for matched text.
+	 *
+	 * @fires matched
+	 * @fires unmatched
+	 *
+	 * @private
+	 */
+	_evaluateTextBeforeSelection() {
+		const text = this._getText();
+
+		const textHasMatch = this.testCallback( text );
+
+		if ( !textHasMatch && this.hasMatch ) {
+			/**
+			 * Fired whenever text doesn't match anymore. Fired only when text matcher was matched.
+			 *
+			 * @event unmatched
+			 */
+			this.fire( 'unmatched' );
+		}
 
-			this.hasMatch = textHasMatch;
+		this.hasMatch = textHasMatch;
 
-			if ( textHasMatch ) {
-				const matched = this.textMatcher( text );
+		if ( textHasMatch ) {
+			const matched = this.textMatcher( text );
 
-				this.fire( 'matched', { text, matched } );
-			}
-		} );
+			/**
+			 * Fired whenever text matcher was matched.
+			 *
+			 * @event matched
+			 */
+			this.fire( 'matched', { text, matched } );
+		}
 	}
 
 	/**
@@ -105,8 +134,7 @@ export default class TextWatcher {
 }
 
 // Returns whole text from parent element by adding all data from text nodes together.
-// @todo copied from autoformat...
-
+//
 // @private
 // @param {module:engine/model/element~Element} element
 // @returns {String}

+ 130 - 9
packages/ckeditor5-mention/tests/mentionui.js

@@ -108,9 +108,13 @@ describe( 'MentionUI', () => {
 		} );
 
 		it( 'should properly calculate position data', () => {
+			const editableElement = editingView.document.selection.editableElement;
+
 			setData( model, '<paragraph>foo []</paragraph>' );
 			stubSelectionRects( [ caretRect ] );
 
+			expect( editor.model.markers.has( 'mention' ) ).to.be.false;
+
 			model.change( writer => {
 				writer.insertText( '@', doc.selection.getFirstPosition() );
 			} );
@@ -118,11 +122,31 @@ describe( 'MentionUI', () => {
 			return waitForDebounce()
 				.then( () => {
 					const pinArgument = pinSpy.firstCall.args[ 0 ];
-					const { target, positions } = pinArgument;
+					const { target, positions, limiter, fitInViewport } = pinArgument;
 
-					expect( target() ).to.deep.equal( caretRect );
+					expect( fitInViewport ).to.be.true;
 					expect( positions ).to.have.length( 4 );
 
+					// Mention UI should set limiter to the editable area.
+					expect( limiter() ).to.equal( editingView.domConverter.mapViewToDom( editableElement ) );
+
+					expect( editor.model.markers.has( 'mention' ) ).to.be.true;
+					const mentionMarker = editor.model.markers.get( 'mention' );
+					const focus = doc.selection.focus;
+					const expectedRange = editor.model.createRange( focus.getShiftedBy( -1 ), focus );
+
+					// It should create a model marker for matcher marker character ('@').
+					expect( expectedRange.isEqual( mentionMarker.getRange() ) ).to.be.true;
+
+					const toViewRangeSpy = sinon.spy( editor.editing.mapper, 'toViewRange' );
+
+					expect( target() ).to.deep.equal( caretRect );
+
+					sinon.assert.calledOnce( toViewRangeSpy );
+					const range = toViewRangeSpy.firstCall.args[ 0 ];
+
+					expect( mentionMarker.getRange().isEqual( range ), 'Should position to mention marker.' );
+
 					const caretSouthEast = positions[ 0 ];
 					const caretNorthEast = positions[ 1 ];
 					const caretSouthWest = positions[ 2 ];
@@ -131,30 +155,30 @@ describe( 'MentionUI', () => {
 					expect( caretSouthEast( caretRect, balloonRect ) ).to.deep.equal( {
 						left: 501,
 						name: 'caret_se',
-						top: 123
+						top: 121
 					} );
 
 					expect( caretNorthEast( caretRect, balloonRect ) ).to.deep.equal( {
 						left: 501,
 						name: 'caret_ne',
-						top: -55
+						top: -53
 					} );
 
 					expect( caretSouthWest( caretRect, balloonRect ) ).to.deep.equal( {
 						left: 301,
 						name: 'caret_sw',
-						top: 123
+						top: 121
 					} );
 
 					expect( caretNorthWest( caretRect, balloonRect ) ).to.deep.equal( {
 						left: 301,
 						name: 'caret_nw',
-						top: -55
+						top: -53
 					} );
 				} );
 		} );
 
-		it( 'should re-calculate position on typing', () => {
+		it( 'should re-calculate position on typing and stay on selected position', () => {
 			setData( model, '<paragraph>foo []</paragraph>' );
 			stubSelectionRects( [ caretRect ] );
 
@@ -162,10 +186,19 @@ describe( 'MentionUI', () => {
 				writer.insertText( '@', doc.selection.getFirstPosition() );
 			} );
 
+			let positionAfterFirstShow;
+
 			return waitForDebounce()
 				.then( () => {
 					sinon.assert.calledOnce( pinSpy );
 
+					const pinArgument = pinSpy.firstCall.args[ 0 ];
+					const { positions } = pinArgument;
+
+					expect( positions ).to.have.length( 4 );
+
+					positionAfterFirstShow = panelView.position;
+
 					model.change( writer => {
 						writer.insertText( 't', doc.selection.getFirstPosition() );
 					} );
@@ -173,6 +206,34 @@ describe( 'MentionUI', () => {
 				.then( waitForDebounce )
 				.then( () => {
 					sinon.assert.calledTwice( pinSpy );
+
+					const pinArgument = pinSpy.secondCall.args[ 0 ];
+					const { positions } = pinArgument;
+
+					expect( positions, 'should reuse first matched position' ).to.have.length( 1 );
+					expect( positions[ 0 ].name ).to.equal( positionAfterFirstShow );
+				} );
+		} );
+
+		it( 'does not fail if selection has no #editableElement', () => {
+			setData( model, '<paragraph>foo []</paragraph>' );
+			stubSelectionRects( [ caretRect ] );
+
+			expect( editor.model.markers.has( 'mention' ) ).to.be.false;
+
+			model.change( writer => {
+				writer.insertText( '@', doc.selection.getFirstPosition() );
+			} );
+
+			return waitForDebounce()
+				.then( () => {
+					const pinArgument = pinSpy.firstCall.args[ 0 ];
+					const { limiter } = pinArgument;
+
+					sinon.stub( editingView.document.selection, 'editableElement' ).value( null );
+
+					// Should not break;
+					expect( limiter() ).to.be.null;
 				} );
 		} );
 	} );
@@ -195,6 +256,7 @@ describe( 'MentionUI', () => {
 				.then( waitForDebounce )
 				.then( () => {
 					expect( panelView.isVisible ).to.be.false;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 				} )
 				.then( waitForDebounce )
 				.then( () => {
@@ -205,6 +267,7 @@ describe( 'MentionUI', () => {
 				.then( waitForDebounce )
 				.then( () => {
 					expect( panelView.isVisible ).to.be.true;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 					expect( listView.items ).to.have.length( 1 );
 
 					model.change( writer => {
@@ -214,6 +277,7 @@ describe( 'MentionUI', () => {
 				.then( waitForDebounce )
 				.then( () => {
 					expect( panelView.isVisible ).to.be.true;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 					expect( listView.items ).to.have.length( 1 );
 				} );
 		} );
@@ -295,6 +359,8 @@ describe( 'MentionUI', () => {
 			it( 'should show panel for matched marker', () => {
 				setData( model, '<paragraph>foo []</paragraph>' );
 
+				expect( editor.model.markers.has( 'mention' ) ).to.be.false;
+
 				model.change( writer => {
 					writer.insertText( '@', doc.selection.getFirstPosition() );
 				} );
@@ -302,6 +368,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.true;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 						expect( listView.items ).to.have.length( 5 );
 					} );
 			} );
@@ -316,6 +383,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.true;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 						expect( listView.items ).to.have.length( 5 );
 					} );
 			} );
@@ -330,6 +398,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.false;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 					} );
 			} );
 
@@ -346,6 +415,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.false;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 					} );
 			} );
 
@@ -362,6 +432,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.false;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 					} );
 			} );
 
@@ -375,6 +446,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.true;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 
 						model.change( () => {
 							model.modifySelection( doc.selection, { direction: 'backward', unit: 'character' } );
@@ -383,6 +455,7 @@ describe( 'MentionUI', () => {
 					.then( waitForDebounce )
 					.then( () => {
 						expect( panelView.isVisible ).to.be.false;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 					} );
 			} );
 
@@ -420,6 +493,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.true;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 						expect( listView.items ).to.have.length( 1 );
 					} );
 			} );
@@ -456,6 +530,7 @@ describe( 'MentionUI', () => {
 					.then( waitForDebounce )
 					.then( () => {
 						expect( panelView.isVisible ).to.be.false;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 						expect( listView.items ).to.have.length( 0 );
 					} );
 			} );
@@ -512,6 +587,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.true;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 						expect( listView.items ).to.have.length( 4 );
 					} );
 			} );
@@ -530,6 +606,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.true;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 						expect( listView.items ).to.have.length( 1 );
 					} );
 			} );
@@ -551,6 +628,7 @@ describe( 'MentionUI', () => {
 					.then( waitForDebounce )
 					.then( () => {
 						expect( panelView.isVisible ).to.be.false;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 						expect( listView.items ).to.have.length( 0 );
 					} );
 			} );
@@ -591,6 +669,7 @@ describe( 'MentionUI', () => {
 				.then( waitForDebounce )
 				.then( () => {
 					expect( panelView.isVisible ).to.be.true;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 
 					fireKeyDownEvent( {
 						keyCode: keyCodes.esc,
@@ -599,6 +678,7 @@ describe( 'MentionUI', () => {
 					} );
 
 					expect( panelView.isVisible ).to.be.false;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 				} );
 		} );
 
@@ -614,14 +694,41 @@ describe( 'MentionUI', () => {
 				.then( waitForDebounce )
 				.then( () => {
 					expect( panelView.isVisible ).to.be.true;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 
 					document.body.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) );
 
 					expect( panelView.isVisible ).to.be.false;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.false;
+				} );
+		} );
+
+		it( 'should hide the panel on selection change', () => {
+			return createClassicTestEditor( staticConfig )
+				.then( () => {
+					setData( model, '<paragraph>foo []</paragraph>' );
+
+					model.change( writer => {
+						writer.insertText( '@', doc.selection.getFirstPosition() );
+					} );
+				} )
+				.then( waitForDebounce )
+				.then( () => {
+					expect( panelView.isVisible ).to.be.true;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.true;
+
+					model.change( writer => {
+						// Place position at the beginning of a paragraph.
+						writer.setSelection( doc.getRoot().getChild( 0 ), 0 );
+					} );
+
+					expect( panelView.isVisible ).to.be.false;
+					expect( panelView.position ).to.be.undefined;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 				} );
 		} );
 
-		it( 'should hide the panel when click outside', () => {
+		it( 'should hide the panel on selection change triggered by mouse click', () => {
 			return createClassicTestEditor( staticConfig )
 				.then( () => {
 					setData( model, '<paragraph>foo []</paragraph>' );
@@ -633,13 +740,22 @@ describe( 'MentionUI', () => {
 				.then( waitForDebounce )
 				.then( () => {
 					expect( panelView.isVisible ).to.be.true;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 
+					// This happens when user clicks outside the panel view and selection is changed.
+					// Two panel closing mechanisms are run:
+					// - clickOutsideHandler
+					// - unmatched text in text watcher
+					// which may fail when trying to remove mention marker twice.
+					document.body.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) );
 					model.change( writer => {
-						// Place position at the begging of a paragraph.
+						// Place position at the beginning of a paragraph.
 						writer.setSelection( doc.getRoot().getChild( 0 ), 0 );
 					} );
 
 					expect( panelView.isVisible ).to.be.false;
+					expect( panelView.position ).to.be.undefined;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 				} );
 		} );
 
@@ -768,6 +884,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.true;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 						expect( listView.items ).to.have.length( 5 );
 					} );
 			} );
@@ -942,6 +1059,7 @@ describe( 'MentionUI', () => {
 				return waitForDebounce()
 					.then( () => {
 						expect( panelView.isVisible ).to.be.true;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.true;
 
 						fireKeyDownEvent( {
 							keyCode: keyCodes.esc,
@@ -950,6 +1068,7 @@ describe( 'MentionUI', () => {
 						} );
 
 						expect( panelView.isVisible ).to.be.false;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 
 						fireKeyDownEvent( {
 							keyCode,
@@ -960,6 +1079,7 @@ describe( 'MentionUI', () => {
 						sinon.assert.notCalled( spy );
 
 						expect( panelView.isVisible ).to.be.false;
+						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 					} );
 			} );
 		}
@@ -1007,6 +1127,7 @@ describe( 'MentionUI', () => {
 					listView.items.get( 0 ).children.get( 0 ).fire( 'execute' );
 
 					expect( panelView.isVisible ).to.be.false;
+					expect( editor.model.markers.has( 'mention' ) ).to.be.false;
 				} );
 		} );