8
0

mouseselectionhandler.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  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. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  65. [ '10', '11' ]
  66. ], { asWidget: true } ) );
  67. } );
  68. it( 'should select rectangular table cells when mouse moved to diagonal cell (up -> down)', () => {
  69. setModelData( model, modelTable( [
  70. [ '[]00', '01' ],
  71. [ '10', '11' ]
  72. ] ) );
  73. pressMouseButtonOver( getTableCell( '00' ) );
  74. assertEqualMarkup( getModelData( model ), modelTable( [
  75. [ '[]00', '01' ],
  76. [ '10', '11' ]
  77. ] ) );
  78. movePressedMouseOver( getTableCell( '11' ) );
  79. assertSelectedCells( model, [
  80. [ 1, 1 ],
  81. [ 1, 1 ]
  82. ] );
  83. assertEqualMarkup( getViewData( view ), viewTable( [
  84. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  85. [ { contents: '10', class: 'selected', isSelected: true }, { contents: '11', class: 'selected', isSelected: true } ]
  86. ], { asWidget: true } ) );
  87. } );
  88. it( 'should select rectangular table cells when mouse moved to diagonal cell (down -> up)', () => {
  89. setModelData( model, modelTable( [
  90. [ '00', '01' ],
  91. [ '10', '[]11' ]
  92. ] ) );
  93. pressMouseButtonOver( getTableCell( '11' ) );
  94. assertEqualMarkup( getModelData( model ), modelTable( [
  95. [ '00', '01' ],
  96. [ '10', '[]11' ]
  97. ] ) );
  98. movePressedMouseOver( getTableCell( '00' ) );
  99. assertSelectedCells( model, [
  100. [ 1, 1 ],
  101. [ 1, 1 ]
  102. ] );
  103. assertEqualMarkup( getViewData( view ), viewTable( [
  104. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  105. [ { contents: '10', class: 'selected', isSelected: true }, { contents: '11', class: 'selected', isSelected: true } ]
  106. ], { asWidget: true } ) );
  107. } );
  108. it( 'should update view selection after changing selection rect', () => {
  109. setModelData( model, modelTable( [
  110. [ '[]00', '01', '02' ],
  111. [ '10', '11', '12' ],
  112. [ '20', '21', '22' ]
  113. ] ) );
  114. pressMouseButtonOver( getTableCell( '00' ) );
  115. movePressedMouseOver( getTableCell( '22' ) );
  116. assertSelectedCells( model, [
  117. [ 1, 1, 1 ],
  118. [ 1, 1, 1 ],
  119. [ 1, 1, 1 ]
  120. ] );
  121. assertEqualMarkup( getViewData( view ), viewTable( [
  122. [
  123. { contents: '00', class: 'selected', isSelected: true },
  124. { contents: '01', class: 'selected', isSelected: true },
  125. { contents: '02', class: 'selected', isSelected: true }
  126. ],
  127. [
  128. { contents: '10', class: 'selected', isSelected: true },
  129. { contents: '11', class: 'selected', isSelected: true },
  130. { contents: '12', class: 'selected', isSelected: true }
  131. ],
  132. [
  133. { contents: '20', class: 'selected', isSelected: true },
  134. { contents: '21', class: 'selected', isSelected: true },
  135. { contents: '22', class: 'selected', isSelected: true }
  136. ]
  137. ], { asWidget: true } ) );
  138. movePressedMouseOver( getTableCell( '11' ) );
  139. assertSelectedCells( model, [
  140. [ 1, 1, 0 ],
  141. [ 1, 1, 0 ],
  142. [ 0, 0, 0 ]
  143. ] );
  144. assertEqualMarkup( getViewData( view ), viewTable( [
  145. [
  146. { contents: '00', class: 'selected', isSelected: true },
  147. { contents: '01', class: 'selected', isSelected: true },
  148. '02'
  149. ],
  150. [
  151. { contents: '10', class: 'selected', isSelected: true },
  152. { contents: '11', class: 'selected', isSelected: true },
  153. '12'
  154. ],
  155. [
  156. '20',
  157. '21',
  158. '22'
  159. ]
  160. ], { asWidget: true } ) );
  161. } );
  162. it( 'should stop selecting after "mouseup" event', () => {
  163. setModelData( model, modelTable( [
  164. [ '[]00', '01' ],
  165. [ '10', '11' ]
  166. ] ) );
  167. pressMouseButtonOver( getTableCell( '00' ) );
  168. assertEqualMarkup( getModelData( model ), modelTable( [
  169. [ '[]00', '01' ],
  170. [ '10', '11' ]
  171. ] ) );
  172. movePressedMouseOver( getTableCell( '01' ) );
  173. releaseMouseButtonOver( getTableCell( '01' ) );
  174. assertSelectedCells( model, [
  175. [ 1, 1 ],
  176. [ 0, 0 ]
  177. ] );
  178. assertEqualMarkup( getViewData( view ), viewTable( [
  179. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  180. [ '10', '11' ]
  181. ], { asWidget: true } ) );
  182. moveReleasedMouseOver( getTableCell( '11' ) );
  183. assertSelectedCells( model, [
  184. [ 1, 1 ],
  185. [ 0, 0 ]
  186. ] );
  187. } );
  188. it( 'should do nothing on "mouseup" event', () => {
  189. setModelData( model, modelTable( [
  190. [ '[]00', '01' ],
  191. [ '10', '11' ]
  192. ] ) );
  193. releaseMouseButtonOver( getTableCell( '01' ) );
  194. assertSelectedCells( model, [
  195. [ 0, 0 ],
  196. [ 0, 0 ]
  197. ] );
  198. } );
  199. it( 'should stop selection mode on "mouseleve" event if next "mousemove" has no button pressed', () => {
  200. setModelData( model, modelTable( [
  201. [ '[]00', '01' ],
  202. [ '10', '11' ]
  203. ] ) );
  204. pressMouseButtonOver( getTableCell( '00' ) );
  205. assertEqualMarkup( getModelData( model ), modelTable( [
  206. [ '[]00', '01' ],
  207. [ '10', '11' ]
  208. ] ) );
  209. movePressedMouseOver( getTableCell( '01' ) );
  210. makeMouseLeave();
  211. assertSelectedCells( model, [
  212. [ 1, 1 ],
  213. [ 0, 0 ]
  214. ] );
  215. assertEqualMarkup( getViewData( view ), viewTable( [
  216. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  217. [ '10', '11' ]
  218. ], { asWidget: true } ) );
  219. moveReleasedMouseOver( getTableCell( '11' ) );
  220. assertSelectedCells( model, [
  221. [ 1, 1 ],
  222. [ 0, 0 ]
  223. ] );
  224. } );
  225. it( 'should continue selection mode on "mouseleve" and "mousemove" if mouse button is pressed', () => {
  226. setModelData( model, modelTable( [
  227. [ '[]00', '01' ],
  228. [ '10', '11' ]
  229. ] ) );
  230. pressMouseButtonOver( getTableCell( '00' ) );
  231. assertEqualMarkup( getModelData( model ), modelTable( [
  232. [ '[]00', '01' ],
  233. [ '10', '11' ]
  234. ] ) );
  235. movePressedMouseOver( getTableCell( '01' ) );
  236. makeMouseLeave();
  237. assertSelectedCells( model, [
  238. [ 1, 1 ],
  239. [ 0, 0 ]
  240. ] );
  241. assertEqualMarkup( getViewData( view ), viewTable( [
  242. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  243. [ '10', '11' ]
  244. ], { asWidget: true } ) );
  245. movePressedMouseOver( getTableCell( '11' ) );
  246. assertSelectedCells( model, [
  247. [ 1, 1 ],
  248. [ 1, 1 ]
  249. ] );
  250. } );
  251. it( 'should do nothing on "mouseleve" event', () => {
  252. setModelData( model, modelTable( [
  253. [ '[]00', '01' ],
  254. [ '10', '11' ]
  255. ] ) );
  256. makeMouseLeave();
  257. assertSelectedCells( model, [
  258. [ 0, 0 ],
  259. [ 0, 0 ]
  260. ] );
  261. } );
  262. it( 'should do nothing on "mousedown" event over ui element (click on selection handle)', () => {
  263. setModelData( model, modelTable( [
  264. [ '[]00', '01' ],
  265. [ '10', '11' ]
  266. ] ) );
  267. const uiElement = viewDoc.getRoot()
  268. .getChild( 0 )
  269. .getChild( 0 ); // selection handler;
  270. fireEvent( view, 'mousedown', addTarget( uiElement ), mouseButtonPressed );
  271. assertEqualMarkup( getModelData( model ), modelTable( [
  272. [ '[]00', '01' ],
  273. [ '10', '11' ]
  274. ] ) );
  275. } );
  276. it( 'should do nothing on "mousemove" event over ui element (click on selection handle)', () => {
  277. setModelData( model, modelTable( [
  278. [ '[]00', '01' ],
  279. [ '10', '11' ]
  280. ] ) );
  281. const uiElement = viewDoc.getRoot()
  282. .getChild( 0 )
  283. .getChild( 0 ); // selection handler;
  284. fireEvent( view, 'mousemove', addTarget( uiElement ), mouseButtonPressed );
  285. assertEqualMarkup( getModelData( model ), modelTable( [
  286. [ '[]00', '01' ],
  287. [ '10', '11' ]
  288. ] ) );
  289. } );
  290. it( 'should clear view table selection after mouse click outside table', () => {
  291. setModelData( model, modelTable( [
  292. [ '[]00', '01' ],
  293. [ '10', '11' ]
  294. ] ) + '<paragraph>foo</paragraph>' );
  295. pressMouseButtonOver( getTableCell( '00' ) );
  296. assertEqualMarkup( getModelData( model ), modelTable( [
  297. [ '[]00', '01' ],
  298. [ '10', '11' ]
  299. ] ) + '<paragraph>foo</paragraph>' );
  300. movePressedMouseOver( getTableCell( '01' ) );
  301. const paragraph = viewDoc.getRoot().getChild( 1 );
  302. fireEvent( view, 'mousemove', addTarget( paragraph ) );
  303. fireEvent( view, 'mousedown', addTarget( paragraph ) );
  304. fireEvent( view, 'mouseup', addTarget( paragraph ) );
  305. // The click in the DOM would trigger selection change and it will set the selection:
  306. model.change( writer => {
  307. writer.setSelection( writer.createRange( writer.createPositionAt( model.document.getRoot().getChild( 1 ), 0 ) ) );
  308. } );
  309. assertEqualMarkup( getViewData( view ), viewTable( [
  310. [ '00', '01' ],
  311. [ '10', '11' ]
  312. ], { asWidget: true } ) + '<p>{}foo</p>' );
  313. } );
  314. } );
  315. function getTableCell( data ) {
  316. for ( const value of view.createRangeIn( viewDoc.getRoot() ) ) {
  317. if ( value.type === 'text' && value.item.data === data ) {
  318. return value.item.parent.parent;
  319. }
  320. }
  321. }
  322. function makeMouseLeave() {
  323. fireEvent( view, 'mouseleave' );
  324. }
  325. function pressMouseButtonOver( target ) {
  326. fireEvent( view, 'mousedown', addTarget( target ), mouseButtonPressed );
  327. }
  328. function movePressedMouseOver( target ) {
  329. moveMouseOver( target, mouseButtonPressed );
  330. }
  331. function moveReleasedMouseOver( target ) {
  332. moveMouseOver( target, mouseButtonReleased );
  333. }
  334. function moveMouseOver( target, ...decorators ) {
  335. fireEvent( view, 'mousemove', addTarget( target ), ...decorators );
  336. }
  337. function releaseMouseButtonOver( target ) {
  338. fireEvent( view, 'mouseup', addTarget( target ), mouseButtonReleased );
  339. }
  340. function addTarget( target ) {
  341. return domEventData => {
  342. domEventData.target = target;
  343. };
  344. }
  345. function mouseButtonPressed( domEventData ) {
  346. domEventData.domEvent.buttons = 1;
  347. }
  348. function mouseButtonReleased( domEventData ) {
  349. domEventData.domEvent.buttons = 0;
  350. }
  351. function fireEvent( view, eventName, ...decorators ) {
  352. const domEvtDataStub = {
  353. domEvent: {
  354. buttons: 0
  355. },
  356. target: undefined,
  357. preventDefault: sinon.spy(),
  358. stopPropagation: sinon.spy()
  359. };
  360. for ( const decorator of decorators ) {
  361. decorator( domEvtDataStub );
  362. }
  363. viewDoc.fire( eventName, domEvtDataStub );
  364. }
  365. } );