Răsfoiți Sursa

Merge branch 'master' into t/ckeditor5/1247

Piotrek Koszuliński 7 ani în urmă
părinte
comite
3ea77ef97c

+ 1 - 1
packages/ckeditor5-table/docs/_snippets/features/table.js

@@ -17,7 +17,7 @@ ClassicEditor
 			viewportTopOffset: 60
 		},
 		table: {
-			toolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
+			contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
 		}
 	} )
 	.then( editor => {

+ 5 - 3
packages/ckeditor5-table/docs/features/table.md

@@ -31,10 +31,10 @@ import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';
 
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
-		plugins: [ Table, TableToolbar, ... ],
+		plugins: [ Table, TableToolbar, Bold, ... ],
 		toolbar: [ 'insertTable', ... ]
 		table: {
-			toolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
+			contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
 		}
 	} )
 	.then( ... )
@@ -68,7 +68,9 @@ And the following commands:
 * The `'splitTableCellVertically'` command implemented by {@link module:table/commands/splitcellcommand~SplitCellCommand}.
 * The `'splitTableCellHorizontally'` command implemented by {@link module:table/commands/splitcellcommand~SplitCellCommand}.
 
-The {@link module:table/tabletoolbar~TableToolbar} plugin introduces the balloon toolbar for tables. The toolbar shows up when a table cell is selected and is anchored to the table. It is possible to {@link module:table/table~TableConfig#toolbar configure} its content. Normally, it contains the table-related tools such as `'tableColumn'`, `'tableRow'`, and `'mergeTableCells'` dropdowns.
+The {@link module:table/tabletoolbar~TableToolbar} plugin introduces two balloon toolbars for tables.
+* The content toolbar shows up when table cell is selected and is anchored to the table. It is possible to {@link module:table/table~TableConfig#contentToolbar configure} its content. Normally, it contains the table-related tools such as `'tableColumn'`, `'tableRow'`, and `'mergeTableCells'` dropdowns.
+* The table toolbar shows up when the whole table is selected, for instance using the widget handler. It is possible to {@link module:table/table~TableConfig#tableToolbar configure} its content.
 
 ## Contribute
 

+ 68 - 14
packages/ckeditor5-table/src/tabletoolbar.js

@@ -8,17 +8,20 @@
  */
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import { isTableContentSelected } from './utils';
+import { isTableContentSelected, isTableWidgetSelected } from './utils';
 import WidgetToolbarRepository from '@ckeditor/ckeditor5-widget/src/widgettoolbarrepository';
 
 /**
- * The table toolbar class. It creates a table toolbar that shows up when the table content is selected.
+ * The table toolbar class. It creates toolbars for the table feature and its content (for now only for a table cell content).
  *
- * Instances of toolbar components (e.g. buttons) are created using the editor's
- * {@link module:ui/componentfactory~ComponentFactory component factory}
- * based on the {@link module:table/table~TableConfig#toolbar `table.toolbar` configuration option}.
+ * Table toolbar shows up when a table widget is selected. Its components (e.g. buttons) are created based on the
+ * {@link module:table/table~TableConfig#toolbar `table.tableToolbar` configuration option}.
  *
- * The toolbar uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon} plugin.
+ * Table content toolbar shows up when the selection is inside the content of a table. It creates its component based on the
+ * {@link module:table/table~TableConfig#contentToolbar `table.contentToolbar` configuration option}.
+ *
+ * Note that the old {@link module:table/table~TableConfig#toolbar `table.toolbar` configuration option} is deprecated
+ * and will be removed in the next major release.
  *
  * @extends module:core/plugin~Plugin
  */
@@ -44,16 +47,49 @@ export default class TableToolbar extends Plugin {
 		const editor = this.editor;
 		const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
 
-		widgetToolbarRepository.register( 'table', {
-			items: editor.config.get( 'table.toolbar' ) || [],
-			visibleWhen: isTableContentSelected,
-		} );
+		const tableContentToolbarItems = editor.config.get( 'table.contentToolbar' );
+		const deprecatedTableContentToolbarItems = editor.config.get( 'table.toolbar' );
+
+		const tableToolbarItems = editor.config.get( 'table.tableToolbar' );
+
+		if ( deprecatedTableContentToolbarItems ) {
+			// eslint-disable-next-line
+			console.warn(
+				'`config.table.toolbar` is deprecated and will be removed in the next major release.' +
+				' Use `config.table.contentToolbar` instead.'
+			);
+		}
+
+		if ( tableContentToolbarItems || deprecatedTableContentToolbarItems ) {
+			widgetToolbarRepository.register( 'tableContent', {
+				items: tableContentToolbarItems || deprecatedTableContentToolbarItems,
+				visibleWhen: isTableContentSelected,
+			} );
+		}
+
+		if ( tableToolbarItems ) {
+			widgetToolbarRepository.register( 'table', {
+				items: tableToolbarItems,
+				visibleWhen: isTableWidgetSelected,
+			} );
+		}
 	}
 }
 
 /**
- * Items to be placed in the table toolbar.
- * This option is used by the {@link module:table/tabletoolbar~TableToolbar} feature.
+ * Items to be placed in the table content toolbar.
+ *
+ * **Note:** This is a deprecated configuration option! Use {@link module:table/table~TableConfig#contentToolbar} instead.
+ *
+ * Read more about configuring toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
+ *
+ * @deprecated
+ * @member {Array.<String>} module:table/table~TableConfig#toolbar
+ */
+
+/**
+ * Items to be placed in the table content toolbar.
+ * The {@link module:table/tabletoolbar~TableToolbar} plugin is required to make this toolbar working.
  *
  * Assuming that you use the {@link module:table/tableui~TableUI} feature, the following toolbar items will be available
  * in {@link module:ui/componentfactory~ComponentFactory}:
@@ -65,7 +101,7 @@ export default class TableToolbar extends Plugin {
  * You can thus configure the toolbar like this:
  *
  *		const tableConfig = {
- *			toolbar: [ 'tableRow', 'tableColumn', 'mergeTableCells' ]
+ *			contentToolbar: [ 'tableRow', 'tableColumn', 'mergeTableCells' ]
  *		};
  *
  * Of course, the same buttons can also be used in the
@@ -73,5 +109,23 @@ export default class TableToolbar extends Plugin {
  *
  * Read more about configuring toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
  *
- * @member {Array.<String>} module:table/table~TableConfig#toolbar
+ * @member {Array.<String>} module:table/table~TableConfig#contentToolbar
+ */
+
+/**
+ * Items to be placed in the table toolbar.
+ * The {@link module:table/tabletoolbar~TableToolbar} plugin is required to make this toolbar working.
+ *
+ * You can thus configure the toolbar like this:
+ *
+ *		const tableConfig = {
+ *			tableToolbar: [ 'blockQuote' ]
+ *		};
+ *
+ * Of course, the same buttons can also be used in the
+ * {@link module:core/editor/editorconfig~EditorConfig#toolbar main editor toolbar}.
+ *
+ * Read more about configuring toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
+ *
+ * @member {Array.<String>} module:table/table~TableConfig#tableToolbar
  */

+ 0 - 52
packages/ckeditor5-table/src/ui/utils.js

@@ -1,52 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/**
- * @module table/ui/utils
- */
-
-import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
-import { findAncestor } from '../commands/utils';
-
-/**
- * A helper utility that positions the
- * {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} instance
- * with respect to the table in the editor content, if one is selected.
- *
- * @param {module:core/editor/editor~Editor} editor The editor instance.
- */
-export function repositionContextualBalloon( editor ) {
-	const balloon = editor.plugins.get( 'ContextualBalloon' );
-
-	balloon.updatePosition( getBalloonPositionData( editor ) );
-}
-
-/**
- * Returns the positioning options that control the geometry of the
- * {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} with respect
- * to the selected element in the editor content.
- *
- * @param {module:core/editor/editor~Editor} editor The editor instance.
- * @returns {module:utils/dom/position~Options}
- */
-export function getBalloonPositionData( editor ) {
-	const editingView = editor.editing.view;
-	const defaultPositions = BalloonPanelView.defaultPositions;
-	const viewSelection = editingView.document.selection;
-
-	const parentTable = findAncestor( 'table', viewSelection.getFirstPosition() );
-
-	return {
-		target: editingView.domConverter.viewToDom( parentTable ),
-		positions: [
-			defaultPositions.northArrowSouth,
-			defaultPositions.northArrowSouthWest,
-			defaultPositions.northArrowSouthEast,
-			defaultPositions.southArrowNorth,
-			defaultPositions.southArrowNorthWest,
-			defaultPositions.southArrowNorthEast
-		]
-	};
-}

+ 1 - 1
packages/ckeditor5-table/tests/integration.js

@@ -12,7 +12,7 @@ import Table from '../src/table';
 import TableToolbar from '../src/tabletoolbar';
 import View from '@ckeditor/ckeditor5-ui/src/view';
 
-describe( 'TableToolbar integration', () => {
+describe( 'TableContentToolbar integration', () => {
 	describe( 'with the BalloonToolbar', () => {
 		let balloon, balloonToolbar, newEditor, editorElement;
 

+ 2 - 1
packages/ckeditor5-table/tests/manual/table.js

@@ -17,7 +17,8 @@ ClassicEditor
 			'heading', '|', 'insertTable', '|', 'bold', 'italic', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo'
 		],
 		table: {
-			toolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
+			contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ],
+			tableToolbar: [ 'bold', 'italic' ]
 		}
 	} )
 	.then( editor => {

+ 6 - 4
packages/ckeditor5-table/tests/manual/table.md

@@ -2,16 +2,16 @@
 
 1. The data should be loaded with:
   * a complex table with:
-    - one heading row, 
-    - two heading columns, 
+    - one heading row,
+    - two heading columns,
     - merged cells in heading columns section,
   * a table with 2 tbody sections in the DOM - should be rendered as a table with one tbody.
   * a table with no tbody sections in the DOM - should be rendered as a table with one tbody.
   * a table with a thead section between two tbody sections in dom - should be rendered as a table with one thead and on tbody section in proper order: 1, 2, 3.
 
 2. Main toolbar should have insert table dropdown.
-  
-3. While the table widget is selected there should be a toolbar attached to the table with 3 dropdowns:
+
+3. While the table cell is selected there should be a toolbar attached to the table with 3 dropdowns:
   * column dropdown with items:
     - header column,
     - insert column before,
@@ -30,6 +30,8 @@
     - split cell vertically,
     - split cell horizontally,
 
+4. While the table widget is selected there should be `bold` and `italic` buttons
+
 ### Testing
 
 Inserting table:

+ 1 - 1
packages/ckeditor5-table/tests/manual/tableblockcontent.js

@@ -19,7 +19,7 @@ ClassicEditor
 			'alignment', '|', 'undo', 'redo'
 		],
 		table: {
-			toolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
+			contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
 		}
 	} )
 	.then( editor => {

+ 2 - 2
packages/ckeditor5-table/tests/manual/tableblockcontent.md

@@ -13,7 +13,7 @@
 
 ### Testing
 
-1. Use <kbd>Enter</kbd> in cells with single `<pargraph>`. When two `<paragraph>`'s are in one table cell they should be rendered as `<p>`.
+1. Use <kbd>Enter</kbd> in cells with single `<paragraph>`. When two `<paragraph>`'s are in one table cell they should be rendered as `<p>`.
 2. Undo previous step - the `<p>` element should be changed to `<span>` for single paragraph.
 3. Change `<heading>` to paragraph - it should be rendered as `<p>` element if there are other headings or other block content.
-4. Change one `<heading>` to paragraph and remove other headings. The `<paragraph>` should be rendered as `<span>`. 
+4. Change one `<heading>` to paragraph and remove other headings. The `<paragraph>` should be rendered as `<span>`.

+ 383 - 132
packages/ckeditor5-table/tests/tabletoolbar.js

@@ -3,9 +3,9 @@
  * For licensing, see LICENSE.md.
  */
 
-/* global document */
+/* global document, window */
 
-import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ClassicTestEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import TableToolbar from '../src/tabletoolbar';
 import Table from '../src/table';
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
@@ -15,199 +15,450 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Range from '@ckeditor/ckeditor5-engine/src/model/range';
 import View from '@ckeditor/ckeditor5-ui/src/view';
 import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import WidgetToolbarRepository from '@ckeditor/ckeditor5-widget/src/widgettoolbarrepository';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 
 describe( 'TableToolbar', () => {
-	let editor, model, doc, widgetToolbarRepository, toolbar, balloon, editorElement;
+	testUtils.createSinonSandbox();
 
-	beforeEach( () => {
-		editorElement = global.document.createElement( 'div' );
-		global.document.body.appendChild( editorElement );
+	describe( 'contentToolbar', () => {
+		let editor, model, doc, widgetToolbarRepository, toolbar, balloon, editorElement;
 
-		return ClassicEditor
-			.create( editorElement, {
-				plugins: [ Paragraph, Table, TableToolbar, FakeButton ],
-				table: {
-					toolbar: [ 'fake_button' ]
-				}
+		beforeEach( () => {
+			editorElement = global.document.createElement( 'div' );
+			global.document.body.appendChild( editorElement );
+
+			return ClassicTestEditor
+				.create( editorElement, {
+					plugins: [ Paragraph, Table, TableToolbar, FakeButton ],
+					table: {
+						contentToolbar: [ 'fake_button' ]
+					}
+				} )
+				.then( newEditor => {
+					editor = newEditor;
+					model = newEditor.model;
+					doc = model.document;
+					widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
+					toolbar = widgetToolbarRepository._toolbars.get( 'tableContent' ).view;
+					balloon = editor.plugins.get( 'ContextualBalloon' );
+				} );
+		} );
+
+		afterEach( () => {
+			editorElement.remove();
+
+			return editor.destroy();
+		} );
+
+		it( 'should be loaded', () => {
+			expect( editor.plugins.get( TableToolbar ) ).to.be.instanceOf( TableToolbar );
+		} );
+
+		it( 'should not initialize if there is no configuration', () => {
+			const editorElement = global.document.createElement( 'div' );
+			global.document.body.appendChild( editorElement );
+
+			return ClassicTestEditor.create( editorElement, {
+				plugins: [ TableToolbar ]
 			} )
-			.then( newEditor => {
-				editor = newEditor;
-				model = newEditor.model;
-				doc = model.document;
-				widgetToolbarRepository = editor.plugins.get( 'WidgetToolbarRepository' );
-				toolbar = widgetToolbarRepository._toolbars.get( 'table' ).view;
-				balloon = editor.plugins.get( 'ContextualBalloon' );
+				.then( editor => {
+					const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
+					expect( widgetToolbarRepository._toolbars.get( 'tableContent' ) ).to.be.undefined;
+
+					editorElement.remove();
+					return editor.destroy();
+				} );
+		} );
+
+		describe( 'toolbar', () => {
+			it( 'should use the config.table.toolbar to create items', () => {
+				expect( toolbar.items ).to.have.length( 1 );
+				expect( toolbar.items.get( 0 ).label ).to.equal( 'fake button' );
 			} );
-	} );
 
-	afterEach( () => {
-		editorElement.remove();
+			it( 'should set proper CSS classes', () => {
+				const spy = sinon.spy( balloon, 'add' );
 
-		return editor.destroy();
-	} );
+				editor.ui.focusTracker.isFocused = true;
 
-	it( 'should be loaded', () => {
-		expect( editor.plugins.get( TableToolbar ) ).to.be.instanceOf( TableToolbar );
-	} );
+				setData( model, '<table><tableRow><tableCell>[]</tableCell></tableRow></table>' );
 
-	it( 'should not initialize if there is no configuration', () => {
-		const editorElement = global.document.createElement( 'div' );
-		global.document.body.appendChild( editorElement );
+				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 table is selected', () => {
+				editor.ui.focusTracker.isFocused = true;
 
-		return ClassicEditor.create( editorElement, {
-			plugins: [ TableToolbar ]
-		} )
-			.then( editor => {
-				expect( editor.plugins.get( TableToolbar )._toolbar ).to.be.undefined;
+				setData( model, '<table><tableRow><tableCell>[]</tableCell></tableRow></table>' );
 
-				editorElement.remove();
-				return editor.destroy();
+				editor.ui.focusTracker.isFocused = false;
+				expect( balloon.visibleView ).to.be.null;
+
+				editor.ui.focusTracker.isFocused = true;
+				expect( balloon.visibleView ).to.equal( toolbar );
 			} );
-	} );
 
-	describe( 'toolbar', () => {
-		it( 'should use the config.table.toolbar to create items', () => {
-			expect( toolbar.items ).to.have.length( 1 );
-			expect( toolbar.items.get( 0 ).label ).to.equal( 'fake button' );
+			it( 'should hide the toolbar when the editor loses focus and the table is selected', () => {
+				editor.ui.focusTracker.isFocused = false;
+
+				setData( model, '<table><tableRow><tableCell>[]</tableCell></tableRow></table>' );
+
+				editor.ui.focusTracker.isFocused = true;
+				expect( balloon.visibleView ).to.equal( toolbar );
+
+				editor.ui.focusTracker.isFocused = false;
+				expect( balloon.visibleView ).to.be.null;
+			} );
 		} );
 
-		it( 'should set proper CSS classes', () => {
-			const spy = sinon.spy( balloon, 'add' );
+		describe( 'integration with the editor selection (ui#update event)', () => {
+			beforeEach( () => {
+				editor.ui.focusTracker.isFocused = true;
+			} );
+
+			it( 'should not show the toolbar on ui#update when the table is selected', () => {
+				setData( model, '<paragraph>foo</paragraph>[<table><tableRow><tableCell></tableCell></tableRow></table>]' );
+
+				expect( balloon.visibleView ).to.be.null;
+			} );
 
-			editor.ui.focusTracker.isFocused = true;
+			it( 'should show the toolbar on ui#update when the table content is selected', () => {
+				setData(
+					model,
+					'<paragraph>[foo]</paragraph><table><tableRow><tableCell><paragraph>bar</paragraph></tableCell></tableRow></table>'
+				);
 
-			setData( model, '<table><tableRow><tableCell>[]</tableCell></tableRow></table>' );
+				expect( balloon.visibleView ).to.be.null;
 
-			sinon.assert.calledWithMatch( spy, {
-				view: toolbar,
-				balloonClassName: 'ck-toolbar-container'
+				editor.ui.fire( 'update' );
+
+				expect( balloon.visibleView ).to.be.null;
+
+				model.change( writer => {
+					// Select the <tableCell>[bar]</tableCell>
+					writer.setSelection(
+						Range.createOn( doc.getRoot().getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 ) )
+					);
+				} );
+
+				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 );
 			} );
-		} );
-	} );
 
-	describe( 'integration with the editor focus', () => {
-		it( 'should show the toolbar when the editor gains focus and the table is selected', () => {
-			editor.ui.focusTracker.isFocused = true;
+			it( 'should not engage when the toolbar is in the balloon yet invisible', () => {
+				setData( model, '<table><tableRow><tableCell><paragraph>x[y]z</paragraph></tableCell></tableRow></table>' );
 
-			setData( model, '<table><tableRow><tableCell>[]</tableCell></tableRow></table>' );
+				expect( balloon.visibleView ).to.equal( toolbar );
 
-			editor.ui.focusTracker.isFocused = false;
-			expect( balloon.visibleView ).to.be.null;
+				// Put anything on top of the ContextualBalloon stack above the table toolbar.
+				const lastView = new View();
+				lastView.element = document.createElement( 'div' );
 
-			editor.ui.focusTracker.isFocused = true;
-			expect( balloon.visibleView ).to.equal( toolbar );
-		} );
+				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 render if the table is de–selected', () => {
+				setData(
+					model,
+					'<paragraph>foo</paragraph><table><tableRow><tableCell><paragraph>[]</paragraph></tableCell></tableRow></table>'
+				);
 
-		it( 'should hide the toolbar when the editor loses focus and the table is selected', () => {
-			editor.ui.focusTracker.isFocused = false;
+				expect( balloon.visibleView ).to.equal( toolbar );
 
-			setData( model, '<table><tableRow><tableCell>[]</tableCell></tableRow></table>' );
+				model.change( writer => {
+					// Select the <paragraph>[...]</paragraph>
+					writer.setSelection(
+						Range.createIn( doc.getRoot().getChild( 0 ) )
+					);
+				} );
 
-			editor.ui.focusTracker.isFocused = true;
-			expect( balloon.visibleView ).to.equal( toolbar );
+				expect( balloon.visibleView ).to.be.null;
 
-			editor.ui.focusTracker.isFocused = false;
-			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( 'integration with the editor selection (ui#update event)', () => {
-		beforeEach( () => {
-			editor.ui.focusTracker.isFocused = true;
+	describe( 'deprecated toolbar', () => {
+		let editor, editorElement, warnMock;
+
+		it( 'table.toolbar should work as table.contentToolbar', () => {
+			editorElement = global.document.createElement( 'div' );
+			global.document.body.appendChild( editorElement );
+			warnMock = sinon.stub( window.console, 'warn' );
+
+			return ClassicTestEditor
+				.create( editorElement, {
+					plugins: [ Paragraph, Table, TableToolbar, FakeButton ],
+					table: {
+						toolbar: [ 'fake_button' ]
+					}
+				} )
+				.then( newEditor => {
+					editor = newEditor;
+
+					const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
+					const toolbarView = widgetToolbarRepository._toolbars.get( 'tableContent' ).view;
+
+					expect( toolbarView.items ).to.have.length( 1 );
+					expect( toolbarView.items.get( 0 ).label ).to.equal( 'fake button' );
+
+					sinon.assert.calledWith(
+						warnMock,
+						'`config.table.toolbar` is deprecated and will be removed in the next major release.' +
+						' Use `config.table.contentToolbar` instead.'
+					);
+				} );
 		} );
 
-		it( 'should not show the toolbar on ui#update when the table is selected', () => {
-			setData( model, '<paragraph>foo</paragraph>[<table><tableRow><tableCell></tableCell></tableRow></table>]' );
+		it( 'table.contentToolbar should be used if both toolbars options are provided', () => {
+			editorElement = global.document.createElement( 'div' );
+			global.document.body.appendChild( editorElement );
+			warnMock = sinon.stub( window.console, 'warn' );
+
+			return ClassicTestEditor
+				.create( editorElement, {
+					plugins: [ Paragraph, Table, TableToolbar, FakeButton, FooButton ],
+					table: {
+						toolbar: [ 'fake_button' ],
+						contentToolbar: [ 'foo_button' ],
+					}
+				} )
+				.then( newEditor => {
+					editor = newEditor;
+
+					const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
+					const toolbarView = widgetToolbarRepository._toolbars.get( 'tableContent' ).view;
+
+					expect( toolbarView.items ).to.have.length( 1 );
+					expect( toolbarView.items.get( 0 ).label ).to.equal( 'foo button' );
+				} );
+		} );
 
-			expect( balloon.visibleView ).to.be.null;
+		afterEach( () => {
+			editorElement.remove();
+
+			return editor.destroy();
 		} );
+	} );
 
-		it( 'should show the toolbar on ui#update when the table content is selected', () => {
-			setData(
-				model,
-				'<paragraph>[foo]</paragraph><table><tableRow><tableCell><paragraph>bar</paragraph></tableCell></tableRow></table>'
-			);
+	describe( 'tableToolbar', () => {
+		let editor, element, widgetToolbarRepository, balloon, toolbar, model;
 
-			expect( balloon.visibleView ).to.be.null;
+		beforeEach( () => {
+			element = document.createElement( 'div' );
+			document.body.appendChild( element );
 
-			editor.ui.fire( 'update' );
+			return ClassicTestEditor.create( element, {
+				plugins: [ Paragraph, Table, TableToolbar, FakeButton ],
+				table: {
+					tableToolbar: [ 'fake_button' ]
+				}
+			} ).then( _editor => {
+				editor = _editor;
+				widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
+				toolbar = widgetToolbarRepository._toolbars.get( 'table' ).view;
+				balloon = editor.plugins.get( 'ContextualBalloon' );
+				model = editor.model;
+			} );
+		} );
 
-			expect( balloon.visibleView ).to.be.null;
+		afterEach( () => {
+			return editor.destroy()
+				.then( () => element.remove() );
+		} );
 
-			model.change( writer => {
-				// Select the <tableCell>[bar]</tableCell>
-				writer.setSelection(
-					Range.createOn( doc.getRoot().getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 ) )
-				);
+		describe( 'toolbar', () => {
+			it( 'should not initialize if there is no configuration', () => {
+				const editorElement = global.document.createElement( 'div' );
+				global.document.body.appendChild( editorElement );
+
+				return ClassicTestEditor.create( editorElement, {
+					plugins: [ TableToolbar ]
+				} )
+					.then( editor => {
+						const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
+						expect( widgetToolbarRepository._toolbars.get( 'table' ) ).to.be.undefined;
+
+						editorElement.remove();
+						return editor.destroy();
+					} );
 			} );
 
-			expect( balloon.visibleView ).to.equal( 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;
 
-			// 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 );
+				setData( model, '[<table><tableRow><tableCell></tableCell></tableRow></table>]' );
+
+				sinon.assert.calledWithMatch( spy, {
+					view: toolbar,
+					balloonClassName: 'ck-toolbar-container'
+				} );
+			} );
 		} );
 
-		it( 'should not engage when the toolbar is in the balloon yet invisible', () => {
-			setData( model, '<table><tableRow><tableCell><paragraph>x[y]z</paragraph></tableCell></tableRow></table>' );
+		describe( 'integration with the editor focus', () => {
+			it( 'should show the toolbar when the editor gains focus and the table is selected', () => {
+				editor.ui.focusTracker.isFocused = true;
 
-			expect( balloon.visibleView ).to.equal( toolbar );
+				setData( model, '[<table><tableRow><tableCell></tableCell></tableRow></table>]' );
 
-			// Put anything on top of the ContextualBalloon stack above the table toolbar.
-			const lastView = new View();
-			lastView.element = document.createElement( 'div' );
+				editor.ui.focusTracker.isFocused = false;
+				expect( balloon.visibleView ).to.be.null;
 
-			balloon.add( {
-				view: lastView,
-				position: {
-					target: document.body
-				}
+				editor.ui.focusTracker.isFocused = true;
+				expect( balloon.visibleView ).to.equal( toolbar );
 			} );
 
-			expect( balloon.visibleView ).to.equal( lastView );
+			it( 'should hide the toolbar when the editor loses focus and the table is selected', () => {
+				editor.ui.focusTracker.isFocused = false;
 
-			editor.ui.fire( 'update' );
+				setData( model, '[<table><tableRow><tableCell></tableCell></tableRow></table>]' );
 
-			expect( balloon.visibleView ).to.equal( lastView );
+				editor.ui.focusTracker.isFocused = true;
+				expect( balloon.visibleView ).to.equal( toolbar );
+
+				editor.ui.focusTracker.isFocused = false;
+				expect( balloon.visibleView ).to.be.null;
+			} );
 		} );
 
-		it( 'should hide the toolbar on render if the table is de–selected', () => {
-			setData(
-				model,
-				'<paragraph>foo</paragraph><table><tableRow><tableCell><paragraph>[]</paragraph></tableCell></tableRow></table>'
-			);
+		describe( 'integration with the editor selection', () => {
+			beforeEach( () => {
+				editor.ui.focusTracker.isFocused = true;
+			} );
+
+			it( 'should show the toolbar on ui#update when the table widget is selected', () => {
+				setData( editor.model, '<paragraph>[foo]</paragraph><table><tableRow><tableCell></tableCell></tableRow></table>' );
 
-			expect( balloon.visibleView ).to.equal( toolbar );
+				expect( balloon.visibleView ).to.be.null;
 
-			model.change( writer => {
-				// Select the <paragraph>[...]</paragraph>
-				writer.setSelection(
-					Range.createIn( doc.getRoot().getChild( 0 ) )
-				);
+				editor.ui.fire( 'update' );
+
+				expect( balloon.visibleView ).to.be.null;
+
+				editor.model.change( writer => {
+					// Select the [<table></table>]
+					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 );
 			} );
 
-			expect( balloon.visibleView ).to.be.null;
+			it( 'should not show the toolbar on ui#update when the selection is inside a table cell', () => {
+				setData( editor.model, '<table><tableRow><tableCell>[]</tableCell></tableRow></table>' );
 
-			// 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;
+				expect( balloon.visibleView ).to.be.null;
+
+				editor.ui.fire( 'update' );
+
+				expect( balloon.visibleView ).to.be.null;
+			} );
+
+			it( 'should not engage when the toolbar is in the balloon yet invisible', () => {
+				setData( model, '<table><tableRow><tableCell></tableCell></tableRow></table>' );
+
+				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 table is de–selected', () => {
+				setData( model, '<paragraph>foo</paragraph>[<table><tableRow><tableCell></tableCell></tableRow></table>]' );
+				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;
+			} );
 		} );
 	} );
+} );
 
-	// 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 );
+// 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'
-				} );
+			view.set( {
+				label: 'fake button'
+			} );
 
-				return view;
+			return view;
+		} );
+	}
+}
+
+// Plugin that adds foo_button to editor's component factory.
+class FooButton extends Plugin {
+	init() {
+		this.editor.ui.componentFactory.add( 'foo_button', locale => {
+			const view = new ButtonView( locale );
+
+			view.set( {
+				label: 'foo button'
 			} );
-		}
+
+			return view;
+		} );
 	}
-} );
+}