Преглед изворни кода

Merge branch 'master' into t/182

Oskar Wróbel пре 8 година
родитељ
комит
d77a97ac2e

+ 19 - 15
packages/ckeditor5-ui/src/contextualballoon.js → packages/ckeditor5-ui/src/panel/balloon/contextualballoon.js

@@ -4,11 +4,11 @@
  */
  */
 
 
 /**
 /**
- * @module ui/contextualballoon
+ * @module ui/panel/balloon/contextualballoon
  */
  */
 
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import BalloonPanelView from './panel/balloon/balloonpanelview';
+import BalloonPanelView from './balloonpanelview';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
 
 /**
 /**
@@ -49,15 +49,15 @@ export default class ContextualBalloon extends Plugin {
 
 
 		/**
 		/**
 		 * Stack of the views injected into the balloon. Last one in the stack is displayed
 		 * Stack of the views injected into the balloon. Last one in the stack is displayed
-		 * as a content of {@link module:ui/contextualballoon~ContextualBalloon#view}.
+		 * as a content of {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon#view}.
 		 *
 		 *
 		 * @private
 		 * @private
 		 * @member {Map} #_stack
 		 * @member {Map} #_stack
 		 */
 		 */
 		this._stack = new Map();
 		this._stack = new Map();
 
 
-		// Add balloon panel view to editor `body` collection.
-		this.editor.ui.view.body.add( this.view );
+		// Add balloon panel view to editor `body` collection and wait until view will be ready.
+		return this.editor.ui.view.body.add( this.view );
 	}
 	}
 
 
 	/**
 	/**
@@ -89,6 +89,7 @@ export default class ContextualBalloon extends Plugin {
 	 * @param {module:ui/view~View} [data.view] Content of the balloon.
 	 * @param {module:ui/view~View} [data.view] Content of the balloon.
 	 * @param {module:utils/dom/position~Options} [data.position] Positioning options.
 	 * @param {module:utils/dom/position~Options} [data.position] Positioning options.
 	 * @param {String} [data.balloonClassName] Additional css class for {@link #view} added when given view is visible.
 	 * @param {String} [data.balloonClassName] Additional css class for {@link #view} added when given view is visible.
+	 * @returns {Promise} A Promise resolved when the child {@link module:ui/view~View#init} is done.
 	 */
 	 */
 	add( data ) {
 	add( data ) {
 		if ( this.hasView( data.view ) ) {
 		if ( this.hasView( data.view ) ) {
@@ -109,7 +110,7 @@ export default class ContextualBalloon extends Plugin {
 		// Add new view to the stack.
 		// Add new view to the stack.
 		this._stack.set( data.view, data );
 		this._stack.set( data.view, data );
 		// And display it.
 		// And display it.
-		this._show( data );
+		return this._show( data );
 	}
 	}
 
 
 	/**
 	/**
@@ -118,6 +119,7 @@ export default class ContextualBalloon extends Plugin {
 	 * When there is no view in the stack then balloon will hide.
 	 * When there is no view in the stack then balloon will hide.
 	 *
 	 *
 	 * @param {module:ui/view~View} view A view to be removed from the balloon.
 	 * @param {module:ui/view~View} view A view to be removed from the balloon.
+	 * @returns {Promise} A Promise resolved when the preceding view is ready.
 	 */
 	 */
 	remove( view ) {
 	remove( view ) {
 		if ( !this.hasView( view ) ) {
 		if ( !this.hasView( view ) ) {
@@ -129,6 +131,9 @@ export default class ContextualBalloon extends Plugin {
 			throw new CKEditorError( 'contextualballoon-remove-view-not-exist: Cannot remove configuration of not existing view.' );
 			throw new CKEditorError( 'contextualballoon-remove-view-not-exist: Cannot remove configuration of not existing view.' );
 		}
 		}
 
 
+		// A Promise resolved when the preceding view is ready.
+		let promise = Promise.resolve();
+
 		// When visible view is being removed.
 		// When visible view is being removed.
 		if ( this.visibleView === view ) {
 		if ( this.visibleView === view ) {
 			// We need to remove it from the view content.
 			// We need to remove it from the view content.
@@ -143,7 +148,7 @@ export default class ContextualBalloon extends Plugin {
 			// If it is some other view.
 			// If it is some other view.
 			if ( last ) {
 			if ( last ) {
 				// Just show it.
 				// Just show it.
-				this._show( last );
+				promise = this._show( last );
 			} else {
 			} else {
 				// Hide the balloon panel.
 				// Hide the balloon panel.
 				this.view.hide();
 				this.view.hide();
@@ -152,6 +157,8 @@ export default class ContextualBalloon extends Plugin {
 			// Just remove given view from the stack.
 			// Just remove given view from the stack.
 			this._stack.delete( view );
 			this._stack.delete( view );
 		}
 		}
+
+		return promise;
 	}
 	}
 
 
 	/**
 	/**
@@ -175,18 +182,14 @@ export default class ContextualBalloon extends Plugin {
 	 * @private
 	 * @private
 	 * @param {Object} data Configuration.
 	 * @param {Object} data Configuration.
 	 * @param {module:ui/view~View} [data.view] View to show in the balloon.
 	 * @param {module:ui/view~View} [data.view] View to show in the balloon.
-	 * @param {String} [data.balloonClassName=''] View to show in the balloon.
+	 * @param {String} [data.balloonClassName=''] Additional class name which will added to the {#_balloon} view.
 	 */
 	 */
 	_show( { view, balloonClassName = '' } ) {
 	_show( { view, balloonClassName = '' } ) {
-		this.view.content.add( view );
 		this.view.className = balloonClassName;
 		this.view.className = balloonClassName;
 
 
-		// When view is not rendered we need to wait for it. See: https://github.com/ckeditor/ckeditor5-ui/issues/187.
-		if ( !view.ready ) {
-			view.once( 'change:ready', () => this.view.attachTo( this._getBalloonPosition() ) );
-		} else {
-			this.view.attachTo( this._getBalloonPosition() );
-		}
+		return this.view.content.add( view ).then( () => {
+			this.view.pin( this._getBalloonPosition() );
+		} );
 	}
 	}
 
 
 	/**
 	/**
@@ -206,6 +209,7 @@ export default class ContextualBalloon extends Plugin {
 	destroy() {
 	destroy() {
 		this.editor.ui.view.body.remove( this.view );
 		this.editor.ui.view.body.remove( this.view );
 		this.view.destroy();
 		this.view.destroy();
+		this._stack.clear();
 		super.destroy();
 		super.destroy();
 	}
 	}
 }
 }

+ 3 - 3
packages/ckeditor5-ui/src/toolbar/contextual/contextualtoolbar.js

@@ -8,7 +8,7 @@
  */
  */
 
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import ContextualBalloon from '../../contextualballoon';
+import ContextualBalloon from '../../panel/balloon/contextualballoon';
 import ToolbarView from '../toolbarview';
 import ToolbarView from '../toolbarview';
 import BalloonPanelView from '../../panel/balloon/balloonpanelview.js';
 import BalloonPanelView from '../../panel/balloon/balloonpanelview.js';
 import debounce from '@ckeditor/ckeditor5-utils/src/lib/lodash/debounce';
 import debounce from '@ckeditor/ckeditor5-utils/src/lib/lodash/debounce';
@@ -18,7 +18,7 @@ const defaultPositions = BalloonPanelView.defaultPositions;
 /**
 /**
  * The contextual toolbar.
  * The contextual toolbar.
  *
  *
- * It uses the {@link module:ui/contextualballoon~ContextualBalloon contextual balloon plugin}.
+ * It uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.
  *
  *
  * @extends module:core/plugin~Plugin
  * @extends module:core/plugin~Plugin
  */
  */
@@ -52,7 +52,7 @@ export default class ContextualToolbar extends Plugin {
 		 * The contextual balloon plugin instance.
 		 * The contextual balloon plugin instance.
 		 *
 		 *
 		 * @private
 		 * @private
-		 * @member {module:ui/contextualballoon~ContextualBalloon}
+		 * @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
 		 */
 		 */
 		this._balloon = this.editor.plugins.get( ContextualBalloon );
 		this._balloon = this.editor.plugins.get( ContextualBalloon );
 
 

+ 1 - 1
packages/ckeditor5-ui/tests/manual/contextualballoon/contextualballoon.js

@@ -16,7 +16,7 @@ import EssentialsPresets from '@ckeditor/ckeditor5-presets/src/essentials';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 
-import ContextualBalloon from '../../../src/contextualballoon';
+import ContextualBalloon from '../../../src/panel/balloon/contextualballoon';
 import ToolbarView from '../../../src/toolbar/toolbarview';
 import ToolbarView from '../../../src/toolbar/toolbarview';
 import ButtonView from '../../../src/button/buttonview';
 import ButtonView from '../../../src/button/buttonview';
 import Template from '../../../src/template';
 import Template from '../../../src/template';

+ 106 - 180
packages/ckeditor5-ui/tests/contextualballoon.js → packages/ckeditor5-ui/tests/panel/balloon/contextualballoon.js

@@ -4,14 +4,13 @@
  */
  */
 
 
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
-import ContextualBalloon from '../src/contextualballoon';
-import BalloonPanelView from '../src/panel/balloon/balloonpanelview';
-import View from '../src/view';
-import Template from '../src/template';
+import ContextualBalloon from '../../../src/panel/balloon/contextualballoon';
+import BalloonPanelView from '../../../src/panel/balloon/balloonpanelview';
+import View from '../../../src/view';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 
-/* global document */
+/* global document, setTimeout */
 
 
 describe( 'ContextualBalloon', () => {
 describe( 'ContextualBalloon', () => {
 	let editor, editorElement, balloon, viewA, viewB;
 	let editor, editorElement, balloon, viewA, viewB;
@@ -27,11 +26,21 @@ describe( 'ContextualBalloon', () => {
 			editor = newEditor;
 			editor = newEditor;
 			balloon = editor.plugins.get( ContextualBalloon );
 			balloon = editor.plugins.get( ContextualBalloon );
 
 
-			viewA = new ViewA();
-			viewB = new ViewB();
-
-			// We don't need to test attachTo method of BalloonPanel it's enough to check if was called with proper data.
+			// We don't need to execute BalloonPanel pin and attachTo methods it's enough to check if was called with proper data.
 			sinon.stub( balloon.view, 'attachTo', () => {} );
 			sinon.stub( balloon.view, 'attachTo', () => {} );
+			sinon.stub( balloon.view, 'pin', () => {} );
+
+			viewA = new View();
+			viewB = new View();
+
+			// Add viewA to the pane and init viewB.
+			return Promise.all( [
+				balloon.add( {
+					view: viewA,
+					position: { target: 'fake' }
+				} ),
+				viewB.init(),
+			] );
 		} );
 		} );
 	} );
 	} );
 
 
@@ -62,20 +71,10 @@ describe( 'ContextualBalloon', () => {
 
 
 	describe( 'hasView()', () => {
 	describe( 'hasView()', () => {
 		it( 'should return true when given view is in stack', () => {
 		it( 'should return true when given view is in stack', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			expect( balloon.hasView( viewA ) ).to.true;
 			expect( balloon.hasView( viewA ) ).to.true;
 		} );
 		} );
 
 
 		it( 'should return true when given view is in stack but is not visible', () => {
 		it( 'should return true when given view is in stack but is not visible', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			balloon.add( {
 			balloon.add( {
 				view: viewB,
 				view: viewB,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
@@ -86,55 +85,46 @@ describe( 'ContextualBalloon', () => {
 		} );
 		} );
 
 
 		it( 'should return false when given view is not in stack', () => {
 		it( 'should return false when given view is not in stack', () => {
-			expect( balloon.hasView( viewA ) ).to.false;
+			expect( balloon.hasView( viewB ) ).to.false;
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'add()', () => {
 	describe( 'add()', () => {
-		it( 'should add view to the stack and display in balloon', () => {
-			balloon.add( {
-				view: viewA,
+		it( 'should return promise resolved when view is ready', ( done ) => {
+			const clock = sinon.useFakeTimers();
+
+			const view = {
+				init: () => {
+					return new Promise( ( resolve ) => {
+						setTimeout( () => {
+							resolve();
+						}, 10 );
+					} );
+				},
+				destroy: () => {}
+			};
+
+			const result = balloon.add( {
+				view: view,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
 			} );
 			} );
 
 
-			viewA.ready = true;
+			expect( result ).to.instanceof( Promise );
 
 
-			expect( balloon.view.content.length ).to.equal( 1 );
-			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewA );
-			expect( balloon.view.attachTo.calledOnce ).to.true;
-			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( { target: 'fake' } );
-		} );
-
-		it( 'should wait for view init', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
-			sinon.assert.notCalled( balloon.view.attachTo );
-
-			viewA.ready = true;
+			result.then( done );
 
 
-			sinon.assert.calledOnce( balloon.view.attachTo );
+			clock.tick( 11 );
+			clock.restore();
 		} );
 		} );
 
 
-		it( 'should not wait when view is ready', () => {
-			viewA.ready = true;
-
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
-			sinon.assert.calledOnce( balloon.view.attachTo );
+		it( 'should add view to the stack and display in balloon attached using given position options', () => {
+			expect( balloon.view.content.length ).to.equal( 1 );
+			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewA );
+			expect( balloon.view.pin.calledOnce ).to.true;
+			expect( balloon.view.pin.firstCall.args[ 0 ] ).to.deep.equal( { target: 'fake' } );
 		} );
 		} );
 
 
 		it( 'should throw an error when try to add the same view more than once', () => {
 		it( 'should throw an error when try to add the same view more than once', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			expect( () => {
 			expect( () => {
 				balloon.add( {
 				balloon.add( {
 					view: viewA,
 					view: viewA,
@@ -144,11 +134,6 @@ describe( 'ContextualBalloon', () => {
 		} );
 		} );
 
 
 		it( 'should add multiple views to he stack and display last one', () => {
 		it( 'should add multiple views to he stack and display last one', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			balloon.add( {
 			balloon.add( {
 				view: viewB,
 				view: viewB,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
@@ -158,37 +143,29 @@ describe( 'ContextualBalloon', () => {
 			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewB );
 			expect( balloon.view.content.get( 0 ) ).to.deep.equal( viewB );
 		} );
 		} );
 
 
-		it( 'should add multiple views to the stack and keep balloon in the same position', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake', foo: 'bar' }
-			} );
-
-			viewA.ready = true;
-
-			balloon.add( {
+		it( 'should keep balloon at the same position after adding next view', () => {
+			return balloon.add( {
 				view: viewB,
 				view: viewB,
-				position: { target: 'fake', bar: 'biz' }
-			} );
+				position: { target: 'other' }
+			} )
+			.then( () => {
+				expect( balloon.view.pin.calledTwice ).to.true;
 
 
-			viewB.ready = true;
-
-			expect( balloon.view.attachTo.calledTwice ).to.true;
-
-			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( {
-				target: 'fake',
-				foo: 'bar'
-			} );
+				expect( balloon.view.pin.firstCall.args[ 0 ] ).to.deep.equal( {
+					target: 'fake'
+				} );
 
 
-			expect( balloon.view.attachTo.secondCall.args[ 0 ] ).to.deep.equal( {
-				target: 'fake',
-				foo: 'bar'
+				expect( balloon.view.pin.secondCall.args[ 0 ] ).to.deep.equal( {
+					target: 'fake'
+				} );
 			} );
 			} );
 		} );
 		} );
 
 
 		it( 'should set additional css class of visible view to BalloonPanelView', () => {
 		it( 'should set additional css class of visible view to BalloonPanelView', () => {
+			const view = new View();
+
 			balloon.add( {
 			balloon.add( {
-				view: viewA,
+				view: view,
 				position: { target: 'fake' },
 				position: { target: 'fake' },
 				balloonClassName: 'foo'
 				balloonClassName: 'foo'
 			} );
 			} );
@@ -207,20 +184,10 @@ describe( 'ContextualBalloon', () => {
 
 
 	describe( 'visibleView', () => {
 	describe( 'visibleView', () => {
 		it( 'should return data of currently visible view', () => {
 		it( 'should return data of currently visible view', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			expect( balloon.visibleView ).to.equal( viewA );
 			expect( balloon.visibleView ).to.equal( viewA );
 		} );
 		} );
 
 
 		it( 'should return data of currently visible view when there is more than one in the stack', () => {
 		it( 'should return data of currently visible view when there is more than one in the stack', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			balloon.add( {
 			balloon.add( {
 				view: viewB,
 				view: viewB,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
@@ -230,28 +197,26 @@ describe( 'ContextualBalloon', () => {
 		} );
 		} );
 
 
 		it( 'should return `null` when the stack is empty', () => {
 		it( 'should return `null` when the stack is empty', () => {
+			balloon.remove( viewA );
 			expect( balloon.visibleView ).to.null;
 			expect( balloon.visibleView ).to.null;
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'remove()', () => {
 	describe( 'remove()', () => {
+		it( 'should return promise', () => {
+			expect( balloon.remove( viewA ) ).to.instanceof( Promise );
+		} );
+
 		it( 'should remove given view and hide balloon when there is no other view to display', () => {
 		it( 'should remove given view and hide balloon when there is no other view to display', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
+			balloon.view.isVisible = true;
 
 
 			balloon.remove( viewA );
 			balloon.remove( viewA );
 
 
 			expect( balloon.visibleView ).to.null;
 			expect( balloon.visibleView ).to.null;
+			expect( balloon.view.isVisible ).to.false;
 		} );
 		} );
 
 
-		it( 'should remove given view and set previous in the stack as visible when removed view was visible', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
+		it( 'should remove given view and set preceding in the stack as visible when removed view was visible', () => {
 			balloon.add( {
 			balloon.add( {
 				view: viewB,
 				view: viewB,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
@@ -262,9 +227,22 @@ describe( 'ContextualBalloon', () => {
 			expect( balloon.visibleView ).to.equal( viewA );
 			expect( balloon.visibleView ).to.equal( viewA );
 		} );
 		} );
 
 
-		it( 'should remove given view from the stack when view is not visible', () => {
+		it( 'should wait for init of preceding view when was is not ready', ( done ) => {
+			const clock = sinon.useFakeTimers();
+
+			const view = {
+				init: () => {
+					return new Promise( ( resolve ) => {
+						setTimeout( () => {
+							resolve();
+						}, 10 );
+					} );
+				},
+				destroy: () => {}
+			};
+
 			balloon.add( {
 			balloon.add( {
-				view: viewA,
+				view: view,
 				position: { target: 'fake' }
 				position: { target: 'fake' }
 			} );
 			} );
 
 
@@ -273,6 +251,18 @@ describe( 'ContextualBalloon', () => {
 				position: { target: 'fake' }
 				position: { target: 'fake' }
 			} );
 			} );
 
 
+			balloon.remove( viewB ).then( done );
+
+			clock.tick( 11 );
+			clock.restore();
+		} );
+
+		it( 'should remove given view from the stack when view is not visible', () => {
+			balloon.add( {
+				view: viewB,
+				position: { target: 'fake' }
+			} );
+
 			balloon.remove( viewA );
 			balloon.remove( viewA );
 
 
 			expect( balloon.visibleView ).to.equal( viewB );
 			expect( balloon.visibleView ).to.equal( viewB );
@@ -280,13 +270,15 @@ describe( 'ContextualBalloon', () => {
 
 
 		it( 'should throw an error when there is no given view in the stack', () => {
 		it( 'should throw an error when there is no given view in the stack', () => {
 			expect( () => {
 			expect( () => {
-				balloon.remove( viewA );
+				balloon.remove( viewB );
 			} ).to.throw( CKEditorError, /^contextualballoon-remove-view-not-exist/ );
 			} ).to.throw( CKEditorError, /^contextualballoon-remove-view-not-exist/ );
 		} );
 		} );
 
 
 		it( 'should set additional css class of visible view to BalloonPanelView', () => {
 		it( 'should set additional css class of visible view to BalloonPanelView', () => {
+			const view = new View();
+
 			balloon.add( {
 			balloon.add( {
-				view: viewA,
+				view: view,
 				position: { target: 'fake' },
 				position: { target: 'fake' },
 				balloonClassName: 'foo'
 				balloonClassName: 'foo'
 			} );
 			} );
@@ -304,10 +296,12 @@ describe( 'ContextualBalloon', () => {
 	} );
 	} );
 
 
 	describe( 'updatePosition()', () => {
 	describe( 'updatePosition()', () => {
-		it( 'should attach balloon to the target using the same position options as currently set', () => {
+		it( 'should attach balloon to the target using position option from the first view in the stack', () => {
 			balloon.add( {
 			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
+				view: viewB,
+				position: {
+					target: 'other'
+				}
 			} );
 			} );
 
 
 			balloon.view.attachTo.reset();
 			balloon.view.attachTo.reset();
@@ -319,11 +313,6 @@ describe( 'ContextualBalloon', () => {
 		} );
 		} );
 
 
 		it( 'should attach balloon to the target using new position options', () => {
 		it( 'should attach balloon to the target using new position options', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
 			balloon.view.attachTo.reset();
 			balloon.view.attachTo.reset();
 
 
 			balloon.updatePosition( { target: 'new' } );
 			balloon.updatePosition( { target: 'new' } );
@@ -337,82 +326,19 @@ describe( 'ContextualBalloon', () => {
 			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( { target: 'new' } );
 			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( { target: 'new' } );
 		} );
 		} );
 
 
-		it( 'should attach balloon to the target using the same position options as currently set when there is more than one view', () => {
-			balloon.add( {
-				view: viewA,
-				position: {
-					target: 'fake',
-					foo: 'bar'
-				}
-			} );
-
-			balloon.add( {
-				view: viewB,
-				position: {
-					target: 'fake',
-					bar: 'biz'
-				}
-			} );
-
-			balloon.view.attachTo.reset();
-
-			balloon.updatePosition();
-
-			expect( balloon.view.attachTo.calledOnce );
-			expect( balloon.view.attachTo.firstCall.args[ 0 ] ).to.deep.equal( {
-				target: 'fake',
-				foo: 'bar'
-			} );
-		} );
-
-		it( 'should remove given view from the stack when view is not visible', () => {
-			balloon.add( {
-				view: viewA,
-				position: { target: 'fake' }
-			} );
-
-			balloon.add( {
-				view: viewB,
-				position: { target: 'fake' }
-			} );
-
-			balloon.remove( viewA );
-
-			expect( balloon.visibleView ).to.equal( viewB );
-		} );
-
 		it( 'should throw an error when there is no given view in the stack', () => {
 		it( 'should throw an error when there is no given view in the stack', () => {
 			expect( () => {
 			expect( () => {
-				balloon.remove( viewA );
+				balloon.remove( viewB );
 			} ).to.throw( CKEditorError, /^contextualballoon-remove-view-not-exist/ );
 			} ).to.throw( CKEditorError, /^contextualballoon-remove-view-not-exist/ );
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'destroy()', () => {
 	describe( 'destroy()', () => {
-		it( 'should balloon panel remove view from editor body collection', () => {
+		it( 'should remove balloon panel view from editor body collection and clear stack', () => {
 			balloon.destroy();
 			balloon.destroy();
 
 
 			expect( editor.ui.view.body.getIndex( balloon.view ) ).to.equal( -1 );
 			expect( editor.ui.view.body.getIndex( balloon.view ) ).to.equal( -1 );
+			expect( balloon.visibleView ).to.null;
 		} );
 		} );
 	} );
 	} );
 } );
 } );
-
-class ViewA extends View {
-	constructor( locale ) {
-		super( locale );
-
-		this.template = new Template( {
-			tag: 'div'
-		} );
-	}
-}
-
-class ViewB extends View {
-	constructor( locale ) {
-		super( locale );
-
-		this.template = new Template( {
-			tag: 'div'
-		} );
-	}
-}

+ 1 - 1
packages/ckeditor5-ui/tests/toolbar/contextual/contextualtoolbar.js

@@ -4,7 +4,7 @@
 
 
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import ContextualToolbar from '../../../src/toolbar/contextual/contextualtoolbar';
 import ContextualToolbar from '../../../src/toolbar/contextual/contextualtoolbar';
-import ContextualBalloon from '../../../src/contextualballoon';
+import ContextualBalloon from '../../../src/panel/balloon/contextualballoon';
 import ToolbarView from '../../../src/toolbar/toolbarview';
 import ToolbarView from '../../../src/toolbar/toolbarview';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';