Răsfoiți Sursa

Merge pull request #268 from ckeditor/i/6127

Internal: Integrated multi cell selection with set column /row header commands. Closes ckeditor/ckeditor5#6127.
Piotrek Koszuliński 5 ani în urmă
părinte
comite
d9a4c4048c

+ 12 - 11
packages/ckeditor5-table/src/commands/setheadercolumncommand.js

@@ -13,7 +13,7 @@ import {
 	updateNumericAttribute,
 	isHeadingColumnCell
 } from './utils';
-import { getTableCellsContainingSelection } from '../utils';
+import { getSelectionAffectedTableCells } from '../utils';
 
 /**
  * The header column command.
@@ -36,10 +36,9 @@ export default class SetHeaderColumnCommand extends Command {
 	 */
 	refresh() {
 		const model = this.editor.model;
-		const doc = model.document;
-		const tableCell = getTableCellsContainingSelection( doc.selection )[ 0 ];
+		const selectedCells = getSelectionAffectedTableCells( model.document.selection );
 		const tableUtils = this.editor.plugins.get( 'TableUtils' );
-		const isInTable = !!tableCell;
+		const isInTable = selectedCells.length > 0;
 
 		this.isEnabled = isInTable;
 
@@ -51,7 +50,7 @@ export default class SetHeaderColumnCommand extends Command {
 		 * @readonly
 		 * @member {Boolean} #value
 		 */
-		this.value = isInTable && isHeadingColumnCell( tableUtils, tableCell );
+		this.value = isInTable && selectedCells.every( cell => isHeadingColumnCell( tableUtils, cell ) );
 	}
 
 	/**
@@ -68,21 +67,23 @@ export default class SetHeaderColumnCommand extends Command {
 	 */
 	execute( options = {} ) {
 		const model = this.editor.model;
-		const doc = model.document;
-		const selection = doc.selection;
 		const tableUtils = this.editor.plugins.get( 'TableUtils' );
 
-		const tableCell = getTableCellsContainingSelection( selection )[ 0 ];
-		const tableRow = tableCell.parent;
+		const selectedCells = getSelectionAffectedTableCells( model.document.selection );
+		const firstCell = selectedCells[ 0 ];
+		const lastCell = selectedCells[ selectedCells.length - 1 ];
+		const tableRow = firstCell.parent;
 		const table = tableRow.parent;
 
-		const { column: selectionColumn } = tableUtils.getCellLocation( tableCell );
+		const [ selectedColumnMin, selectedColumnMax ] =
+			// Returned cells might not necessary be in order, so make sure to sort it.
+			[ tableUtils.getCellLocation( firstCell ).column, tableUtils.getCellLocation( lastCell ).column ].sort();
 
 		if ( options.forceValue === this.value ) {
 			return;
 		}
 
-		const headingColumnsToSet = this.value ? selectionColumn : selectionColumn + 1;
+		const headingColumnsToSet = this.value ? selectedColumnMin : selectedColumnMax + 1;
 
 		model.change( writer => {
 			updateNumericAttribute( 'headingColumns', headingColumnsToSet, table, writer, 0 );

+ 14 - 18
packages/ckeditor5-table/src/commands/setheaderrowcommand.js

@@ -9,11 +9,8 @@
 
 import Command from '@ckeditor/ckeditor5-core/src/command';
 
-import {
-	createEmptyTableCell,
-	updateNumericAttribute
-} from './utils';
-import { getTableCellsContainingSelection } from '../utils';
+import { createEmptyTableCell, updateNumericAttribute } from './utils';
+import { getSelectionAffectedTableCells } from '../utils';
 import TableWalker from '../tablewalker';
 
 /**
@@ -36,11 +33,8 @@ export default class SetHeaderRowCommand extends Command {
 	 */
 	refresh() {
 		const model = this.editor.model;
-		const doc = model.document;
-		const selection = doc.selection;
-
-		const tableCell = getTableCellsContainingSelection( selection )[ 0 ];
-		const isInTable = !!tableCell;
+		const selectedCells = getSelectionAffectedTableCells( model.document.selection );
+		const isInTable = selectedCells.length > 0;
 
 		this.isEnabled = isInTable;
 
@@ -52,7 +46,7 @@ export default class SetHeaderRowCommand extends Command {
 		 * @readonly
 		 * @member {Boolean} #value
 		 */
-		this.value = isInTable && this._isInHeading( tableCell, tableCell.parent.parent );
+		this.value = isInTable && selectedCells.every( cell => this._isInHeading( cell, cell.parent.parent ) );
 	}
 
 	/**
@@ -69,21 +63,23 @@ export default class SetHeaderRowCommand extends Command {
 	 */
 	execute( options = {} ) {
 		const model = this.editor.model;
-		const doc = model.document;
-		const selection = doc.selection;
 
-		const tableCell = getTableCellsContainingSelection( selection )[ 0 ];
-		const tableRow = tableCell.parent;
-		const table = tableRow.parent;
+		const selectedCells = getSelectionAffectedTableCells( model.document.selection );
+		const firstCell = selectedCells[ 0 ];
+		const lastCell = selectedCells[ selectedCells.length - 1 ];
+		const table = firstCell.parent.parent;
 
 		const currentHeadingRows = table.getAttribute( 'headingRows' ) || 0;
-		const selectionRow = tableRow.index;
+
+		const [ selectedRowMin, selectedRowMax ] =
+			// Returned cells might not necessary be in order, so make sure to sort it.
+			[ firstCell.parent.index, lastCell.parent.index ].sort();
 
 		if ( options.forceValue === this.value ) {
 			return;
 		}
 
-		const headingRowsToSet = this.value ? selectionRow : selectionRow + 1;
+		const headingRowsToSet = this.value ? selectedRowMin : selectedRowMax + 1;
 
 		model.change( writer => {
 			if ( headingRowsToSet ) {

+ 271 - 3
packages/ckeditor5-table/tests/commands/setheadercolumncommand.js

@@ -7,7 +7,8 @@ import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltestedit
 import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 import SetHeaderColumnCommand from '../../src/commands/setheadercolumncommand';
-import { defaultConversion, defaultSchema, modelTable } from '../_utils/utils';
+import { assertSelectedCells, defaultConversion, defaultSchema, modelTable } from '../_utils/utils';
+import TableSelection from '../../src/tableselection';
 import TableUtils from '../../src/tableutils';
 import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 
@@ -17,7 +18,7 @@ describe( 'SetHeaderColumnCommand', () => {
 	beforeEach( () => {
 		return ModelTestEditor
 			.create( {
-				plugins: [ TableUtils ]
+				plugins: [ TableUtils, TableSelection ]
 			} )
 			.then( newEditor => {
 				editor = newEditor;
@@ -43,6 +44,36 @@ describe( 'SetHeaderColumnCommand', () => {
 			setData( model, '<table><tableRow><tableCell><paragraph>foo[]</paragraph></tableCell></tableRow></table>' );
 			expect( command.isEnabled ).to.be.true;
 		} );
+
+		it( 'should be true if multiple columns are selected', () => {
+			setData( model, modelTable( [
+				[ '01', '02', '03' ]
+			] ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
+			);
+
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'should be true if multiple header columns are selected', () => {
+			setData( model, modelTable( [
+				[ '01', '02', '03' ]
+			], { headingColumns: 2 } ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
+			);
+
+			expect( command.isEnabled ).to.be.true;
+		} );
 	} );
 
 	describe( 'value', () => {
@@ -64,6 +95,36 @@ describe( 'SetHeaderColumnCommand', () => {
 			expect( command.value ).to.be.true;
 		} );
 
+		it( 'should be true if multiple header columns are selected', () => {
+			setData( model, modelTable( [
+				[ '01', '02', '03' ]
+			], { headingColumns: 2 } ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
+			);
+
+			expect( command.value ).to.be.true;
+		} );
+
+		it( 'should be true if multiple header columns are selected in reversed order', () => {
+			setData( model, modelTable( [
+				[ '01', '02', '03' ]
+			], { headingColumns: 2 } ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] )
+			);
+
+			expect( command.value ).to.be.true;
+		} );
+
 		it( 'should be false if selection is in a heading row', () => {
 			setData( model, modelTable( [
 				[ '01', '02[]' ],
@@ -72,10 +133,25 @@ describe( 'SetHeaderColumnCommand', () => {
 
 			expect( command.value ).to.be.false;
 		} );
+
+		it( 'should be false if only part of selected columns are headers', () => {
+			setData( model, modelTable( [
+				[ '01', '02', '03', '04' ]
+			], { headingColumns: 2 } ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
+				modelRoot.getNodeByPath( [ 0, 0, 2 ] )
+			);
+
+			expect( command.value ).to.be.false;
+		} );
 	} );
 
 	describe( 'execute()', () => {
-		it( 'should set heading columns attribute that cover column in which is selection', () => {
+		it( 'should set heading columns attribute that cover column with collapsed selection', () => {
 			setData( model, modelTable( [
 				[ '00', '01[]', '02', '03' ]
 			] ) );
@@ -87,6 +163,29 @@ describe( 'SetHeaderColumnCommand', () => {
 			], { headingColumns: 2 } ) );
 		} );
 
+		it( 'should set heading columns attribute that cover column with entire cell selected', () => {
+			setData( model, modelTable( [
+				[ '00', '01', '02', '03' ]
+			] ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
+			);
+
+			command.execute();
+
+			assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+				[ '00', '01', '02', '03' ]
+			], { headingColumns: 2 } ) );
+
+			assertSelectedCells( model, [
+				[ 0, 1, 0, 0 ]
+			] );
+		} );
+
 		it( 'should set heading columns attribute below current selection column', () => {
 			setData( model, modelTable( [
 				[ '00', '01[]', '02', '03' ]
@@ -99,6 +198,175 @@ describe( 'SetHeaderColumnCommand', () => {
 			], { headingColumns: 1 } ) );
 		} );
 
+		describe( 'multi-cell selection', () => {
+			describe( 'setting header', () => {
+				it( 'should set it correctly in a middle of single-row, multiple cell selection', () => {
+					setData( model, modelTable( [
+						[ '00', '01', '02', '03' ]
+					] ) );
+
+					const tableSelection = editor.plugins.get( TableSelection );
+					const modelRoot = model.document.getRoot();
+					tableSelection._setCellSelection(
+						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
+						modelRoot.getNodeByPath( [ 0, 0, 2 ] )
+					);
+
+					command.execute();
+
+					assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+						[ '00', '01', '02', '03' ]
+					], {
+						headingColumns: 3
+					} ) );
+
+					assertSelectedCells( model, [
+						[ 0, 1, 1, 0 ]
+					] );
+				} );
+
+				it( 'should set it correctly in a middle of multi-row, multiple cell selection', () => {
+					setData( model, modelTable( [
+						[ '00', '01', '02', '03' ],
+						[ '10', '11', '12', '13' ]
+					] ) );
+
+					const tableSelection = editor.plugins.get( TableSelection );
+					const modelRoot = model.document.getRoot();
+					tableSelection._setCellSelection(
+						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
+						modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+					);
+
+					command.execute();
+
+					assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+						[ '00', '01', '02', '03' ],
+						[ '10', '11', '12', '13' ]
+					], {
+						headingColumns: 2
+					} ) );
+
+					assertSelectedCells( model, [
+						[ 0, 1, 0, 0 ],
+						[ 0, 1, 0, 0 ]
+					] );
+				} );
+
+				it( 'should remove header columns in case of multiple cell selection', () => {
+					setData( model, modelTable( [
+						[ '00', '01', '02', '03' ]
+					], { headingColumns: 4 } ) );
+
+					const tableSelection = editor.plugins.get( TableSelection );
+					const modelRoot = model.document.getRoot();
+					tableSelection._setCellSelection(
+						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
+						modelRoot.getNodeByPath( [ 0, 0, 2 ] )
+					);
+
+					command.execute();
+
+					assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+						[ '00', '01', '02', '03' ]
+					], {
+						headingColumns: 1
+					} ) );
+
+					assertSelectedCells( model, [
+						[ 0, 1, 1, 0 ]
+					] );
+				} );
+
+				it( 'should remove header columns in case of multiple cell selection - reversed order', () => {
+					setData( model, modelTable( [
+						[ '00', '01', '02', '03' ]
+					], {
+						headingColumns: 4
+					} ) );
+
+					const tableSelection = editor.plugins.get( TableSelection );
+					const modelRoot = model.document.getRoot();
+					tableSelection._setCellSelection(
+						modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
+						modelRoot.getNodeByPath( [ 0, 0, 1 ] )
+					);
+
+					command.execute();
+
+					assertEqualMarkup( getData( model, {
+						withoutSelection: true
+					} ), modelTable( [
+						[ '00', '01', '02', '03' ]
+					], {
+						headingColumns: 1
+					} ) );
+
+					assertSelectedCells( model, [
+						[ 0, 1, 1, 0 ]
+					] );
+				} );
+
+				it( 'should respect forceValue=true in case of multiple cell selection', () => {
+					setData( model, modelTable( [
+						[ '00', '01', '02', '03' ]
+					], {
+						headingColumns: 3
+					} ) );
+
+					const tableSelection = editor.plugins.get( TableSelection );
+					const modelRoot = model.document.getRoot();
+					tableSelection._setCellSelection(
+						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
+						modelRoot.getNodeByPath( [ 0, 0, 2 ] )
+					);
+
+					command.execute( { forceValue: true } );
+
+					assertEqualMarkup( getData( model, {
+						withoutSelection: true
+					} ), modelTable( [
+						[ '00', '01', '02', '03' ]
+					], {
+						headingColumns: 3
+					} ) );
+
+					assertSelectedCells( model, [
+						[ 0, 1, 1, 0 ]
+					] );
+				} );
+
+				it( 'should respect forceValue=false in case of multiple cell selection', () => {
+					setData( model, modelTable( [
+						[ '00', '01', '02', '03' ]
+					], {
+						headingColumns: 1
+					} ) );
+
+					const tableSelection = editor.plugins.get( TableSelection );
+					const modelRoot = model.document.getRoot();
+					tableSelection._setCellSelection(
+						modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
+						modelRoot.getNodeByPath( [ 0, 0, 2 ] )
+					);
+
+					command.execute( { forceValue: false } );
+
+					assertEqualMarkup( getData( model, {
+						withoutSelection: true
+					} ), modelTable( [
+						[ '00', '01', '02', '03' ]
+					], {
+						headingColumns: 1
+					} ) );
+
+					assertSelectedCells( model, [
+						[ 0, 1, 1, 0 ]
+					] );
+				} );
+			} );
+		} );
+
 		it( 'should toggle of selected column', () => {
 			setData( model, modelTable( [
 				[ '00', '01[]', '02', '03' ]

+ 295 - 2
packages/ckeditor5-table/tests/commands/setheaderrowcommand.js

@@ -6,7 +6,8 @@
 import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
 import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import SetHeaderRowCommand from '../../src/commands/setheaderrowcommand';
-import { defaultConversion, defaultSchema, modelTable } from '../_utils/utils';
+import { assertSelectedCells, defaultConversion, defaultSchema, modelTable } from '../_utils/utils';
+import TableSelection from '../../src/tableselection';
 import TableUtils from '../../src/tableutils';
 import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 
@@ -16,7 +17,7 @@ describe( 'SetHeaderRowCommand', () => {
 	beforeEach( () => {
 		return ModelTestEditor
 			.create( {
-				plugins: [ TableUtils ]
+				plugins: [ TableUtils, TableSelection ]
 			} )
 			.then( newEditor => {
 				editor = newEditor;
@@ -42,6 +43,36 @@ describe( 'SetHeaderRowCommand', () => {
 			setData( model, '<table><tableRow><tableCell><paragraph>foo[]</paragraph></tableCell></tableRow></table>' );
 			expect( command.isEnabled ).to.be.true;
 		} );
+
+		it( 'should be true if multiple cells are selected', () => {
+			setData( model, modelTable( [
+				[ '01', '02', '03' ]
+			] ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
+			);
+
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'should be true if multiple cells in a header row are selected', () => {
+			setData( model, modelTable( [
+				[ '01', '02', '03' ]
+			], { headingRows: 1 } ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
+			);
+
+			expect( command.isEnabled ).to.be.true;
+		} );
 	} );
 
 	describe( 'value', () => {
@@ -80,6 +111,54 @@ describe( 'SetHeaderRowCommand', () => {
 
 			expect( command.value ).to.be.false;
 		} );
+
+		it( 'should be true if multiple header rows are selected', () => {
+			setData( model, modelTable( [
+				[ '01', '02' ],
+				[ '11', '12' ]
+			], { headingRows: 2 } ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] ),
+				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+			);
+
+			expect( command.value ).to.be.true;
+		} );
+
+		it( 'should be true if multiple header columns are selected in reversed order', () => {
+			setData( model, modelTable( [
+				[ '01', '02' ],
+				[ '11', '12' ]
+			], { headingRows: 2 } ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
+				modelRoot.getNodeByPath( [ 0, 0, 1 ] )
+			);
+
+			expect( command.value ).to.be.true;
+		} );
+
+		it( 'should be false if only part of selected columns are headers', () => {
+			setData( model, modelTable( [
+				[ '01', '02' ],
+				[ '11', '12' ]
+			], { headingRows: 1 } ) );
+
+			const tableSelection = editor.plugins.get( TableSelection );
+			const modelRoot = model.document.getRoot();
+			tableSelection._setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 1, 0 ] )
+			);
+
+			expect( command.value ).to.be.false;
+		} );
 	} );
 
 	describe( 'execute()', () => {
@@ -164,6 +243,220 @@ describe( 'SetHeaderRowCommand', () => {
 			] ) );
 		} );
 
+		describe( 'multi-cell selection', () => {
+			it( 'should set it correctly in a middle of multi-row table', () => {
+				setData( model, modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				] ) );
+
+				const tableSelection = editor.plugins.get( TableSelection );
+				const modelRoot = model.document.getRoot();
+				tableSelection._setCellSelection(
+					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
+					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
+				);
+
+				command.execute();
+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				], {
+					headingRows: 3
+				} ) );
+
+				assertSelectedCells( model, [
+					[ 0 ],
+					[ 1 ],
+					[ 1 ],
+					[ 0 ]
+				] );
+			} );
+
+			it( 'should set it correctly in a middle of multi-row table - reversed selection', () => {
+				setData( model, modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				] ) );
+
+				const tableSelection = editor.plugins.get( TableSelection );
+				const modelRoot = model.document.getRoot();
+				tableSelection._setCellSelection(
+					modelRoot.getNodeByPath( [ 0, 2, 0 ] ),
+					modelRoot.getNodeByPath( [ 0, 1, 0 ] )
+				);
+
+				command.execute();
+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				], {
+					headingRows: 3
+				} ) );
+
+				assertSelectedCells( model, [
+					[ 0 ],
+					[ 1 ],
+					[ 1 ],
+					[ 0 ]
+				] );
+			} );
+
+			it( 'should set it correctly in a middle of multi-row, multiple cell selection', () => {
+				setData( model, modelTable( [
+					[ '00', '01', '02', '03' ],
+					[ '10', '11', '12', '13' ],
+					[ '20', '21', '22', '23' ],
+					[ '30', '31', '32', '33' ]
+				] ) );
+
+				const tableSelection = editor.plugins.get( TableSelection );
+				const modelRoot = model.document.getRoot();
+				tableSelection._setCellSelection(
+					modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
+					modelRoot.getNodeByPath( [ 0, 2, 2 ] )
+				);
+
+				command.execute();
+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '00', '01', '02', '03' ],
+					[ '10', '11', '12', '13' ],
+					[ '20', '21', '22', '23' ],
+					[ '30', '31', '32', '33' ]
+				], {
+					headingRows: 3
+				} ) );
+
+				assertSelectedCells( model, [
+					[ 0, 0, 0, 0 ],
+					[ 0, 1, 1, 0 ],
+					[ 0, 1, 1, 0 ],
+					[ 0, 0, 0, 0 ]
+				] );
+			} );
+
+			it( 'should remove header rows in case of multiple cell selection', () => {
+				setData( model, modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				], { headingRows: 4 } ) );
+
+				const tableSelection = editor.plugins.get( TableSelection );
+				const modelRoot = model.document.getRoot();
+				tableSelection._setCellSelection(
+					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
+					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
+				);
+
+				command.execute();
+
+				assertEqualMarkup( getData( model, { withoutSelection: true } ), modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				], {
+					headingRows: 1
+				} ) );
+
+				assertSelectedCells( model, [
+					[ 0 ],
+					[ 1 ],
+					[ 1 ],
+					[ 0 ]
+				] );
+			} );
+
+			it( 'should respect forceValue=true in case of multiple row selection', () => {
+				setData( model, modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				], {
+					headingRows: 3
+				} ) );
+
+				const tableSelection = editor.plugins.get( TableSelection );
+				const modelRoot = model.document.getRoot();
+				tableSelection._setCellSelection(
+					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
+					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
+				);
+
+				command.execute( { forceValue: true } );
+
+				assertEqualMarkup( getData( model, {
+					withoutSelection: true
+				} ), modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				], {
+					headingRows: 3
+				} ) );
+
+				assertSelectedCells( model, [
+					[ 0 ],
+					[ 1 ],
+					[ 1 ],
+					[ 0 ]
+				] );
+			} );
+
+			it( 'should respect forceValue=false in case of multiple cell selection', () => {
+				setData( model, modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				], {
+					headingRows: 1
+				} ) );
+
+				const tableSelection = editor.plugins.get( TableSelection );
+				const modelRoot = model.document.getRoot();
+				tableSelection._setCellSelection(
+					modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
+					modelRoot.getNodeByPath( [ 0, 2, 0 ] )
+				);
+
+				command.execute( { forceValue: false } );
+
+				assertEqualMarkup( getData( model, {
+					withoutSelection: true
+				} ), modelTable( [
+					[ '00' ],
+					[ '10' ],
+					[ '20' ],
+					[ '30' ]
+				], {
+					headingRows: 1
+				} ) );
+
+				assertSelectedCells( model, [
+					[ 0 ],
+					[ 1 ],
+					[ 1 ],
+					[ 0 ]
+				] );
+			} );
+		} );
+
 		it( 'should respect forceValue parameter #1', () => {
 			setData( model, modelTable( [
 				[ '00' ],