Browse Source

Merge branch 'master' into i/6150

Piotrek Koszuliński 5 years ago
parent
commit
5cc51a067e

+ 5 - 19
packages/ckeditor5-table/src/tableclipboard.js

@@ -48,18 +48,18 @@ export default class TableClipboard extends Plugin {
 		 */
 		this._tableSelection = editor.plugins.get( 'TableSelection' );
 
-		this.listenTo( viewDocument, 'copy', ( evt, data ) => this._onCopy( evt, data ), { priority: 'normal' } );
-		this.listenTo( viewDocument, 'cut', ( evt, data ) => this._onCut( evt, data ), { priority: 'high' } );
+		this.listenTo( viewDocument, 'copy', ( evt, data ) => this._onCopyCut( evt, data ) );
+		this.listenTo( viewDocument, 'cut', ( evt, data ) => this._onCopyCut( evt, data ) );
 	}
 
 	/**
-	 * A clipboard "copy" event handler.
+	 * Copies table content to a clipboard on "copy" & "cut" events.
 	 *
 	 * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the handled event.
 	 * @param {Object} data Clipboard event data.
 	 * @private
 	 */
-	_onCopy( evt, data ) {
+	_onCopyCut( evt, data ) {
 		const tableSelection = this._tableSelection;
 
 		if ( !tableSelection.hasMultiCellSelection ) {
@@ -72,7 +72,7 @@ export default class TableClipboard extends Plugin {
 		const dataController = this.editor.data;
 		const viewDocument = this.editor.editing.view.document;
 
-		const content = dataController.toView( tableSelection.getSelectionAsFragment() );
+		const content = dataController.toView( this._tableSelection.getSelectionAsFragment() );
 
 		viewDocument.fire( 'clipboardOutput', {
 			dataTransfer: data.dataTransfer,
@@ -80,18 +80,4 @@ export default class TableClipboard extends Plugin {
 			method: evt.name
 		} );
 	}
-
-	/**
-	 * A clipboard "cut" event handler.
-	 *
-	 * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the handled event.
-	 * @param {Object} data Clipboard event data.
-	 * @private
-	 */
-	_onCut( evt, data ) {
-		if ( this._tableSelection.hasMultiCellSelection ) {
-			data.preventDefault();
-			evt.stop();
-		}
-	}
 }

+ 74 - 2
packages/ckeditor5-table/src/tableselection.js

@@ -14,6 +14,7 @@ import TableUtils from './tableutils';
 import { setupTableSelectionHighlighting } from './tableselection/converters';
 import MouseSelectionHandler from './tableselection/mouseselectionhandler';
 import { findAncestor } from './commands/utils';
+import { clearTableCellsContents } from './tableselection/utils';
 import cropTable from './tableselection/croptable';
 
 import '../theme/tableselection.css';
@@ -95,13 +96,38 @@ export default class TableSelection extends Plugin {
 	 */
 	init() {
 		const editor = this.editor;
-		const selection = editor.model.document.selection;
+		const model = editor.model;
+		const selection = model.document.selection;
 
 		this._tableUtils = editor.plugins.get( 'TableUtils' );
 
 		setupTableSelectionHighlighting( editor, this );
 
-		selection.on( 'change:range', () => this._clearSelectionOnExternalChange( selection ) );
+		this.listenTo( selection, 'change:range', () => this._clearSelectionOnExternalChange( selection ) );
+		this.listenTo( model, 'deleteContent', ( evt, args ) => this._handleDeleteContent( evt, args ), { priority: 'high' } );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	afterInit() {
+		const editor = this.editor;
+
+		const deleteCommand = editor.commands.get( 'delete' );
+
+		if ( deleteCommand ) {
+			this.listenTo( deleteCommand, 'execute', event => {
+				this._handleDeleteCommand( event, { isForward: false } );
+			}, { priority: 'high' } );
+		}
+
+		const forwardDeleteCommand = editor.commands.get( 'forwardDelete' );
+
+		if ( forwardDeleteCommand ) {
+			this.listenTo( forwardDeleteCommand, 'execute', event => {
+				this._handleDeleteCommand( event, { isForward: true } );
+			}, { priority: 'high' } );
+		}
 	}
 
 	/**
@@ -279,4 +305,50 @@ export default class TableSelection extends Plugin {
 			this.clearSelection();
 		}
 	}
+
+	/**
+	 * It overrides default `model.deleteContent()` behavior over a selected table fragment.
+	 *
+	 * @private
+	 * @param {module:utils/eventinfo~EventInfo} event
+	 * @param {Array.<*>} args Delete content method arguments.
+	 */
+	_handleDeleteContent( event, args ) {
+		const [ selection ] = args;
+		const model = this.editor.model;
+
+		if ( this.hasMultiCellSelection && selection.is( 'documentSelection' ) ) {
+			event.stop();
+
+			clearTableCellsContents( model, this.getSelectedTableCells() );
+
+			model.change( writer => {
+				writer.setSelection( Array.from( this.getSelectedTableCells() ).pop(), 0 );
+			} );
+		}
+	}
+
+	/**
+	 * It overrides default `DeleteCommand` behavior over a selected table fragment.
+	 *
+	 * @private
+	 * @param {module:utils/eventinfo~EventInfo} event
+	 * @param {Object} options
+	 * @param {Boolean} options.isForward Whether it handles forward or backward delete.
+	 */
+	_handleDeleteCommand( event, options ) {
+		const model = this.editor.model;
+
+		if ( this.hasMultiCellSelection ) {
+			event.stop();
+
+			clearTableCellsContents( model, this.getSelectedTableCells() );
+
+			const tableCell = options.isForward ? this._startElement : this._endElement;
+
+			model.change( writer => {
+				writer.setSelection( tableCell, 0 );
+			} );
+		}
+	}
 }

+ 1 - 1
packages/ckeditor5-table/src/tableselection/croptable.js

@@ -17,7 +17,7 @@ import { findAncestor } from '../commands/utils';
  *		tableSelection.startSelectingFrom( startCell )
  *		tableSelection.setSelectingFrom( endCell )
  *
- *		const croppedTable = cropTable( tableSelection.getSelectedTableCells );
+ *		const croppedTable = cropTable( tableSelection.getSelectedTableCells() );
  *
  * **Note**: This function is used also by {@link module:table/tableselection~TableSelection#getSelectionAsFragment}
  *

+ 31 - 0
packages/ckeditor5-table/src/tableselection/utils.js

@@ -0,0 +1,31 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module table/tableselection/utils
+ */
+
+/**
+ * Clears contents of the passed table cells.
+ *
+ * This is to be used with table selection
+ *
+ *		tableSelection.startSelectingFrom( startCell )
+ *		tableSelection.setSelectingFrom( endCell )
+ *
+ *		clearTableCellsContents( editor.model, tableSelection.getSelectedTableCells() );
+ *
+ * **Note**: This function is used also by {@link module:table/tableselection~TableSelection#getSelectionAsFragment}
+ *
+ * @param {module:engine/model/model~Model} model
+ * @param {Iterable.<module:engine/model/element~Element>} tableCells
+ */
+export function clearTableCellsContents( model, tableCells ) {
+	model.change( writer => {
+		for ( const tableCell of tableCells ) {
+			model.deleteContent( writer.createSelection( tableCell, 'in' ) );
+		}
+	} );
+}

+ 108 - 80
packages/ckeditor5-table/tests/tableclipboard.js

@@ -5,14 +5,13 @@
 
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
-import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 import TableEditing from '../src/tableediting';
 import { modelTable, viewTable } from './_utils/utils';
 import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
-import ViewDocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment';
-import { stringify as stringifyView } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 import TableClipboard from '../src/tableclipboard';
+import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 
 describe( 'table clipboard', () => {
 	let editor, model, modelRoot, tableSelection, viewDocument;
@@ -40,7 +39,7 @@ describe( 'table clipboard', () => {
 
 	describe( 'Clipboard integration', () => {
 		describe( 'copy', () => {
-			it( 'should to nothing for normal selection in table', () => {
+			it( 'should do nothing for normal selection in table', () => {
 				const dataTransferMock = createDataTransfer();
 				const spy = sinon.spy();
 
@@ -54,35 +53,26 @@ describe( 'table clipboard', () => {
 				sinon.assert.calledOnce( spy );
 			} );
 
-			it( 'should copy selected table cells as standalone table', done => {
-				const dataTransferMock = createDataTransfer();
+			it( 'should copy selected table cells as a standalone table', () => {
 				const preventDefaultSpy = sinon.spy();
 
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 2 ] ) );
 
-				viewDocument.on( 'clipboardOutput', ( evt, data ) => {
-					expect( preventDefaultSpy.calledOnce ).to.be.true;
-					expect( data.method ).to.equal( 'copy' );
-
-					expect( data.dataTransfer ).to.equal( dataTransferMock );
-
-					expect( data.content ).is.instanceOf( ViewDocumentFragment );
-					expect( stringifyView( data.content ) ).to.equal( viewTable( [
-						[ '01', '02' ],
-						[ '11', '12' ]
-					] ) );
-
-					done();
-				} );
-
-				viewDocument.fire( 'copy', {
-					dataTransfer: dataTransferMock,
+				const data = {
+					dataTransfer: createDataTransfer(),
 					preventDefault: preventDefaultSpy
-				} );
+				};
+				viewDocument.fire( 'copy', data );
+
+				sinon.assert.calledOnce( preventDefaultSpy );
+				expect( data.dataTransfer.getData( 'text/html' ) ).to.equal( viewTable( [
+					[ '01', '02' ],
+					[ '11', '12' ]
+				] ) );
 			} );
 
-			it( 'should trim selected table to a selection rectangle (inner cell with colspan, no colspan after trim)', done => {
+			it( 'should trim selected table to a selection rectangle (inner cell with colspan, no colspan after trim)', () => {
 				setModelData( model, modelTable( [
 					[ '00[]', '01', '02' ],
 					[ '10', { contents: '11', colspan: 2 } ],
@@ -92,14 +82,14 @@ describe( 'table clipboard', () => {
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
 
-				assertClipboardCopy( viewTable( [
+				assertClipboardContentOnMethod( 'copy', viewTable( [
 					[ '00', '01' ],
 					[ '10', '11' ],
 					[ '20', '21' ]
-				] ), done );
+				] ) );
 			} );
 
-			it( 'should trim selected table to a selection rectangle (inner cell with colspan, has colspan after trim)', done => {
+			it( 'should trim selected table to a selection rectangle (inner cell with colspan, has colspan after trim)', () => {
 				setModelData( model, modelTable( [
 					[ '00[]', '01', '02' ],
 					[ { contents: '10', colspan: 3 } ],
@@ -109,14 +99,14 @@ describe( 'table clipboard', () => {
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
 
-				assertClipboardCopy( viewTable( [
+				assertClipboardContentOnMethod( 'copy', viewTable( [
 					[ '00', '01' ],
 					[ { contents: '10', colspan: 2 } ],
 					[ '20', '21' ]
-				] ), done );
+				] ) );
 			} );
 
-			it( 'should trim selected table to a selection rectangle (inner cell with rowspan, no colspan after trim)', done => {
+			it( 'should trim selected table to a selection rectangle (inner cell with rowspan, no colspan after trim)', () => {
 				setModelData( model, modelTable( [
 					[ '00[]', '01', '02' ],
 					[ '10', { contents: '11', rowspan: 2 }, '12' ],
@@ -126,13 +116,13 @@ describe( 'table clipboard', () => {
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 2 ] ) );
 
-				assertClipboardCopy( viewTable( [
+				assertClipboardContentOnMethod( 'copy', viewTable( [
 					[ '00', '01', '02' ],
 					[ '10', '11', '12' ]
-				] ), done );
+				] ) );
 			} );
 
-			it( 'should trim selected table to a selection rectangle (inner cell with rowspan, has rowspan after trim)', done => {
+			it( 'should trim selected table to a selection rectangle (inner cell with rowspan, has rowspan after trim)', () => {
 				setModelData( model, modelTable( [
 					[ '00[]', { contents: '01', rowspan: 3 }, '02' ],
 					[ '10', '12' ],
@@ -142,13 +132,13 @@ describe( 'table clipboard', () => {
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
 
-				assertClipboardCopy( viewTable( [
+				assertClipboardContentOnMethod( 'copy', viewTable( [
 					[ '00', { contents: '01', rowspan: 2 }, '02' ],
 					[ '10', '12' ]
-				] ), done );
+				] ) );
 			} );
 
-			it( 'should prepend spanned columns with empty cells (outside cell with colspan)', done => {
+			it( 'should prepend spanned columns with empty cells (outside cell with colspan)', () => {
 				setModelData( model, modelTable( [
 					[ '00[]', '01', '02' ],
 					[ { contents: '10', colspan: 2 }, '12' ],
@@ -158,14 +148,14 @@ describe( 'table clipboard', () => {
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
 
-				assertClipboardCopy( viewTable( [
+				assertClipboardContentOnMethod( 'copy', viewTable( [
 					[ '01', '02' ],
-					[ '', '12' ],
+					[ '&nbsp;', '12' ],
 					[ '21', '22' ]
-				] ), done );
+				] ) );
 			} );
 
-			it( 'should prepend spanned columns with empty cells (outside cell with rowspan)', done => {
+			it( 'should prepend spanned columns with empty cells (outside cell with rowspan)', () => {
 				setModelData( model, modelTable( [
 					[ '00[]', { contents: '01', rowspan: 2 }, '02' ],
 					[ '10', '12' ],
@@ -175,13 +165,13 @@ describe( 'table clipboard', () => {
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 1, 0 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
 
-				assertClipboardCopy( viewTable( [
-					[ '10', '', '12' ],
+				assertClipboardContentOnMethod( 'copy', viewTable( [
+					[ '10', '&nbsp;', '12' ],
 					[ '20', '21', '22' ]
-				] ), done );
+				] ) );
 			} );
 
-			it( 'should fix selected table to a selection rectangle (hardcore case)', done => {
+			it( 'should fix selected table to a selection rectangle (hardcore case)', () => {
 				// This test check how previous simple rules run together (mixed prepending and trimming).
 				// In the example below a selection is set from cell "32" to "88"
 				//
@@ -222,17 +212,17 @@ describe( 'table clipboard', () => {
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 7, 6 ] ) );
 
-				assertClipboardCopy( viewTable( [
-					[ '21', '', '23', '', '', { contents: '27', colspan: 2 } ],
-					[ '31', '', '33', '', '', '37', '37' ],
-					[ '', '', '', '', '', '47', '47' ],
-					[ '51', '52', '53', '', '', { contents: '57', rowspan: 3 }, '57' ],
-					[ { contents: '61', colspan: 3 }, '', '', '', '67' ],
+				assertClipboardContentOnMethod( 'copy', viewTable( [
+					[ '21', '&nbsp;', '23', '&nbsp;', '&nbsp;', { contents: '27', colspan: 2 } ],
+					[ '31', '&nbsp;', '33', '&nbsp;', '&nbsp;', '37', '37' ],
+					[ '&nbsp;', '&nbsp;', '&nbsp;', '&nbsp;', '&nbsp;', '47', '47' ],
+					[ '51', '52', '53', '&nbsp;', '&nbsp;', { contents: '57', rowspan: 3 }, '57' ],
+					[ { contents: '61', colspan: 3 }, '&nbsp;', '&nbsp;', '&nbsp;', '67' ],
 					[ '71', '72', '73', '74', '75', '77' ]
-				] ), done );
+				] ) );
 			} );
 
-			it( 'should update table heading attributes (selection with headings)', done => {
+			it( 'should update table heading attributes (selection with headings)', () => {
 				setModelData( model, modelTable( [
 					[ '00', '01', '02', '03', '04' ],
 					[ '10', '11', '12', '13', '14' ],
@@ -244,14 +234,14 @@ describe( 'table clipboard', () => {
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 3, 3 ] ) );
 
-				assertClipboardCopy( viewTable( [
+				assertClipboardContentOnMethod( 'copy', viewTable( [
 					[ '11', '12', '13' ],
 					[ '21', '22', '23' ],
 					[ { contents: '31', isHeading: true }, '32', '33' ] // TODO: bug in viewTable
-				], { headingRows: 2, headingColumns: 1 } ), done );
+				], { headingRows: 2, headingColumns: 1 } ) );
 			} );
 
-			it( 'should update table heading attributes (selection without headings)', done => {
+			it( 'should update table heading attributes (selection without headings)', () => {
 				setModelData( model, modelTable( [
 					[ '00', '01', '02', '03', '04' ],
 					[ '10', '11', '12', '13', '14' ],
@@ -263,60 +253,98 @@ describe( 'table clipboard', () => {
 				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 3, 2 ] ) );
 				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 4, 4 ] ) );
 
-				assertClipboardCopy( viewTable( [
+				assertClipboardContentOnMethod( 'copy', viewTable( [
 					[ '32', '33', '34' ],
 					[ '42', '43', '44' ]
-				] ), done );
+				] ) );
 			} );
 		} );
 
 		describe( 'cut', () => {
-			it( 'is disabled for multi-range selection over a table', () => {
+			it( 'should not block clipboardOutput if no multi-cell selection', () => {
+				setModelData( model, modelTable( [
+					[ '[00]', '01', '02' ],
+					[ '10', '11', '12' ],
+					[ '20', '21', '22' ]
+				] ) );
+
 				const dataTransferMock = createDataTransfer();
-				const preventDefaultSpy = sinon.spy();
-				const spy = sinon.spy();
 
-				viewDocument.on( 'clipboardOutput', spy );
+				viewDocument.fire( 'cut', {
+					dataTransfer: dataTransferMock,
+					preventDefault: sinon.spy()
+				} );
 
-				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
-				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 2 ] ) );
+				expect( dataTransferMock.getData( 'text/html' ) ).to.equal( '00' );
+			} );
+
+			it( 'should be preventable', () => {
+				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
+				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
+
+				viewDocument.on( 'clipboardOutput', evt => evt.stop(), { priority: 'high' } );
 
 				viewDocument.fire( 'cut', {
-					dataTransfer: dataTransferMock,
-					preventDefault: preventDefaultSpy
+					dataTransfer: createDataTransfer(),
+					preventDefault: sinon.spy()
 				} );
 
-				sinon.assert.notCalled( spy );
-				sinon.assert.calledOnce( preventDefaultSpy );
+				assertEqualMarkup( getModelData( model ), modelTable( [
+					[ { contents: '00', isSelected: true }, { contents: '01', isSelected: true }, '02' ],
+					[ { contents: '10', isSelected: true }, { contents: '11', isSelected: true }, '12' ],
+					[ '20', '21', '22' ]
+				] ) );
 			} );
 
-			it( 'is not disabled normal selection over a table', () => {
-				const dataTransferMock = createDataTransfer();
+			it( 'is clears selected table cells', () => {
 				const spy = sinon.spy();
 
 				viewDocument.on( 'clipboardOutput', spy );
 
+				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
+				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
+
 				viewDocument.fire( 'cut', {
-					dataTransfer: dataTransferMock,
+					dataTransfer: createDataTransfer(),
 					preventDefault: sinon.spy()
 				} );
 
-				sinon.assert.calledOnce( spy );
+				assertEqualMarkup( getModelData( model ), modelTable( [
+					[ '', '', '02' ],
+					[ '', '[]', '12' ],
+					[ '20', '21', '22' ]
+				] ) );
 			} );
-		} );
-	} );
 
-	function assertClipboardCopy( expectedViewTable, callback ) {
-		viewDocument.on( 'clipboardOutput', ( evt, data ) => {
-			expect( stringifyView( data.content ) ).to.equal( expectedViewTable );
+			it( 'should copy selected table cells as a standalone table', () => {
+				const preventDefaultSpy = sinon.spy();
+
+				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
+				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 2 ] ) );
+
+				const data = {
+					dataTransfer: createDataTransfer(),
+					preventDefault: preventDefaultSpy
+				};
+				viewDocument.fire( 'cut', data );
 
-			callback();
+				sinon.assert.calledOnce( preventDefaultSpy );
+				expect( data.dataTransfer.getData( 'text/html' ) ).to.equal( viewTable( [
+					[ '01', '02' ],
+					[ '11', '12' ]
+				] ) );
+			} );
 		} );
+	} );
 
-		viewDocument.fire( 'copy', {
+	function assertClipboardContentOnMethod( method, expectedViewTable ) {
+		const data = {
 			dataTransfer: createDataTransfer(),
 			preventDefault: sinon.spy()
-		} );
+		};
+		viewDocument.fire( method, data );
+
+		expect( data.dataTransfer.getData( 'text/html' ) ).to.equal( expectedViewTable );
 	}
 
 	function createDataTransfer() {

+ 173 - 0
packages/ckeditor5-table/tests/tableselection-integration.js

@@ -0,0 +1,173 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals document */
+
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+import TableEditing from '../src/tableediting';
+import TableSelection from '../src/tableselection';
+import { modelTable } from './_utils/utils';
+import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
+import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
+import Delete from '@ckeditor/ckeditor5-typing/src/delete';
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
+import Input from '@ckeditor/ckeditor5-typing/src/input';
+import ViewText from '@ckeditor/ckeditor5-engine/src/view/text';
+
+describe( 'table selection', () => {
+	let editor, model, tableSelection, modelRoot, element, viewDocument;
+
+	describe( 'TableSelection - input integration', () => {
+		afterEach( async () => {
+			element.remove();
+			await editor.destroy();
+		} );
+
+		describe( 'on delete', () => {
+			beforeEach( async () => {
+				await setupEditor( [ Delete ] );
+			} );
+
+			it( 'should clear contents of the selected table cells and put selection in last cell on backward delete', () => {
+				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
+				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
+
+				const domEventData = new DomEventData( viewDocument, {
+					preventDefault: sinon.spy()
+				}, {
+					direction: 'backward',
+					unit: 'character',
+					sequence: 1
+				} );
+				viewDocument.fire( 'delete', domEventData );
+
+				assertEqualMarkup( getModelData( model ), modelTable( [
+					[ '', '', '13' ],
+					[ '', '[]', '23' ],
+					[ '31', '32', '33' ]
+				] ) );
+			} );
+
+			it( 'should clear contents of the selected table cells and put selection in last cell on forward delete', () => {
+				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
+				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
+
+				const domEventData = new DomEventData( viewDocument, {
+					preventDefault: sinon.spy()
+				}, {
+					direction: 'forward',
+					unit: 'character',
+					sequence: 1
+				} );
+				viewDocument.fire( 'delete', domEventData );
+
+				assertEqualMarkup( getModelData( model ), modelTable( [
+					[ '[]', '', '13' ],
+					[ '', '', '23' ],
+					[ '31', '32', '33' ]
+				] ) );
+			} );
+
+			it( 'should not interfere with default key handler if no table selection', () => {
+				setModelData( model, modelTable( [
+					[ '11[]', '12', '13' ],
+					[ '21', '22', '23' ],
+					[ '31', '32', '33' ]
+				] ) );
+
+				const domEventData = new DomEventData( viewDocument, {
+					preventDefault: sinon.spy()
+				}, {
+					direction: 'backward',
+					unit: 'character',
+					sequence: 1
+				} );
+				viewDocument.fire( 'delete', domEventData );
+
+				assertEqualMarkup( getModelData( model ), modelTable( [
+					[ '1[]', '12', '13' ],
+					[ '21', '22', '23' ],
+					[ '31', '32', '33' ]
+				] ) );
+			} );
+		} );
+
+		describe( 'on user input', () => {
+			beforeEach( async () => {
+				await setupEditor( [ Input ] );
+			} );
+
+			it( 'should clear contents of the selected table cells and put selection in last cell on user input', () => {
+				tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
+				tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
+
+				viewDocument.fire( 'keydown', { keyCode: getCode( 'x' ) } );
+
+				//                                      figure       table         tbody         tr            td            span
+				const viewSpan = viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 1 ).getChild( 1 ).getChild( 0 );
+
+				viewDocument.fire( 'mutations', [
+					{
+						type: 'children',
+						oldChildren: [],
+						newChildren: [ new ViewText( 'x' ) ],
+						node: viewSpan
+					}
+				] );
+
+				assertEqualMarkup( getModelData( model ), modelTable( [
+					[ '', '', '13' ],
+					[ '', 'x[]', '23' ],
+					[ '31', '32', '33' ]
+				] ) );
+			} );
+
+			it( 'should not interfere with default key handler if no table selection', () => {
+				viewDocument.fire( 'keydown', { keyCode: getCode( 'x' ) } );
+
+				//                                      figure       table         tbody         tr            td            span
+				const viewSpan = viewDocument.getRoot().getChild( 0 ).getChild( 1 ).getChild( 0 ).getChild( 0 ).getChild( 0 ).getChild( 0 );
+
+				viewDocument.fire( 'mutations', [
+					{
+						type: 'children',
+						oldChildren: [],
+						newChildren: [ new ViewText( 'x' ) ],
+						node: viewSpan
+					}
+				] );
+
+				assertEqualMarkup( getModelData( model ), modelTable( [
+					[ 'x[]11', '12', '13' ],
+					[ '21', '22', '23' ],
+					[ '31', '32', '33' ]
+				] ) );
+			} );
+		} );
+	} );
+
+	async function setupEditor( plugins ) {
+		element = document.createElement( 'div' );
+		document.body.appendChild( element );
+
+		editor = await ClassicTestEditor.create( element, {
+			plugins: [ TableEditing, TableSelection, Paragraph, ...plugins ]
+		} );
+
+		model = editor.model;
+		modelRoot = model.document.getRoot();
+		viewDocument = editor.editing.view.document;
+		tableSelection = editor.plugins.get( TableSelection );
+
+		setModelData( model, modelTable( [
+			[ '[]11', '12', '13' ],
+			[ '21', '22', '23' ],
+			[ '31', '32', '33' ]
+		] ) );
+	}
+} );