Browse Source

Merge branch 'master' into t/38

Piotrek Koszuliński 7 years ago
parent
commit
859aad9802

+ 6 - 0
packages/ckeditor5-media-embed/lang/contexts.json

@@ -0,0 +1,6 @@
+{
+	"Media URL": "Label for the URL input in the Media Embed URL editing balloon.",
+	"The URL must not be empty.": "An error message that informs about an empty value in the URL input.",
+	"This media URL is not supported.": "An error message that informs about unsupported media URL.",
+	"Insert media": "Toolbar button tooltip for the Media Embed feature."
+}

+ 1 - 0
packages/ckeditor5-media-embed/package.json

@@ -20,6 +20,7 @@
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@ckeditor/ckeditor5-basic-styles": "^10.0.2",
     "@ckeditor/ckeditor5-basic-styles": "^10.0.2",
+    "@ckeditor/ckeditor5-editor-balloon": "^11.0.0",
     "@ckeditor/ckeditor5-editor-classic": "^11.0.0",
     "@ckeditor/ckeditor5-editor-classic": "^11.0.0",
     "@ckeditor/ckeditor5-link": "^10.0.3",
     "@ckeditor/ckeditor5-link": "^10.0.3",
     "@ckeditor/ckeditor5-list": "^11.0.1",
     "@ckeditor/ckeditor5-list": "^11.0.1",

+ 6 - 4
packages/ckeditor5-media-embed/src/mediaembed.js

@@ -16,12 +16,14 @@ import Widget from '@ckeditor/ckeditor5-widget/src/widget';
 /**
 /**
  * The media embed plugin.
  * The media embed plugin.
  *
  *
- * It loads the {@link module:media-embed/mediaembedediting~MediaEmbedEditing media embed editing feature},
- * {@link module:media-embed/mediaembedui~MediaEmbedUI media embed UI feature} and
- * {@link module:media-embed/automediaembed~AutoMediaEmbed auto-media embed feature}.
- *
  * For a detailed overview, check the {@glink features/media-embed Media Embed feature documentation}.
  * For a detailed overview, check the {@glink features/media-embed Media Embed feature documentation}.
  *
  *
+ * This is a "glue" plugin which loads the following plugins:
+ *
+ * * {@link module:media-embed/mediaembedediting~MediaEmbedEditing media embed editing feature},
+ * * {@link module:media-embed/mediaembedui~MediaEmbedUI media embed UI feature} and
+ * * {@link module:media-embed/automediaembed~AutoMediaEmbed auto-media embed feature}.
+ *
  * @extends module:core/plugin~Plugin
  * @extends module:core/plugin~Plugin
  */
  */
 export default class MediaEmbed extends Plugin {
 export default class MediaEmbed extends Plugin {

+ 4 - 5
packages/ckeditor5-media-embed/src/mediaembedcommand.js

@@ -8,7 +8,7 @@
  */
  */
 
 
 import Command from '@ckeditor/ckeditor5-core/src/command';
 import Command from '@ckeditor/ckeditor5-core/src/command';
-import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
+import { findOptimalInsertionPosition } from '@ckeditor/ckeditor5-widget/src/utils';
 import { getSelectedMediaElement } from './utils';
 import { getSelectedMediaElement } from './utils';
 
 
 /**
 /**
@@ -62,14 +62,13 @@ export default class MediaEmbedCommand extends Command {
 				writer.setAttribute( 'url', url, selectedMedia );
 				writer.setAttribute( 'url', url, selectedMedia );
 			} );
 			} );
 		} else {
 		} else {
-			const firstPosition = selection.getFirstPosition();
-			const isRoot = firstPosition.parent === firstPosition.root;
-			const insertPosition = isRoot ? ModelPosition.createAt( firstPosition ) : ModelPosition.createAfter( firstPosition.parent );
+			const insertPosition = findOptimalInsertionPosition( selection );
 
 
 			model.change( writer => {
 			model.change( writer => {
 				const mediaElement = writer.createElement( 'media', { url } );
 				const mediaElement = writer.createElement( 'media', { url } );
 
 
-				writer.insert( mediaElement, insertPosition );
+				model.insertContent( mediaElement, insertPosition );
+
 				writer.setSelection( mediaElement, 'on' );
 				writer.setSelection( mediaElement, 'on' );
 			} );
 			} );
 		}
 		}

+ 57 - 0
packages/ckeditor5-media-embed/src/mediaembedtoolbar.js

@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2016 - 2018, CKSource - Frederico Knabben. All rights reserved.
+ */
+
+/**
+ * @module media-embed/mediaembedtoolbar
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import WidgetToolbarRepository from '@ckeditor/ckeditor5-widget/src/widgettoolbarrepository';
+import { isMediaWidgetSelected } from './utils';
+
+/**
+ * The media embed toolbar plugin. It creates a toolbar for media embed that shows up when the media element is selected.
+ *
+ * Instances of toolbar components (e.g. buttons) are created based on the
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#toolbar `media.toolbar` configuration option}.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class MediaEmbedToolbar extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ WidgetToolbarRepository ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'MediaEmbedToolbar';
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	afterInit() {
+		const editor = this.editor;
+		const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
+
+		widgetToolbarRepository.register( 'mediaEmbed', {
+			items: editor.config.get( 'mediaEmbed.toolbar' ) || [],
+			visibleWhen: isMediaWidgetSelected,
+		} );
+	}
+}
+
+/**
+ * Items to be placed in the media embed toolbar.
+ * This option requires adding {@link module:media-embed/mediaembedtoolbar~MediaEmbedToolbar} to the plugin list.
+ *
+ * Read more about configuring toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
+ *
+ * @member {Array.<String>} module:media-embed/mediaembed~MediaEmbedConfig#toolbar
+ */

+ 11 - 1
packages/ckeditor5-media-embed/src/utils.js

@@ -8,7 +8,7 @@
  */
  */
 
 
 import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
 import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
-import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
+import { isWidget, toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
 
 
 const mediaSymbol = Symbol( 'isMedia' );
 const mediaSymbol = Symbol( 'isMedia' );
 
 
@@ -28,6 +28,16 @@ export function toMediaWidget( viewElement, writer, label ) {
 	return toWidget( viewElement, writer, { label } );
 	return toWidget( viewElement, writer, { label } );
 }
 }
 
 
+export function isMediaWidgetSelected( viewSelection ) {
+	const viewElement = viewSelection.getSelectedElement();
+
+	return !!( viewElement && isMediaWidget( viewElement ) );
+}
+
+export function isMediaWidget( viewElement ) {
+	return !!viewElement.getCustomProperty( mediaSymbol ) && isWidget( viewElement );
+}
+
 /**
 /**
  * Creates a view element representing the media. Either "semantic" one for the data pipeline:
  * Creates a view element representing the media. Either "semantic" one for the data pipeline:
  *
  *

+ 14 - 14
packages/ckeditor5-media-embed/tests/automediaembed.js

@@ -76,7 +76,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -104,7 +104,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -115,7 +115,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="https://youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="https://youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -126,7 +126,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="http://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="http://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -137,7 +137,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="http://youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="http://youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -148,7 +148,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -159,7 +159,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -170,7 +170,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="https://twitter.com/ckeditor/status/1035181110140063749"></media>]'
+				'[<media url="https://twitter.com/ckeditor/status/1035181110140063749"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -181,7 +181,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -192,7 +192,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -203,7 +203,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph></paragraph>[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
 			);
 			);
 		} );
 		} );
 
 
@@ -214,7 +214,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 			clock.tick( 100 );
 			clock.tick( 100 );
 
 
 			expect( getData( editor.model ) ).to.equal(
 			expect( getData( editor.model ) ).to.equal(
-				'<paragraph>For</paragraph>[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+				'[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]<paragraph>For</paragraph>'
 			);
 			);
 		} );
 		} );
 
 
@@ -393,7 +393,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 
 
 			setTimeout( () => {
 			setTimeout( () => {
 				expect( getData( editor.model ) ).to.equal(
 				expect( getData( editor.model ) ).to.equal(
-					'<paragraph>ABCDEFGHIJ</paragraph>[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+					'[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]<paragraph>ABCDEFGHIJ</paragraph>'
 				);
 				);
 
 
 				done();
 				done();
@@ -415,7 +415,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 
 
 			setTimeout( () => {
 			setTimeout( () => {
 				expect( getData( editor.model ) ).to.equal(
 				expect( getData( editor.model ) ).to.equal(
-					'<paragraph>ABCDEFGHIJ</paragraph>[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]'
+					'[<media url="https://www.youtube.com/watch?v=H08tGjXNHO4"></media>]<paragraph>ABCDEFGHIJ</paragraph>'
 				);
 				);
 
 
 				done();
 				done();

+ 4 - 2
packages/ckeditor5-media-embed/tests/manual/mediaembed.js

@@ -8,15 +8,17 @@
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
 import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
 import MediaEmbed from '../../src/mediaembed';
 import MediaEmbed from '../../src/mediaembed';
+import MediaEmbedToolbar from '../../src/mediaembedtoolbar';
 
 
 ClassicEditor
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
 	.create( document.querySelector( '#editor' ), {
-		plugins: [ ArticlePluginSet, MediaEmbed ],
+		plugins: [ ArticlePluginSet, MediaEmbed, MediaEmbedToolbar ],
 		toolbar: [
 		toolbar: [
 			'heading', '|', 'mediaEmbed', '|', 'bold', 'italic', 'bulletedList', 'numberedList', 'blockQuote', 'link', 'undo', 'redo'
 			'heading', '|', 'mediaEmbed', '|', 'bold', 'italic', 'bulletedList', 'numberedList', 'blockQuote', 'link', 'undo', 'redo'
 		],
 		],
 		mediaEmbed: {
 		mediaEmbed: {
-			previewsInData: true
+			previewsInData: true,
+			toolbar: [ 'blockQuote' ]
 		}
 		}
 	} )
 	} )
 	.then( editor => {
 	.then( editor => {

+ 7 - 0
packages/ckeditor5-media-embed/tests/manual/mediaembed.md

@@ -45,3 +45,10 @@
 1. Hover the URL in the content (the tooltip should show up),
 1. Hover the URL in the content (the tooltip should show up),
 1. Click the URL,
 1. Click the URL,
 1. A new browser tab should open with the media URL.
 1. A new browser tab should open with the media URL.
+
+### Media embed toolbar
+
+1. Click the media,
+1. The block quote button should be visible,
+1. Click the block quote button,
+1. The block quote should be applied to the media.

+ 250 - 0
packages/ckeditor5-media-embed/tests/mediaembedtoolbar.js

@@ -0,0 +1,250 @@
+/**
+ * Copyright (c) 2016 - 2017, CKSource - Frederico Knabben. All rights reserved.
+ */
+
+/* global document */
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import BalloonEditor from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor';
+import MediaEmbed from '../src/mediaembed';
+import MediaEmbedToolbar from '../src/mediaembedtoolbar';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import View from '@ckeditor/ckeditor5-ui/src/view';
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+
+describe( 'MediaEmbedToolbar', () => {
+	let editor, element, widgetToolbarRepository, balloon, toolbar, model;
+
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		element = document.createElement( 'div' );
+		document.body.appendChild( element );
+
+		return ClassicTestEditor.create( element, {
+			plugins: [ Paragraph, MediaEmbed, MediaEmbedToolbar, FakeButton ],
+			mediaEmbed: {
+				toolbar: [ 'fake_button' ]
+			}
+		} ).then( _editor => {
+			editor = _editor;
+			model = editor.model;
+			widgetToolbarRepository = editor.plugins.get( 'WidgetToolbarRepository' );
+			toolbar = widgetToolbarRepository._toolbars.get( 'mediaEmbed' ).view;
+			balloon = editor.plugins.get( 'ContextualBalloon' );
+		} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy()
+			.then( () => element.remove() );
+	} );
+
+	it( 'should be loaded', () => {
+		expect( editor.plugins.get( MediaEmbedToolbar ) ).to.be.instanceOf( MediaEmbedToolbar );
+	} );
+
+	describe( 'toolbar', () => {
+		it( 'should use the config.table.tableWidget to create items', () => {
+			expect( toolbar.items ).to.have.length( 1 );
+			expect( toolbar.items.get( 0 ).label ).to.equal( 'fake button' );
+		} );
+
+		it( 'should set proper CSS classes', () => {
+			const spy = sinon.spy( balloon, 'add' );
+
+			editor.ui.focusTracker.isFocused = true;
+
+			setData( model, '[<media url=""></media>]' );
+
+			sinon.assert.calledWithMatch( spy, {
+				view: toolbar,
+				balloonClassName: 'ck-toolbar-container'
+			} );
+		} );
+	} );
+
+	describe( 'integration with the editor focus', () => {
+		it( 'should show the toolbar when the editor gains focus and the media widget is selected', () => {
+			editor.ui.focusTracker.isFocused = true;
+
+			setData( editor.model, '[<media url=""></media>]' );
+
+			editor.ui.focusTracker.isFocused = false;
+			expect( balloon.visibleView ).to.be.null;
+
+			editor.ui.focusTracker.isFocused = true;
+			expect( balloon.visibleView ).to.equal( toolbar );
+		} );
+
+		it( 'should hide the toolbar when the editor loses focus and the media widget is selected', () => {
+			editor.ui.focusTracker.isFocused = false;
+
+			setData( editor.model, '[<media url=""></media>]' );
+
+			editor.ui.focusTracker.isFocused = true;
+			expect( balloon.visibleView ).to.equal( toolbar );
+
+			editor.ui.focusTracker.isFocused = false;
+			expect( balloon.visibleView ).to.be.null;
+		} );
+	} );
+
+	describe( 'integration with the editor selection', () => {
+		beforeEach( () => {
+			editor.ui.focusTracker.isFocused = true;
+		} );
+
+		it( 'should show the toolbar on ui#update when the media widget is selected', () => {
+			setData( editor.model, '<paragraph>[foo]</paragraph><media url=""></media>' );
+
+			expect( balloon.visibleView ).to.be.null;
+
+			editor.ui.fire( 'update' );
+
+			expect( balloon.visibleView ).to.be.null;
+
+			editor.model.change( writer => {
+				// Select the [<media></media>]
+				writer.setSelection( editor.model.document.getRoot().getChild( 1 ), 'on' );
+			} );
+
+			expect( balloon.visibleView ).to.equal( toolbar );
+
+			// Make sure successive change does not throw, e.g. attempting
+			// to insert the toolbar twice.
+			editor.ui.fire( 'update' );
+			expect( balloon.visibleView ).to.equal( toolbar );
+		} );
+
+		it( 'should not engage when the toolbar is in the balloon yet invisible', () => {
+			setData( editor.model, '<media url=""></media>' );
+
+			expect( balloon.visibleView ).to.equal( toolbar );
+
+			const lastView = new View();
+			lastView.element = document.createElement( 'div' );
+
+			balloon.add( {
+				view: lastView,
+				position: {
+					target: document.body
+				}
+			} );
+
+			expect( balloon.visibleView ).to.equal( lastView );
+
+			editor.ui.fire( 'update' );
+
+			expect( balloon.visibleView ).to.equal( lastView );
+		} );
+
+		it( 'should hide the toolbar on ui#update if the media is de–selected', () => {
+			setData( model, '<paragraph>foo</paragraph>[<media url=""></media>]' );
+
+			expect( balloon.visibleView ).to.equal( toolbar );
+
+			model.change( writer => {
+				// Select the <paragraph>[...]</paragraph>
+				writer.setSelection( model.document.getRoot().getChild( 0 ), 'in' );
+			} );
+
+			expect( balloon.visibleView ).to.be.null;
+
+			// Make sure successive change does not throw, e.g. attempting
+			// to remove the toolbar twice.
+			editor.ui.fire( 'update' );
+			expect( balloon.visibleView ).to.be.null;
+		} );
+	} );
+} );
+
+describe( 'MediaEmbedToolbar - integration with BalloonEditor', () => {
+	let clock, editor, balloonToolbar, element, widgetToolbarRepository, balloon, toolbar, model;
+
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		element = document.createElement( 'div' );
+		document.body.appendChild( element );
+		clock = testUtils.sinon.useFakeTimers();
+
+		return BalloonEditor.create( element, {
+			plugins: [ Paragraph, MediaEmbed, MediaEmbedToolbar, FakeButton, Bold ],
+			balloonToolbar: [ 'bold' ],
+			media: {
+				toolbar: [ 'fake_button' ]
+			}
+		} ).then( _editor => {
+			editor = _editor;
+			model = editor.model;
+			widgetToolbarRepository = editor.plugins.get( 'WidgetToolbarRepository' );
+			toolbar = widgetToolbarRepository._toolbars.get( 'mediaEmbed' ).view;
+			balloon = editor.plugins.get( 'ContextualBalloon' );
+			balloonToolbar = editor.plugins.get( 'BalloonToolbar' );
+		} );
+	} );
+
+	beforeEach( () => {
+		editor.ui.focusTracker.isFocused = true;
+	} );
+
+	afterEach( () => {
+		return editor.destroy()
+			.then( () => element.remove() );
+	} );
+
+	it( 'balloon toolbar should be hidden when media widget is selected', () => {
+		setData( model, '<paragraph>[abc]</paragraph><media url=""></media>' );
+		editor.editing.view.document.isFocused = true;
+
+		expect( balloon.visibleView ).to.equal( null );
+
+		model.change( writer => {
+			// Select the [<media></media>]
+			writer.setSelection( model.document.getRoot().getChild( 1 ), 'on' );
+		} );
+
+		expect( balloon.visibleView ).to.equal( toolbar );
+
+		clock.tick( 200 );
+
+		expect( balloon.visibleView ).to.equal( toolbar );
+	} );
+
+	it( 'balloon toolbar should be visible when media widget is not selected', () => {
+		setData( model, '<paragraph>abc</paragraph>[<media url=""></media>]' );
+		editor.editing.view.document.isFocused = true;
+
+		expect( balloon.visibleView ).to.equal( toolbar );
+
+		model.change( writer => {
+			// Select the <paragraph>[abc]</paragraph>
+			writer.setSelection( model.document.getRoot().getChild( 0 ), 'in' );
+		} );
+
+		clock.tick( 200 );
+
+		expect( balloon.visibleView ).to.equal( balloonToolbar.toolbarView );
+	} );
+} );
+
+// Plugin that adds fake_button to editor's component factory.
+class FakeButton extends Plugin {
+	init() {
+		this.editor.ui.componentFactory.add( 'fake_button', locale => {
+			const view = new ButtonView( locale );
+
+			view.set( {
+				label: 'fake button'
+			} );
+
+			return view;
+		} );
+	}
+}