tableselection-integration.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /* globals document */
  6. import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  7. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  8. import Delete from '@ckeditor/ckeditor5-typing/src/delete';
  9. import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
  10. import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline';
  11. import TableEditing from '../src/tableediting';
  12. import TableSelection from '../src/tableselection';
  13. import TableClipboard from '../src/tableclipboard';
  14. import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  15. import { assertSelectedCells, modelTable } from './_utils/utils';
  16. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  17. import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
  18. import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
  19. import Input from '@ckeditor/ckeditor5-typing/src/input';
  20. import ViewText from '@ckeditor/ckeditor5-engine/src/view/text';
  21. import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
  22. describe( 'TableSelection - integration', () => {
  23. let editor, model, tableSelection, modelRoot, element, viewDocument;
  24. afterEach( async () => {
  25. element.remove();
  26. await editor.destroy();
  27. } );
  28. describe( 'on delete', () => {
  29. beforeEach( async () => {
  30. await setupEditor( [ Delete ] );
  31. } );
  32. it( 'should clear contents of the selected table cells and put selection in last cell on backward delete', () => {
  33. tableSelection.setCellSelection(
  34. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  35. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  36. );
  37. const domEventData = new DomEventData( viewDocument, {
  38. preventDefault: sinon.spy()
  39. }, {
  40. direction: 'backward',
  41. unit: 'character',
  42. sequence: 1
  43. } );
  44. viewDocument.fire( 'delete', domEventData );
  45. assertEqualMarkup( getModelData( model ), modelTable( [
  46. [ '', '', '13' ],
  47. [ '', '[]', '23' ],
  48. [ '31', '32', '33' ]
  49. ] ) );
  50. } );
  51. it( 'should clear contents of the selected table cells and put selection in last cell on forward delete', () => {
  52. tableSelection.setCellSelection(
  53. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  54. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  55. );
  56. const domEventData = new DomEventData( viewDocument, {
  57. preventDefault: sinon.spy()
  58. }, {
  59. direction: 'forward',
  60. unit: 'character',
  61. sequence: 1
  62. } );
  63. viewDocument.fire( 'delete', domEventData );
  64. assertEqualMarkup( getModelData( model ), modelTable( [
  65. [ '[]', '', '13' ],
  66. [ '', '', '23' ],
  67. [ '31', '32', '33' ]
  68. ] ) );
  69. } );
  70. it( 'should not interfere with default key handler if no table selection', () => {
  71. setModelData( model, modelTable( [
  72. [ '11[]', '12', '13' ],
  73. [ '21', '22', '23' ],
  74. [ '31', '32', '33' ]
  75. ] ) );
  76. const domEventData = new DomEventData( viewDocument, {
  77. preventDefault: sinon.spy()
  78. }, {
  79. direction: 'backward',
  80. unit: 'character',
  81. sequence: 1
  82. } );
  83. viewDocument.fire( 'delete', domEventData );
  84. assertEqualMarkup( getModelData( model ), modelTable( [
  85. [ '1[]', '12', '13' ],
  86. [ '21', '22', '23' ],
  87. [ '31', '32', '33' ]
  88. ] ) );
  89. } );
  90. } );
  91. describe( 'on user input', () => {
  92. beforeEach( async () => {
  93. await setupEditor( [ Input ] );
  94. } );
  95. it( 'should clear contents of the selected table cells and put selection in last cell on user input', () => {
  96. tableSelection.setCellSelection(
  97. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  98. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  99. );
  100. viewDocument.fire( 'keydown', { keyCode: getCode( 'x' ) } );
  101. // Mutate at the place where the document selection was put; it's more realistic
  102. // than mutating at some arbitrary position.
  103. const placeOfMutation = viewDocument.selection.getFirstRange().start.parent;
  104. viewDocument.fire( 'mutations', [
  105. {
  106. type: 'children',
  107. oldChildren: [],
  108. newChildren: [ new ViewText( viewDocument, 'x' ) ],
  109. node: placeOfMutation
  110. }
  111. ] );
  112. assertEqualMarkup( getModelData( model ), modelTable( [
  113. [ '', '', '13' ],
  114. [ '', 'x[]', '23' ],
  115. [ '31', '32', '33' ]
  116. ] ) );
  117. } );
  118. it( 'should not interfere with default key handler if no table selection', () => {
  119. viewDocument.fire( 'keydown', { keyCode: getCode( 'x' ) } );
  120. // Mutate at the place where the document selection was put; it's more realistic
  121. // than mutating at some arbitrary position.
  122. const placeOfMutation = viewDocument.selection.getFirstRange().start.parent;
  123. viewDocument.fire( 'mutations', [
  124. {
  125. type: 'children',
  126. oldChildren: [],
  127. newChildren: [ new ViewText( viewDocument, 'x' ) ],
  128. node: placeOfMutation
  129. }
  130. ] );
  131. assertEqualMarkup( getModelData( model ), modelTable( [
  132. [ 'x[]11', '12', '13' ],
  133. [ '21', '22', '23' ],
  134. [ '31', '32', '33' ]
  135. ] ) );
  136. } );
  137. } );
  138. describe( 'with other features', () => {
  139. beforeEach( async () => {
  140. await setupEditor( [ Clipboard, HorizontalLine ] );
  141. } );
  142. it( 'allows pasting over multi-cell selection', () => {
  143. tableSelection.setCellSelection(
  144. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  145. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  146. );
  147. const dataTransferMock = {
  148. getData: sinon.stub().withArgs( 'text/plain' ).returns( 'foo' )
  149. };
  150. editor.editing.view.document.fire( 'clipboardInput', {
  151. dataTransfer: dataTransferMock,
  152. stop: sinon.spy()
  153. } );
  154. assertEqualMarkup( getModelData( model ), modelTable( [
  155. [ 'foo[]', '', '13' ],
  156. [ '', '', '23' ],
  157. [ '31', '32', '33' ]
  158. ] ) );
  159. } );
  160. it( 'allows inserting a horizontal line over a multi-range selection', () => {
  161. tableSelection.setCellSelection(
  162. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  163. modelRoot.getNodeByPath( [ 0, 1, 1 ] )
  164. );
  165. editor.execute( 'horizontalLine' );
  166. assertEqualMarkup(
  167. getModelData( model ),
  168. '<table>' +
  169. '<tableRow>' +
  170. '<tableCell><horizontalLine></horizontalLine><paragraph>[]</paragraph></tableCell>' +
  171. '<tableCell><paragraph></paragraph></tableCell>' +
  172. '<tableCell><paragraph>13</paragraph></tableCell>' +
  173. '</tableRow>' +
  174. '<tableRow>' +
  175. '<tableCell><paragraph></paragraph></tableCell>' +
  176. '<tableCell><paragraph></paragraph></tableCell>' +
  177. '<tableCell><paragraph>23</paragraph></tableCell>' +
  178. '</tableRow>' +
  179. '<tableRow>' +
  180. '<tableCell><paragraph>31</paragraph></tableCell>' +
  181. '<tableCell><paragraph>32</paragraph></tableCell>' +
  182. '<tableCell><paragraph>33</paragraph></tableCell>' +
  183. '</tableRow>' +
  184. '</table>'
  185. );
  186. } );
  187. // https://github.com/ckeditor/ckeditor5/issues/7659.
  188. // The fix is in the `DocumentSelection` class but this test is here to make sure that the fix works
  189. // and that the behavior won't change in the future.
  190. it( 'should not fix selection if not all ranges were removed', () => {
  191. // [ ][ ][ ]
  192. // [x][x][ ]
  193. // [x][x][ ]
  194. tableSelection.setCellSelection(
  195. modelRoot.getNodeByPath( [ 0, 1, 0 ] ),
  196. modelRoot.getNodeByPath( [ 0, 2, 1 ] )
  197. );
  198. editor.model.change( writer => {
  199. // Remove second row.
  200. writer.remove( modelRoot.getNodeByPath( [ 0, 1 ] ) );
  201. } );
  202. assertEqualMarkup(
  203. getModelData( model ),
  204. '<table>' +
  205. '<tableRow>' +
  206. '<tableCell><paragraph>11</paragraph></tableCell>' +
  207. '<tableCell><paragraph>12</paragraph></tableCell>' +
  208. '<tableCell><paragraph>13</paragraph></tableCell>' +
  209. '</tableRow>' +
  210. '<tableRow>' +
  211. '[<tableCell><paragraph>31</paragraph></tableCell>]' +
  212. '[<tableCell><paragraph>32</paragraph></tableCell>]' +
  213. '<tableCell><paragraph>33</paragraph></tableCell>' +
  214. '</tableRow>' +
  215. '</table>'
  216. );
  217. } );
  218. } );
  219. describe( 'with undo', () => {
  220. beforeEach( async () => {
  221. await setupEditor( [ UndoEditing ] );
  222. } );
  223. it( 'works with merge cells command', () => {
  224. setModelData( editor.model, modelTable( [
  225. [ '00', '01' ],
  226. [ '10', '11' ]
  227. ] ) );
  228. tableSelection.setCellSelection(
  229. modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
  230. modelRoot.getNodeByPath( [ 0, 0, 1 ] )
  231. );
  232. editor.execute( 'mergeTableCells' );
  233. assertEqualMarkup( getModelData( model ), modelTable( [
  234. [ { colspan: 2, contents: '<paragraph>[00</paragraph><paragraph>01]</paragraph>' } ],
  235. [ '10', '11' ]
  236. ] ) );
  237. editor.execute( 'undo' );
  238. assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
  239. [ '00', '01' ],
  240. [ '10', '11' ]
  241. ] ) );
  242. assertSelectedCells( model, [
  243. [ 1, 1 ],
  244. [ 0, 0 ]
  245. ] );
  246. } );
  247. } );
  248. async function setupEditor( plugins ) {
  249. element = document.createElement( 'div' );
  250. document.body.appendChild( element );
  251. editor = await ClassicTestEditor.create( element, {
  252. plugins: [ TableEditing, TableSelection, TableClipboard, Paragraph, ...plugins ]
  253. } );
  254. model = editor.model;
  255. modelRoot = model.document.getRoot();
  256. viewDocument = editor.editing.view.document;
  257. tableSelection = editor.plugins.get( TableSelection );
  258. setModelData( model, modelTable( [
  259. [ '[]11', '12', '13' ],
  260. [ '21', '22', '23' ],
  261. [ '31', '32', '33' ]
  262. ] ) );
  263. }
  264. } );