8
0
Просмотр исходного кода

Re-organize code to anticipate table cropping.

Maciej Gołaszewski 5 лет назад
Родитель
Сommit
ec300ea045
2 измененных файлов с 809 добавлено и 94 удалено
  1. 59 56
      packages/ckeditor5-table/src/tableclipboard.js
  2. 750 38
      packages/ckeditor5-table/tests/tableclipboard.js

+ 59 - 56
packages/ckeditor5-table/src/tableclipboard.js

@@ -131,85 +131,88 @@ export default class TableClipboard extends Plugin {
 				return;
 				return;
 			}
 			}
 
 
-			if ( selectionHeight === insertHeight && selectionWidth === insertWidth ) {
-				const model = this.editor.model;
+			// TODO: Temporally block block tables that are smaller than selection area.
+			if ( selectionHeight > insertHeight || selectionWidth > insertWidth ) {
+				// @if CK_DEBUG // console.log( 'Pasted table is smaller than selection area.' );
 
 
-				model.change( writer => {
-					const insertionMap = new Map();
+				return;
+			}
 
 
-					for ( const { column, row, cell } of new TableWalker( table ) ) {
-						insertionMap.set( `${ row }x${ column }`, cell );
-					}
+			if ( selectionHeight < insertHeight || selectionWidth < insertWidth ) {
+				// @if CK_DEBUG // console.log( 'Pasted table extends selection area.' );
 
 
-					const tableMap = [ ...new TableWalker( contentTable, {
-						startRow: rowIndexes.first,
-						endRow: rowIndexes.last,
-						includeSpanned: true
-					} ) ];
+				return;
+			}
 
 
-					let previousCell;
+			const model = this.editor.model;
 
 
-					const cellsToSelect = [];
+			model.change( writer => {
+				const insertionMap = new Map();
 
 
-					for ( const { column, row, cell, isSpanned } of tableMap ) {
-						// TODO: crete issue for table walker startColumn, endColumn.
-						if ( column < columnIndexes.first || column > columnIndexes.last ) {
-							previousCell = cell;
+				for ( const { column, row, cell } of new TableWalker( table ) ) {
+					insertionMap.set( `${ row }x${ column }`, cell );
+				}
 
 
-							continue;
-						}
+				const tableMap = [ ...new TableWalker( contentTable, {
+					startRow: rowIndexes.first,
+					endRow: rowIndexes.last,
+					includeSpanned: true
+				} ) ];
 
 
-						const toGet = `${ row - rowIndexes.first }x${ column - columnIndexes.first }`;
-						const cellToInsert = insertionMap.get( toGet );
+				let previousCell;
 
 
-						if ( !cellToInsert ) {
-							if ( !isSpanned ) {
-								writer.remove( writer.createRangeOn( cell ) );
-							}
+				const cellsToSelect = [];
 
 
-							continue;
-						}
+				for ( const { column, row, cell, isSpanned } of tableMap ) {
+					// TODO: crete issue for table walker startColumn, endColumn.
+					if ( column < columnIndexes.first || column > columnIndexes.last ) {
+						previousCell = cell;
 
 
-						let targetCell = cell;
+						continue;
+					}
 
 
-						if ( isSpanned ) {
-							let insertPosition;
+					const toGet = `${ row - rowIndexes.first }x${ column - columnIndexes.first }`;
+					const cellToInsert = insertionMap.get( toGet );
 
 
-							if ( column === 0 || !previousCell || previousCell.parent.index !== row ) {
-								insertPosition = writer.createPositionAt( contentTable.getChild( row ), 0 );
-							} else {
-								insertPosition = writer.createPositionAfter( previousCell );
-							}
+					if ( !cellToInsert ) {
+						if ( !isSpanned ) {
+							writer.remove( writer.createRangeOn( cell ) );
+						}
 
 
-							targetCell = writer.createElement( 'tableCell' );
+						continue;
+					}
 
 
-							writer.insert( targetCell, insertPosition );
-						} else {
-							writer.remove( writer.createRangeIn( cell ) );
-						}
+					let targetCell = cell;
 
 
-						updateNumericAttribute( 'colspan', cellToInsert.getAttribute( 'colspan' ), targetCell, writer, 1 );
-						updateNumericAttribute( 'rowspan', cellToInsert.getAttribute( 'rowspan' ), targetCell, writer, 1 );
+					if ( isSpanned ) {
+						let insertPosition;
 
 
-						for ( const child of cellToInsert.getChildren() ) {
-							writer.insert( child, targetCell, 'end' );
+						if ( column === 0 || !previousCell || previousCell.parent.index !== row ) {
+							insertPosition = writer.createPositionAt( contentTable.getChild( row ), 0 );
+						} else {
+							insertPosition = writer.createPositionAfter( previousCell );
 						}
 						}
 
 
-						cellsToSelect.push( targetCell );
-						previousCell = targetCell;
+						targetCell = writer.createElement( 'tableCell' );
+
+						writer.insert( targetCell, insertPosition );
+					} else {
+						writer.remove( writer.createRangeIn( cell ) );
 					}
 					}
 
 
-					writer.setSelection( cellsToSelect.map( cell => writer.createRangeOn( cell ) ) );
-				} );
+					updateNumericAttribute( 'colspan', cellToInsert.getAttribute( 'colspan' ), targetCell, writer, 1 );
+					updateNumericAttribute( 'rowspan', cellToInsert.getAttribute( 'rowspan' ), targetCell, writer, 1 );
 
 
-				return;
-			}
+					for ( const child of cellToInsert.getChildren() ) {
+						writer.insert( child, targetCell, 'end' );
+					}
 
 
-			if ( selectionHeight > insertHeight || selectionWidth > insertWidth ) {
-				// @if CK_DEBUG // console.log( 'Pasted table extends selection area.' );
-			} else {
-				// @if CK_DEBUG // console.log( 'Pasted table is smaller than selection area.' );
-			}
+					cellsToSelect.push( targetCell );
+					previousCell = targetCell;
+				}
+
+				writer.setSelection( cellsToSelect.map( cell => writer.createRangeOn( cell ) ) );
+			} );
 		}
 		}
 	}
 	}
 }
 }

+ 750 - 38
packages/ckeditor5-table/tests/tableclipboard.js

@@ -408,51 +408,51 @@ describe( 'table clipboard', () => {
 				] ) );
 				] ) );
 			} );
 			} );
 
 
-			describe( 'pasted table is equal to the selected area', () => {
-				it( 'should be disabled in a readonly mode', () => {
-					editor.isReadOnly = true;
+			it( 'should be disabled in a readonly mode', () => {
+				editor.isReadOnly = true;
 
 
-					tableSelection.setCellSelection(
-						modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
-						modelRoot.getNodeByPath( [ 0, 1, 1 ] )
-					);
+				tableSelection.setCellSelection(
+					modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+					modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+				);
 
 
-					const data = pasteTable( [
-						[ 'aa', 'ab' ],
-						[ 'ba', 'bb' ]
-					] );
+				const data = pasteTable( [
+					[ 'aa', 'ab' ],
+					[ 'ba', 'bb' ]
+				] );
 
 
-					editor.isReadOnly = false;
+				editor.isReadOnly = false;
 
 
-					assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
-						[ '00', '01', '02', '03' ],
-						[ '10', '11', '12', '13' ],
-						[ '20', '21', '22', '23' ],
-						[ '30', '31', '32', '33' ]
-					] ) );
+				assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+					[ '00', '01', '02', '03' ],
+					[ '10', '11', '12', '13' ],
+					[ '20', '21', '22', '23' ],
+					[ '30', '31', '32', '33' ]
+				] ) );
 
 
-					sinon.assert.calledOnce( data.preventDefault );
-				} );
+				sinon.assert.calledOnce( data.preventDefault );
+			} );
 
 
-				it( 'should allow normal paste if no table cells are selected', () => {
-					const data = {
-						dataTransfer: createDataTransfer(),
-						preventDefault: sinon.spy(),
-						stopPropagation: sinon.spy()
-					};
-					data.dataTransfer.setData( 'text/html', '<p>foo</p>' );
-					viewDocument.fire( 'paste', data );
-
-					editor.isReadOnly = false;
-
-					assertEqualMarkup( getModelData( model ), modelTable( [
-						[ '00foo[]', '01', '02', '03' ],
-						[ '10', '11', '12', '13' ],
-						[ '20', '21', '22', '23' ],
-						[ '30', '31', '32', '33' ]
-					] ) );
-				} );
+			it( 'should allow normal paste if no table cells are selected', () => {
+				const data = {
+					dataTransfer: createDataTransfer(),
+					preventDefault: sinon.spy(),
+					stopPropagation: sinon.spy()
+				};
+				data.dataTransfer.setData( 'text/html', '<p>foo</p>' );
+				viewDocument.fire( 'paste', data );
+
+				editor.isReadOnly = false;
+
+				assertEqualMarkup( getModelData( model ), modelTable( [
+					[ '00foo[]', '01', '02', '03' ],
+					[ '10', '11', '12', '13' ],
+					[ '20', '21', '22', '23' ],
+					[ '30', '31', '32', '33' ]
+				] ) );
+			} );
 
 
+			describe( 'pasted table is equal to the selected area', () => {
 				describe( 'no spans', () => {
 				describe( 'no spans', () => {
 					it( 'handles simple table paste to a simple table fragment - at the beginning of a table', () => {
 					it( 'handles simple table paste to a simple table fragment - at the beginning of a table', () => {
 						tableSelection.setCellSelection(
 						tableSelection.setCellSelection(
@@ -1162,6 +1162,718 @@ describe( 'table clipboard', () => {
 					} );
 					} );
 				} );
 				} );
 			} );
 			} );
+
+			describe( 'pasted table is smaller than the selected area', () => {
+				describe( 'no spans', () => {
+					it( 'handles simple table paste to a simple table fragment - at the beginning of a table', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', 'ab', 'ac' ],
+							[ 'ba', 'bb', 'bc' ],
+							[ 'ca', 'cb', 'cc' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', 'ab', '02', '03' ],
+							[ 'ba', 'bb', '12', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						assertSelectedCells( model, [
+							[ 1, 1, 0, 0 ],
+							[ 1, 1, 0, 0 ],
+							[ 0, 0, 0, 0 ],
+							[ 0, 0, 0, 0 ]
+						] );
+					} );
+
+					it( 'handles simple table paste to a simple table fragment - at the end of a table', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 2, 2 ] ),
+							modelRoot.getNodeByPath( [ 0, 3, 3 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', 'ab' ],
+							[ 'ba', 'bb' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', '11', '12', '13' ],
+							[ '20', '21', 'aa', 'ab' ],
+							[ '30', '31', 'ba', 'bb' ]
+						] ) );
+
+						assertSelectedCells( model, [
+							[ 0, 0, 0, 0 ],
+							[ 0, 0, 0, 0 ],
+							[ 0, 0, 1, 1 ],
+							[ 0, 0, 1, 1 ]
+						] );
+					} );
+
+					it( 'handles simple table paste to a simple table fragment - in the middle of a table', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
+							modelRoot.getNodeByPath( [ 0, 2, 2 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', 'ab' ],
+							[ 'ba', 'bb' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', 'aa', 'ab', '13' ],
+							[ '20', 'ba', 'bb', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						assertSelectedCells( model, [
+							[ 0, 0, 0, 0 ],
+							[ 0, 1, 1, 0 ],
+							[ 0, 1, 1, 0 ],
+							[ 0, 0, 0, 0 ]
+						] );
+					} );
+
+					it( 'handles simple row paste to a simple row fragment - in the middle of a table', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
+							modelRoot.getNodeByPath( [ 0, 1, 2 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', 'ab' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', 'aa', 'ab', '13' ],
+							[ '20', '21', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						assertSelectedCells( model, [
+							[ 0, 0, 0, 0 ],
+							[ 0, 1, 1, 0 ],
+							[ 0, 0, 0, 0 ],
+							[ 0, 0, 0, 0 ]
+						] );
+					} );
+
+					it( 'handles simple column paste to a simple column fragment - in the middle of a table', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
+							modelRoot.getNodeByPath( [ 0, 2, 1 ] )
+						);
+
+						pasteTable( [
+							[ 'aa' ],
+							[ 'ba' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', 'aa', '12', '13' ],
+							[ '20', 'ba', '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						assertSelectedCells( model, [
+							[ 0, 0, 0, 0 ],
+							[ 0, 1, 0, 0 ],
+							[ 0, 1, 0, 0 ],
+							[ 0, 0, 0, 0 ]
+						] );
+					} );
+
+					it( 'handles simple table paste to a simple table fragment - whole table selected', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 3, 3 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', 'ab', 'ac', 'ad' ],
+							[ 'ba', 'bb', 'bc', 'bd' ],
+							[ 'ca', 'cb', 'cc', 'cd' ],
+							[ 'da', 'db', 'dc', 'dd' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', 'ab', 'ac', 'ad' ],
+							[ 'ba', 'bb', 'bc', 'bd' ],
+							[ 'ca', 'cb', 'cc', 'cd' ],
+							[ 'da', 'db', 'dc', 'dd' ]
+						] ) );
+
+						assertSelectedCells( model, [
+							[ 1, 1, 1, 1 ],
+							[ 1, 1, 1, 1 ],
+							[ 1, 1, 1, 1 ],
+							[ 1, 1, 1, 1 ]
+						] );
+					} );
+				} );
+
+				describe.skip( 'pasted table has spans', () => {
+					it( 'handles pasting table that has cell with colspan', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
+							modelRoot.getNodeByPath( [ 0, 2, 2 ] )
+						);
+
+						pasteTable( [
+							[ { colspan: 2, contents: 'aa' } ],
+							[ 'ba', 'bb' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', { colspan: 2, contents: 'aa' }, '13' ],
+							[ '20', 'ba', 'bb', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 0, 0, 0, 0 ],
+							[ 0, 1, 0 ],
+							[ 0, 1, 1, 0 ],
+							[ 0, 0, 0, 0 ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+
+					it( 'handles pasting table that has many cells with various colspan', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 3, 2 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', { colspan: 2, contents: 'ab' } ],
+							[ { colspan: 3, contents: 'ba' } ],
+							[ 'ca', 'cb', 'cc' ],
+							[ { colspan: 2, contents: 'da' }, 'dc' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
+							[ { colspan: 3, contents: 'ba' }, '13' ],
+							[ 'ca', 'cb', 'cc', '23' ],
+							[ { colspan: 2, contents: 'da' }, 'dc', '33' ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 1, 1,    0 ],
+							[ 1,       0 ],
+							[ 1, 1, 1, 0 ],
+							[ 1,    1, 0 ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+
+					it( 'handles pasting table that has cell with rowspan', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 1, 1 ] ),
+							modelRoot.getNodeByPath( [ 0, 2, 2 ] )
+						);
+
+						pasteTable( [
+							[ { rowspan: 2, contents: 'aa' }, 'ab' ],
+							[ 'bb' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', { rowspan: 2, contents: 'aa' }, 'ab', '13' ],
+							[ '20', 'bb', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 0, 0, 0, 0 ],
+							[ 0, 1, 1, 0 ],
+							[ 0,    1, 0 ],
+							[ 0, 0, 0, 0 ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+
+					it( 'handles pasting table that has many cells with various rowspan', () => {
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 2, 3 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
+							[ { rowspan: 2, contents: 'ba' }, 'bd' ],
+							[ 'cc', 'cd' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
+							[ { rowspan: 2, contents: 'ba' }, 'bd' ],
+							[ 'cc', 'cd' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 1, 1, 1, 1 ],
+							[ 1,       1 ],
+							[       1, 1 ],
+							[ 0,    0, 0 ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+
+					it( 'handles pasting multi-spanned table', () => {
+						setModelData( model, modelTable( [
+							[ '00', '01', '02', '03', '04', '05' ],
+							[ '10', '11', '12', '13', '14', '15' ],
+							[ '20', '21', '22', '23', '24', '25' ],
+							[ '30', '31', '32', '33', '34', '35' ],
+							[ '40', '41', '42', '43', '44', '45' ]
+						] ) );
+
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 3, 4 ] )
+						);
+
+						// +----+----+----+----+----+
+						// | aa      | ac | ad | ae |
+						// +----+----+----+----+    +
+						// | ba | bb           |    |
+						// +----+              +----+
+						// | ca |              | ce |
+						// +    +----+----+----+----+
+						// |    | db | dc | dd      |
+						// +----+----+----+----+----+
+						pasteTable( [
+							[ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
+							[ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
+							[ { contents: 'ca', rowspan: 2 }, 'ce' ],
+							[ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
+						] );
+
+						// +----+----+----+----+----+----+
+						// | aa      | ac | ad | ae | 05 |
+						// +----+----+----+----+    +----+
+						// | ba | bb           |    | 15 |
+						// +----+              +----+----+
+						// | ca |              | ce | 25 |
+						// +    +----+----+----+----+----+
+						// |    | db | dc | dd      | 35 |
+						// +----+----+----+----+----+----+
+						// | 40 | 41 | 42 | 43 | 44 | 45 |
+						// +----+----+----+----+----+----+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 }, '05' ],
+							[ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 }, '15' ],
+							[ { contents: 'ca', rowspan: 2 }, 'ce', '25' ],
+							[ 'db', 'dc', { contents: 'dd', colspan: 2 }, '35' ],
+							[ '40', '41', '42', '43', '44', '45' ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 1,    1, 1, 1, 0 ],
+							[ 1, 1,          0 ],
+							[ 1,          1, 0 ],
+							[    1, 1, 1,    0 ],
+							[ 0, 0, 0, 0, 0, 0 ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+				} );
+
+				describe.skip( 'content table has spans', () => {
+					it( 'handles pasting simple table over a table with colspans (no colspan exceeds selection)', () => {
+						setModelData( model, modelTable( [
+							[ '00[]', '01', '02', '03' ],
+							[ { colspan: 3, contents: '10' }, '13' ],
+							[ { colspan: 2, contents: '20' }, '22', '23' ],
+							[ '30', '31', { colspan: 2, contents: '31' } ]
+						] ) );
+
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 2, 1 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', 'ab', 'ac' ],
+							[ 'ba', 'bb', 'bc' ],
+							[ 'ca', 'cb', 'cc' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', 'ab', 'ac', '03' ],
+							[ 'ba', 'bb', 'bc', '13' ],
+							[ 'ca', 'cb', 'cc', '23' ],
+							[ '30', '31', { colspan: 2, contents: '31' } ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 1, 1, 1, 0 ],
+							[ 1, 1, 1, 0 ],
+							[ 1, 1, 1, 0 ],
+							[ 0, 0, 0    ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+
+					it( 'handles pasting simple table over a table with rowspans (no rowspan exceeds selection)', () => {
+						setModelData( model, modelTable( [
+							[ '00', { rowspan: 3, contents: '01' }, { rowspan: 2, contents: '02' }, '03' ],
+							[ { rowspan: 2, contents: '10' }, '13' ],
+							[ '22', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 2, 0 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', 'ab', 'ac' ],
+							[ 'ba', 'bb', 'bc' ],
+							[ 'ca', 'cb', 'cc' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', 'ab', 'ac', '03' ],
+							[ 'ba', 'bb', 'bc', '13' ],
+							[ 'ca', 'cb', 'cc', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						assertSelectedCells( model, [
+							[ 1, 1, 1, 0 ],
+							[ 1, 1, 1, 0 ],
+							[ 1, 1, 1, 0 ],
+							[ 0, 0, 0, 0 ]
+						] );
+					} );
+
+					it( 'handles pasting simple table over table with multi-spans (no span exceeds selection)', () => {
+						// +----+----+----+----+----+----+
+						// | 00      | 02 | 03      | 05 |
+						// +         +    +         +----+
+						// |         |    |         | 15 |
+						// +----+----+----+         +----+
+						// | 20 | 21      |         | 25 |
+						// +    +----+----+----+----+----+
+						// |    | 31 | 32      | 34 | 35 |
+						// +----+----+----+----+----+----+
+						// | 40 | 41 | 42 | 43 | 44 | 45 |
+						// +----+----+----+----+----+----+
+						setModelData( model, modelTable( [
+							[
+								{ contents: '00', colspan: 2, rowspan: 2 },
+								{ contents: '02', rowspan: 2 },
+								{ contents: '03', colspan: 2, rowspan: 3 },
+								'05'
+							],
+							[ '15' ],
+							[ { contents: '20', rowspan: 2 }, { contents: '21', colspan: 2 }, '25' ],
+							[ '31', { contents: '32', colspan: 2 }, '34', '35' ],
+							[ '40', '41', '42', '43', '44', '45' ]
+						] ) );
+
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 3, 2 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', 'ab', 'ac', 'ad', 'ae' ],
+							[ 'ba', 'bb', 'bc', 'bd', 'be' ],
+							[ 'ca', 'cb', 'cc', 'cd', 'ce' ],
+							[ 'da', 'db', 'dc', 'dd', 'de' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', 'ab', 'ac', 'ad', 'ae', '05' ],
+							[ 'ba', 'bb', 'bc', 'bd', 'be', '15' ],
+							[ 'ca', 'cb', 'cc', 'cd', 'ce', '25' ],
+							[ 'da', 'db', 'dc', 'dd', 'de', '35' ],
+							[ '40', '41', '42', '43', '44', '45' ]
+						] ) );
+
+						assertSelectedCells( model, [
+							[ 1, 1, 1, 1, 1, 0 ],
+							[ 1, 1, 1, 1, 1, 0 ],
+							[ 1, 1, 1, 1, 1, 0 ],
+							[ 1, 1, 1, 1, 1, 0 ],
+							[ 0, 0, 0, 0, 0, 0 ]
+						] );
+					} );
+
+					// TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
+					it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
+						// +----+----+----+----+
+						// | 00 | 01 | 02 | 03 |
+						// +----+----+----+----+
+						// | 10 | 11      | 13 |
+						// +    +         +----+
+						// |    |         | 23 |
+						// +----+----+----+----+
+						// | 30 | 31 | 32 | 33 |
+						// +----+----+----+----+
+						setModelData( model, modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
+							[ '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
+							modelRoot.getNodeByPath( [ 0, 1, 0 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', 'ab', 'ac' ],
+							[ 'ba', 'bb', 'bc' ],
+							[ 'ca', 'cb', 'cc' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', 'ab', 'ac', '03' ],
+							[ 'ba', 'bb', 'bc', '13' ],
+							[ 'ca', 'cb', 'cc', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						assertSelectedCells( model, [
+							[ 1, 1, 1, 0 ],
+							[ 1, 1, 1, 0 ],
+							[ 1, 1, 1, 0 ],
+							[ 0, 0, 0, 0 ]
+						] );
+					} );
+				} );
+
+				describe.skip( 'content and paste tables have spans', () => {
+					it( 'handles pasting colspanned table over table with colspans (no colspan exceeds selection)', () => {
+						setModelData( model, modelTable( [
+							[ '00[]', '01', '02', '03' ],
+							[ { colspan: 3, contents: '10' }, '13' ],
+							[ { colspan: 2, contents: '20' }, '22', '23' ],
+							[ '30', '31', { colspan: 2, contents: '31' } ]
+						] ) );
+
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 2, 1 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', { colspan: 2, contents: 'ab' } ],
+							[ { colspan: 3, contents: 'ba' } ],
+							[ 'ca', 'cb', 'cc' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', { colspan: 2, contents: 'ab' }, '03' ],
+							[ { colspan: 3, contents: 'ba' }, '13' ],
+							[ 'ca', 'cb', 'cc', '23' ],
+							[ '30', '31', { colspan: 2, contents: '31' } ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 1, 1,    0 ],
+							[ 1,       0 ],
+							[ 1, 1, 1, 0 ],
+							[ 0, 0, 0    ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+
+					it( 'handles pasting rowspanned table over table with rowspans (no rowspan exceeds selection)', () => {
+						setModelData( model, modelTable( [
+							[ { rowspan: 3, contents: '00' }, { rowspan: 2, contents: '01' }, '02', '03' ],
+							[ { rowspan: 2, contents: '12' }, '13' ],
+							[ '21', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 2, 1 ] )
+						);
+
+						pasteTable( [
+							[ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
+							[ { rowspan: 2, contents: 'ba' }, 'bd' ],
+							[ 'cc', 'cd' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ 'aa', { rowspan: 3, contents: 'ab' }, { rowspan: 2, contents: 'ac' }, 'ad' ],
+							[ { rowspan: 2, contents: 'ba' }, 'bd' ],
+							[ 'cc', 'cd' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 1, 1, 1, 1 ],
+							[ 1,       1 ],
+							[       1, 1 ],
+							[ 0, 0, 0, 0 ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+
+					it( 'handles pasting multi-spanned table over table with multi-spans (no span exceeds selection)', () => {
+						// +----+----+----+----+----+----+
+						// | 00      | 02 | 03      | 05 |
+						// +         +    +         +----+
+						// |         |    |         | 15 |
+						// +----+----+----+         +----+
+						// | 20 | 21      |         | 25 |
+						// +    +----+----+----+----+----+
+						// |    | 31 | 32      | 34 | 35 |
+						// +----+----+----+----+----+----+
+						// | 40 | 41 | 42 | 43 | 44 | 45 |
+						// +----+----+----+----+----+----+
+						setModelData( model, modelTable( [
+							[
+								{ contents: '00', colspan: 2, rowspan: 2 },
+								{ contents: '02', rowspan: 2 },
+								{ contents: '03', colspan: 2, rowspan: 3 },
+								'05'
+							],
+							[ '15' ],
+							[ { contents: '20', rowspan: 2 }, { contents: '21', colspan: 2 }, '25' ],
+							[ '31', { contents: '32', colspan: 2 }, '34', '35' ],
+							[ '40', '41', '42', '43', '44', '45' ]
+						] ) );
+
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+							modelRoot.getNodeByPath( [ 0, 3, 2 ] )
+						);
+
+						// +----+----+----+----+----+
+						// | aa      | ac | ad | ae |
+						// +----+----+----+----+    +
+						// | ba | bb           |    |
+						// +----+              +----+
+						// | ca |              | ce |
+						// +    +----+----+----+----+
+						// |    | db | dc | dd      |
+						// +----+----+----+----+----+
+						pasteTable( [
+							[ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 } ],
+							[ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 } ],
+							[ { contents: 'ca', rowspan: 2 }, 'ce' ],
+							[ 'db', 'dc', { contents: 'dd', colspan: 2 } ]
+						] );
+
+						// +----+----+----+----+----+----+
+						// | aa      | ac | ad | ae | 05 |
+						// +----+----+----+----+    +----+
+						// | ba | bb           |    | 15 |
+						// +----+              +----+----+
+						// | ca |              | ce | 25 |
+						// +    +----+----+----+----+----+
+						// |    | db | dc | dd      | 35 |
+						// +----+----+----+----+----+----+
+						// | 40 | 41 | 42 | 43 | 44 | 45 |
+						// +----+----+----+----+----+----+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ { contents: 'aa', colspan: 2 }, 'ac', 'ad', { contents: 'ae', rowspan: 2 }, '05' ],
+							[ 'ba', { contents: 'bb', colspan: 3, rowspan: 2 }, '15' ],
+							[ { contents: 'ca', rowspan: 2 }, 'ce', '25' ],
+							[ 'db', 'dc', { contents: 'dd', colspan: 2 }, '35' ],
+							[ '40', '41', '42', '43', '44', '45' ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 1,    1, 1, 1, 0 ],
+							[ 1, 1,          0 ],
+							[ 1,          1, 0 ],
+							[    1, 1, 1,    0 ],
+							[ 0, 0, 0, 0, 0, 0 ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+
+					// TODO: Skipped case - should allow pasting but no tools to compare areas (like in MergeCellsCommand).
+					it.skip( 'handles pasting table that has cell with colspan (last row in selection is spanned)', () => {
+						// +----+----+----+----+
+						// | 00 | 01 | 02 | 03 |
+						// +----+----+----+----+
+						// | 10 | 11      | 13 |
+						// +    +         +----+
+						// |    |         | 23 |
+						// +----+----+----+----+
+						// | 30 | 31 | 32 | 33 |
+						// +----+----+----+----+
+						setModelData( model, modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ { contents: '10', rowspan: 2 }, { contents: '11', colspan: 2, rowspan: 2 }, '13' ],
+							[ '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						tableSelection.setCellSelection(
+							modelRoot.getNodeByPath( [ 0, 0, 2 ] ),
+							modelRoot.getNodeByPath( [ 0, 1, 0 ] )
+						);
+
+						// +----+----+----+
+						// | aa | ab | ac |
+						// +----+----+----+
+						// | ba      | bc |
+						// +         +----+
+						// |         | cc |
+						// +----+----+----+
+						pasteTable( [
+							[ 'aa', 'ab', 'ac' ],
+							[ { contents: 'ba', colspan: 2, rowspan: 2 }, 'bc' ],
+							[ 'cc' ]
+						] );
+
+						assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+							[ '00', '01', '02', '03' ],
+							[ '10', { colspan: 2, contents: 'aa' }, '13' ],
+							[ '20', 'ba', 'bb', '23' ],
+							[ '30', '31', '32', '33' ]
+						] ) );
+
+						/* eslint-disable no-multi-spaces */
+						assertSelectedCells( model, [
+							[ 0, 0, 0, 0 ],
+							[ 0, 1,    0 ],
+							[ 0, 1, 1, 0 ],
+							[ 0, 0, 0, 0 ]
+						] );
+						/* eslint-enable no-multi-spaces */
+					} );
+				} );
+			} );
 		} );
 		} );
 	} );
 	} );