mouseselectionhandler.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  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. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  6. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  7. import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  8. import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  9. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  10. import TableEditing from '../../src/tableediting';
  11. import TableSelection from '../../src/tableselection';
  12. import { assertSelectedCells, modelTable, viewTable } from '../_utils/utils';
  13. describe( 'table selection', () => {
  14. let editor, model, view, viewDoc;
  15. beforeEach( async () => {
  16. editor = await VirtualTestEditor.create( {
  17. plugins: [ TableEditing, TableSelection, Paragraph ]
  18. } );
  19. model = editor.model;
  20. view = editor.editing.view;
  21. viewDoc = view.document;
  22. setModelData( model, modelTable( [
  23. [ '11[]', '12', '13' ],
  24. [ '21', '22', '23' ],
  25. [ '31', '32', '33' ]
  26. ] ) );
  27. } );
  28. afterEach( async () => {
  29. await editor.destroy();
  30. } );
  31. describe( 'MouseSelectionHandler', () => {
  32. it( 'should not start table selection when mouse move is inside one table cell', () => {
  33. setModelData( model, modelTable( [
  34. [ '[]00', '01' ],
  35. [ '10', '11' ]
  36. ] ) );
  37. pressMouseButtonOver( getTableCell( '00' ) );
  38. assertEqualMarkup( getModelData( model ), modelTable( [
  39. [ '[]00', '01' ],
  40. [ '10', '11' ]
  41. ] ) );
  42. movePressedMouseOver( getTableCell( '00' ) );
  43. assertEqualMarkup( getModelData( model ), modelTable( [
  44. [ '[]00', '01' ],
  45. [ '10', '11' ]
  46. ] ) );
  47. } );
  48. it( 'should start table selection when mouse move expands over two cells', () => {
  49. setModelData( model, modelTable( [
  50. [ '[]00', '01' ],
  51. [ '10', '11' ]
  52. ] ) );
  53. pressMouseButtonOver( getTableCell( '00' ) );
  54. assertEqualMarkup( getModelData( model ), modelTable( [
  55. [ '[]00', '01' ],
  56. [ '10', '11' ]
  57. ] ) );
  58. movePressedMouseOver( getTableCell( '01' ) );
  59. assertSelectedCells( model, [
  60. [ 1, 1 ],
  61. [ 0, 0 ]
  62. ] );
  63. assertEqualMarkup( getViewData( view ), viewTable( [
  64. [
  65. { contents: '00', class: 'ck-editor__editable_selected', isSelected: true },
  66. { contents: '01', class: 'ck-editor__editable_selected', isSelected: true }
  67. ],
  68. [ '10', '11' ]
  69. ], { asWidget: true } ) );
  70. } );
  71. it( 'should select rectangular table cells when mouse moved to diagonal cell (up -> down)', () => {
  72. setModelData( model, modelTable( [
  73. [ '[]00', '01' ],
  74. [ '10', '11' ]
  75. ] ) );
  76. pressMouseButtonOver( getTableCell( '00' ) );
  77. assertEqualMarkup( getModelData( model ), modelTable( [
  78. [ '[]00', '01' ],
  79. [ '10', '11' ]
  80. ] ) );
  81. movePressedMouseOver( getTableCell( '11' ) );
  82. assertSelectedCells( model, [
  83. [ 1, 1 ],
  84. [ 1, 1 ]
  85. ] );
  86. assertEqualMarkup( getViewData( view ), viewTable( [
  87. [
  88. { contents: '00', class: 'ck-editor__editable_selected', isSelected: true },
  89. { contents: '01', class: 'ck-editor__editable_selected', isSelected: true }
  90. ],
  91. [
  92. { contents: '10', class: 'ck-editor__editable_selected', isSelected: true },
  93. { contents: '11', class: 'ck-editor__editable_selected', isSelected: true }
  94. ]
  95. ], { asWidget: true } ) );
  96. } );
  97. it( 'should select rectangular table cells when mouse moved to diagonal cell (down -> up)', () => {
  98. setModelData( model, modelTable( [
  99. [ '00', '01' ],
  100. [ '10', '[]11' ]
  101. ] ) );
  102. pressMouseButtonOver( getTableCell( '11' ) );
  103. assertEqualMarkup( getModelData( model ), modelTable( [
  104. [ '00', '01' ],
  105. [ '10', '[]11' ]
  106. ] ) );
  107. movePressedMouseOver( getTableCell( '00' ) );
  108. assertSelectedCells( model, [
  109. [ 1, 1 ],
  110. [ 1, 1 ]
  111. ] );
  112. assertEqualMarkup( getViewData( view ), viewTable( [
  113. [
  114. { contents: '00', class: 'ck-editor__editable_selected', isSelected: true },
  115. { contents: '01', class: 'ck-editor__editable_selected', isSelected: true }
  116. ],
  117. [
  118. { contents: '10', class: 'ck-editor__editable_selected', isSelected: true },
  119. { contents: '11', class: 'ck-editor__editable_selected', isSelected: true }
  120. ]
  121. ], { asWidget: true } ) );
  122. } );
  123. it( 'should update view selection after changing selection rect', () => {
  124. setModelData( model, modelTable( [
  125. [ '[]00', '01', '02' ],
  126. [ '10', '11', '12' ],
  127. [ '20', '21', '22' ]
  128. ] ) );
  129. pressMouseButtonOver( getTableCell( '00' ) );
  130. movePressedMouseOver( getTableCell( '22' ) );
  131. assertSelectedCells( model, [
  132. [ 1, 1, 1 ],
  133. [ 1, 1, 1 ],
  134. [ 1, 1, 1 ]
  135. ] );
  136. assertEqualMarkup( getViewData( view ), viewTable( [
  137. [
  138. { contents: '00', class: 'ck-editor__editable_selected', isSelected: true },
  139. { contents: '01', class: 'ck-editor__editable_selected', isSelected: true },
  140. { contents: '02', class: 'ck-editor__editable_selected', isSelected: true }
  141. ],
  142. [
  143. { contents: '10', class: 'ck-editor__editable_selected', isSelected: true },
  144. { contents: '11', class: 'ck-editor__editable_selected', isSelected: true },
  145. { contents: '12', class: 'ck-editor__editable_selected', isSelected: true }
  146. ],
  147. [
  148. { contents: '20', class: 'ck-editor__editable_selected', isSelected: true },
  149. { contents: '21', class: 'ck-editor__editable_selected', isSelected: true },
  150. { contents: '22', class: 'ck-editor__editable_selected', isSelected: true }
  151. ]
  152. ], { asWidget: true } ) );
  153. movePressedMouseOver( getTableCell( '11' ) );
  154. assertSelectedCells( model, [
  155. [ 1, 1, 0 ],
  156. [ 1, 1, 0 ],
  157. [ 0, 0, 0 ]
  158. ] );
  159. assertEqualMarkup( getViewData( view ), viewTable( [
  160. [
  161. { contents: '00', class: 'ck-editor__editable_selected', isSelected: true },
  162. { contents: '01', class: 'ck-editor__editable_selected', isSelected: true },
  163. '02'
  164. ],
  165. [
  166. { contents: '10', class: 'ck-editor__editable_selected', isSelected: true },
  167. { contents: '11', class: 'ck-editor__editable_selected', isSelected: true },
  168. '12'
  169. ],
  170. [
  171. '20',
  172. '21',
  173. '22'
  174. ]
  175. ], { asWidget: true } ) );
  176. } );
  177. it( 'should stop selecting after "mouseup" event', () => {
  178. setModelData( model, modelTable( [
  179. [ '[]00', '01' ],
  180. [ '10', '11' ]
  181. ] ) );
  182. pressMouseButtonOver( getTableCell( '00' ) );
  183. assertEqualMarkup( getModelData( model ), modelTable( [
  184. [ '[]00', '01' ],
  185. [ '10', '11' ]
  186. ] ) );
  187. movePressedMouseOver( getTableCell( '01' ) );
  188. releaseMouseButtonOver( getTableCell( '01' ) );
  189. assertSelectedCells( model, [
  190. [ 1, 1 ],
  191. [ 0, 0 ]
  192. ] );
  193. assertEqualMarkup( getViewData( view ), viewTable( [
  194. [
  195. { contents: '00', class: 'ck-editor__editable_selected', isSelected: true },
  196. { contents: '01', class: 'ck-editor__editable_selected', isSelected: true }
  197. ],
  198. [ '10', '11' ]
  199. ], { asWidget: true } ) );
  200. moveReleasedMouseOver( getTableCell( '11' ) );
  201. assertSelectedCells( model, [
  202. [ 1, 1 ],
  203. [ 0, 0 ]
  204. ] );
  205. } );
  206. it( 'should do nothing on "mouseup" event', () => {
  207. setModelData( model, modelTable( [
  208. [ '[]00', '01' ],
  209. [ '10', '11' ]
  210. ] ) );
  211. releaseMouseButtonOver( getTableCell( '01' ) );
  212. assertSelectedCells( model, [
  213. [ 0, 0 ],
  214. [ 0, 0 ]
  215. ] );
  216. } );
  217. it( 'should stop selection mode on "mouseleve" event if next "mousemove" has no button pressed', () => {
  218. setModelData( model, modelTable( [
  219. [ '[]00', '01' ],
  220. [ '10', '11' ]
  221. ] ) );
  222. pressMouseButtonOver( getTableCell( '00' ) );
  223. assertEqualMarkup( getModelData( model ), modelTable( [
  224. [ '[]00', '01' ],
  225. [ '10', '11' ]
  226. ] ) );
  227. movePressedMouseOver( getTableCell( '01' ) );
  228. makeMouseLeave();
  229. assertSelectedCells( model, [
  230. [ 1, 1 ],
  231. [ 0, 0 ]
  232. ] );
  233. assertEqualMarkup( getViewData( view ), viewTable( [
  234. [
  235. { contents: '00', class: 'ck-editor__editable_selected', isSelected: true },
  236. { contents: '01', class: 'ck-editor__editable_selected', isSelected: true }
  237. ],
  238. [ '10', '11' ]
  239. ], { asWidget: true } ) );
  240. moveReleasedMouseOver( getTableCell( '11' ) );
  241. assertSelectedCells( model, [
  242. [ 1, 1 ],
  243. [ 0, 0 ]
  244. ] );
  245. } );
  246. it( 'should continue selection mode on "mouseleve" and "mousemove" if mouse button is pressed', () => {
  247. setModelData( model, modelTable( [
  248. [ '[]00', '01' ],
  249. [ '10', '11' ]
  250. ] ) );
  251. pressMouseButtonOver( getTableCell( '00' ) );
  252. assertEqualMarkup( getModelData( model ), modelTable( [
  253. [ '[]00', '01' ],
  254. [ '10', '11' ]
  255. ] ) );
  256. movePressedMouseOver( getTableCell( '01' ) );
  257. makeMouseLeave();
  258. assertSelectedCells( model, [
  259. [ 1, 1 ],
  260. [ 0, 0 ]
  261. ] );
  262. assertEqualMarkup( getViewData( view ), viewTable( [
  263. [
  264. { contents: '00', class: 'ck-editor__editable_selected', isSelected: true },
  265. { contents: '01', class: 'ck-editor__editable_selected', isSelected: true }
  266. ],
  267. [ '10', '11' ]
  268. ], { asWidget: true } ) );
  269. movePressedMouseOver( getTableCell( '11' ) );
  270. assertSelectedCells( model, [
  271. [ 1, 1 ],
  272. [ 1, 1 ]
  273. ] );
  274. } );
  275. it( 'should do nothing on "mouseleve" event', () => {
  276. setModelData( model, modelTable( [
  277. [ '[]00', '01' ],
  278. [ '10', '11' ]
  279. ] ) );
  280. makeMouseLeave();
  281. assertSelectedCells( model, [
  282. [ 0, 0 ],
  283. [ 0, 0 ]
  284. ] );
  285. } );
  286. it( 'should do nothing on "mousedown" event over ui element (click on selection handle)', () => {
  287. setModelData( model, modelTable( [
  288. [ '[]00', '01' ],
  289. [ '10', '11' ]
  290. ] ) );
  291. const uiElement = viewDoc.getRoot()
  292. .getChild( 0 )
  293. .getChild( 0 ); // selection handler;
  294. fireEvent( view, 'mousedown', addTarget( uiElement ), mouseButtonPressed );
  295. assertEqualMarkup( getModelData( model ), modelTable( [
  296. [ '[]00', '01' ],
  297. [ '10', '11' ]
  298. ] ) );
  299. } );
  300. it( 'should do nothing on "mousemove" event over ui element (click on selection handle)', () => {
  301. setModelData( model, modelTable( [
  302. [ '[]00', '01' ],
  303. [ '10', '11' ]
  304. ] ) );
  305. const uiElement = viewDoc.getRoot()
  306. .getChild( 0 )
  307. .getChild( 0 ); // selection handler;
  308. fireEvent( view, 'mousemove', addTarget( uiElement ), mouseButtonPressed );
  309. assertEqualMarkup( getModelData( model ), modelTable( [
  310. [ '[]00', '01' ],
  311. [ '10', '11' ]
  312. ] ) );
  313. } );
  314. it( 'should clear view table selection after mouse click outside table', () => {
  315. setModelData( model, modelTable( [
  316. [ '[]00', '01' ],
  317. [ '10', '11' ]
  318. ] ) + '<paragraph>foo</paragraph>' );
  319. pressMouseButtonOver( getTableCell( '00' ) );
  320. assertEqualMarkup( getModelData( model ), modelTable( [
  321. [ '[]00', '01' ],
  322. [ '10', '11' ]
  323. ] ) + '<paragraph>foo</paragraph>' );
  324. movePressedMouseOver( getTableCell( '01' ) );
  325. const paragraph = viewDoc.getRoot().getChild( 1 );
  326. fireEvent( view, 'mousemove', addTarget( paragraph ) );
  327. fireEvent( view, 'mousedown', addTarget( paragraph ) );
  328. fireEvent( view, 'mouseup', addTarget( paragraph ) );
  329. // The click in the DOM would trigger selection change and it will set the selection:
  330. model.change( writer => {
  331. writer.setSelection( writer.createRange( writer.createPositionAt( model.document.getRoot().getChild( 1 ), 0 ) ) );
  332. } );
  333. assertEqualMarkup( getViewData( view ), viewTable( [
  334. [ '00', '01' ],
  335. [ '10', '11' ]
  336. ], { asWidget: true } ) + '<p>{}foo</p>' );
  337. } );
  338. } );
  339. function getTableCell( data ) {
  340. for ( const value of view.createRangeIn( viewDoc.getRoot() ) ) {
  341. if ( value.type === 'text' && value.item.data === data ) {
  342. return value.item.parent.parent;
  343. }
  344. }
  345. }
  346. function makeMouseLeave() {
  347. fireEvent( view, 'mouseleave' );
  348. }
  349. function pressMouseButtonOver( target ) {
  350. fireEvent( view, 'mousedown', addTarget( target ), mouseButtonPressed );
  351. }
  352. function movePressedMouseOver( target ) {
  353. moveMouseOver( target, mouseButtonPressed );
  354. }
  355. function moveReleasedMouseOver( target ) {
  356. moveMouseOver( target, mouseButtonReleased );
  357. }
  358. function moveMouseOver( target, ...decorators ) {
  359. fireEvent( view, 'mousemove', addTarget( target ), ...decorators );
  360. }
  361. function releaseMouseButtonOver( target ) {
  362. fireEvent( view, 'mouseup', addTarget( target ), mouseButtonReleased );
  363. }
  364. function addTarget( target ) {
  365. return domEventData => {
  366. domEventData.target = target;
  367. };
  368. }
  369. function mouseButtonPressed( domEventData ) {
  370. domEventData.domEvent.buttons = 1;
  371. }
  372. function mouseButtonReleased( domEventData ) {
  373. domEventData.domEvent.buttons = 0;
  374. }
  375. function fireEvent( view, eventName, ...decorators ) {
  376. const domEvtDataStub = {
  377. domEvent: {
  378. buttons: 0
  379. },
  380. target: undefined,
  381. preventDefault: sinon.spy(),
  382. stopPropagation: sinon.spy()
  383. };
  384. for ( const decorator of decorators ) {
  385. decorator( domEvtDataStub );
  386. }
  387. viewDoc.fire( eventName, domEvtDataStub );
  388. }
  389. } );