tableselection.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  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 { modelTable, viewTable } from './_utils/utils';
  13. describe( 'table selection', () => {
  14. let editor, model, tableSelection, modelRoot;
  15. beforeEach( async () => {
  16. editor = await VirtualTestEditor.create( {
  17. plugins: [ TableEditing, TableSelection, Paragraph ]
  18. } );
  19. model = editor.model;
  20. modelRoot = model.document.getRoot();
  21. tableSelection = editor.plugins.get( TableSelection );
  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( 'TableSelection', () => {
  32. describe( 'isSelectingAndSomethingElse()', () => {
  33. it( 'should be false if selection is not started', () => {
  34. expect( tableSelection.isSelectingAndSomethingElse ).to.be.false;
  35. } );
  36. it( 'should be true if selection is selecting two different cells', () => {
  37. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  38. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  39. expect( tableSelection.isSelectingAndSomethingElse ).to.be.true;
  40. } );
  41. it( 'should be false if selection start/end is selecting the same table cell', () => {
  42. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  43. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  44. expect( tableSelection.isSelectingAndSomethingElse ).to.be.false;
  45. } );
  46. it( 'should be false if selection has no end cell', () => {
  47. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  48. expect( tableSelection.isSelectingAndSomethingElse ).to.be.false;
  49. } );
  50. it( 'should be false if selection has ended', () => {
  51. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  52. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  53. tableSelection.stopSelection();
  54. expect( tableSelection.isSelectingAndSomethingElse ).to.be.false;
  55. } );
  56. } );
  57. describe( 'startSelectingFrom()', () => {
  58. it( 'should not change model selection', () => {
  59. const spy = sinon.spy();
  60. model.document.selection.on( 'change', spy );
  61. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  62. sinon.assert.notCalled( spy );
  63. } );
  64. } );
  65. describe( 'setSelectingTo()', () => {
  66. it( 'should not change model selection if selection is not started', () => {
  67. const spy = sinon.spy();
  68. model.document.selection.on( 'change', spy );
  69. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  70. sinon.assert.notCalled( spy );
  71. } );
  72. it( 'should change model selection if valid selection will be set', () => {
  73. const spy = sinon.spy();
  74. model.document.selection.on( 'change', spy );
  75. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  76. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  77. sinon.assert.calledOnce( spy );
  78. } );
  79. it( 'should not change model selection if passed table cell is from other table then start cell', () => {
  80. setModelData( model,
  81. modelTable( [
  82. [ '11[]', '12', '13' ],
  83. [ '21', '22', '23' ],
  84. [ '31', '32', '33' ]
  85. ] ) +
  86. modelTable( [
  87. [ 'a', 'b' ],
  88. [ 'c', 'd' ]
  89. ] )
  90. );
  91. const spy = sinon.spy();
  92. model.document.selection.on( 'change', spy );
  93. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  94. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 1, 0, 1 ] ) );
  95. sinon.assert.notCalled( spy );
  96. } );
  97. it( 'should select two table cells', () => {
  98. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  99. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  100. assertSelectedCells( [
  101. [ 1, 1, 0 ],
  102. [ 0, 0, 0 ],
  103. [ 0, 0, 0 ]
  104. ] );
  105. } );
  106. it( 'should select four table cells for diagonal selection', () => {
  107. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  108. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
  109. assertSelectedCells( [
  110. [ 1, 1, 0 ],
  111. [ 1, 1, 0 ],
  112. [ 0, 0, 0 ]
  113. ] );
  114. } );
  115. it( 'should select row table cells', () => {
  116. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  117. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 2 ] ) );
  118. assertSelectedCells( [
  119. [ 1, 1, 1 ],
  120. [ 0, 0, 0 ],
  121. [ 0, 0, 0 ]
  122. ] );
  123. } );
  124. it( 'should select column table cells', () => {
  125. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  126. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
  127. assertSelectedCells( [
  128. [ 0, 1, 0 ],
  129. [ 0, 1, 0 ],
  130. [ 0, 1, 0 ]
  131. ] );
  132. } );
  133. it( 'should create proper selection on consecutive changes', () => {
  134. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
  135. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
  136. assertSelectedCells( [
  137. [ 0, 0, 0 ],
  138. [ 0, 1, 0 ],
  139. [ 0, 1, 0 ]
  140. ] );
  141. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  142. assertSelectedCells( [
  143. [ 0, 1, 0 ],
  144. [ 0, 1, 0 ],
  145. [ 0, 0, 0 ]
  146. ] );
  147. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
  148. assertSelectedCells( [
  149. [ 0, 0, 0 ],
  150. [ 0, 1, 1 ],
  151. [ 0, 1, 1 ]
  152. ] );
  153. } );
  154. } );
  155. describe( 'stopSelection()', () => {
  156. it( 'should not clear currently selected cells if not cell was passed', () => {
  157. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  158. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  159. tableSelection.stopSelection();
  160. assertSelectedCells( [
  161. [ 1, 1, 0 ],
  162. [ 0, 0, 0 ],
  163. [ 0, 0, 0 ]
  164. ] );
  165. } );
  166. it( 'should change model selection if cell was passed', () => {
  167. const spy = sinon.spy();
  168. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  169. model.document.selection.on( 'change', spy );
  170. tableSelection.stopSelection( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  171. sinon.assert.calledOnce( spy );
  172. } );
  173. it( 'should extend selection to passed table cell', () => {
  174. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  175. tableSelection.stopSelection( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  176. assertSelectedCells( [
  177. [ 1, 1, 0 ],
  178. [ 0, 0, 0 ],
  179. [ 0, 0, 0 ]
  180. ] );
  181. } );
  182. } );
  183. describe( 'clearSelection()', () => {
  184. it( 'should not change model selection', () => {
  185. const spy = sinon.spy();
  186. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  187. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  188. model.document.selection.on( 'change', spy );
  189. tableSelection.clearSelection();
  190. sinon.assert.notCalled( spy );
  191. } );
  192. it( 'should stop selecting mode', () => {
  193. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  194. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  195. tableSelection.clearSelection();
  196. expect( tableSelection.isSelectingAndSomethingElse ).to.be.false;
  197. } );
  198. it( 'should not reset model selections', () => {
  199. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  200. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  201. tableSelection.clearSelection();
  202. assertSelectedCells( [
  203. [ 1, 1, 0 ],
  204. [ 0, 0, 0 ],
  205. [ 0, 0, 0 ]
  206. ] );
  207. } );
  208. } );
  209. describe( '* getSelectedTableCells()', () => {
  210. it( 'should return nothing if selection is not started', () => {
  211. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [] );
  212. } );
  213. it( 'should return two table cells', () => {
  214. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  215. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  216. tableSelection.startSelectingFrom( firstCell );
  217. tableSelection.setSelectingTo( lastCell );
  218. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [
  219. firstCell, lastCell
  220. ] );
  221. } );
  222. it( 'should return four table cells for diagonal selection', () => {
  223. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  224. const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  225. tableSelection.startSelectingFrom( firstCell );
  226. tableSelection.setSelectingTo( lastCell );
  227. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [
  228. firstCell, modelRoot.getNodeByPath( [ 0, 0, 1 ] ), modelRoot.getNodeByPath( [ 0, 1, 0 ] ), lastCell
  229. ] );
  230. } );
  231. it( 'should return row table cells', () => {
  232. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  233. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
  234. tableSelection.startSelectingFrom( firstCell );
  235. tableSelection.setSelectingTo( lastCell );
  236. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [
  237. firstCell, modelRoot.getNodeByPath( [ 0, 0, 1 ] ), lastCell
  238. ] );
  239. } );
  240. it( 'should return column table cells', () => {
  241. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  242. const lastCell = modelRoot.getNodeByPath( [ 0, 2, 1 ] );
  243. tableSelection.startSelectingFrom( firstCell );
  244. tableSelection.setSelectingTo( lastCell );
  245. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [
  246. firstCell, modelRoot.getNodeByPath( [ 0, 1, 1 ] ), lastCell
  247. ] );
  248. } );
  249. } );
  250. } );
  251. describe( 'mouse selection', () => {
  252. let view, domEvtDataStub;
  253. beforeEach( () => {
  254. view = editor.editing.view;
  255. domEvtDataStub = {
  256. domEvent: {
  257. buttons: 1
  258. },
  259. target: undefined,
  260. preventDefault: sinon.spy(),
  261. stopPropagation: sinon.spy()
  262. };
  263. } );
  264. it( 'should not start table selection when mouse move is inside one table cell', () => {
  265. setModelData( model, modelTable( [
  266. [ '[]00', '01' ],
  267. [ '10', '11' ]
  268. ] ) );
  269. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  270. view.document.fire( 'mousedown', domEvtDataStub );
  271. assertEqualMarkup( getModelData( model ), modelTable( [
  272. [ '[]00', '01' ],
  273. [ '10', '11' ]
  274. ] ) );
  275. view.document.fire( 'mousemove', domEvtDataStub );
  276. assertEqualMarkup( getModelData( model ), modelTable( [
  277. [ '[]00', '01' ],
  278. [ '10', '11' ]
  279. ] ) );
  280. } );
  281. it( 'should start table selection when mouse move expands over two cells', () => {
  282. setModelData( model, modelTable( [
  283. [ '[]00', '01' ],
  284. [ '10', '11' ]
  285. ] ) );
  286. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  287. view.document.fire( 'mousedown', domEvtDataStub );
  288. assertEqualMarkup( getModelData( model ), modelTable( [
  289. [ '[]00', '01' ],
  290. [ '10', '11' ]
  291. ] ) );
  292. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 1 );
  293. view.document.fire( 'mousemove', domEvtDataStub );
  294. assertEqualMarkup( getModelData( model ), modelTable( [
  295. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true } ],
  296. [ '10', '11' ]
  297. ] ) );
  298. assertEqualMarkup( getViewData( view ), viewTable( [
  299. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  300. [ '10', '11' ]
  301. ], { asWidget: true } ) );
  302. } );
  303. it( 'should select rectangular table cells when mouse moved to diagonal cell (up -> down)', () => {
  304. setModelData( model, modelTable( [
  305. [ '[]00', '01' ],
  306. [ '10', '11' ]
  307. ] ) );
  308. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  309. view.document.fire( 'mousedown', domEvtDataStub );
  310. assertEqualMarkup( getModelData( model ), modelTable( [
  311. [ '[]00', '01' ],
  312. [ '10', '11' ]
  313. ] ) );
  314. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  315. view.document.fire( 'mousemove', domEvtDataStub );
  316. assertEqualMarkup( getModelData( model ), modelTable( [
  317. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true } ],
  318. [ { contents: '10', isSelected: true }, { contents: '11', isSelected: true } ]
  319. ] ) );
  320. assertEqualMarkup( getViewData( view ), viewTable( [
  321. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  322. [ { contents: '10', class: 'selected', isSelected: true }, { contents: '11', class: 'selected', isSelected: true } ]
  323. ], { asWidget: true } ) );
  324. } );
  325. it( 'should select rectangular table cells when mouse moved to diagonal cell (down -> up)', () => {
  326. setModelData( model, modelTable( [
  327. [ '00', '01' ],
  328. [ '10', '[]11' ]
  329. ] ) );
  330. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  331. view.document.fire( 'mousedown', domEvtDataStub );
  332. assertEqualMarkup( getModelData( model ), modelTable( [
  333. [ '00', '01' ],
  334. [ '10', '[]11' ]
  335. ] ) );
  336. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  337. view.document.fire( 'mousemove', domEvtDataStub );
  338. assertEqualMarkup( getModelData( model ), modelTable( [
  339. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true } ],
  340. [ { contents: '10', isSelected: true }, { contents: '11', isSelected: true } ]
  341. ] ) );
  342. assertEqualMarkup( getViewData( view ), viewTable( [
  343. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  344. [ { contents: '10', class: 'selected', isSelected: true }, { contents: '11', class: 'selected', isSelected: true } ]
  345. ], { asWidget: true } ) );
  346. } );
  347. it( 'should update view selection after changing selection rect', () => {
  348. setModelData( model, modelTable( [
  349. [ '[]00', '01', '02' ],
  350. [ '10', '11', '12' ],
  351. [ '20', '21', '22' ]
  352. ] ) );
  353. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  354. view.document.fire( 'mousedown', domEvtDataStub );
  355. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 2, 2 );
  356. view.document.fire( 'mousemove', domEvtDataStub );
  357. assertEqualMarkup( getModelData( model ), modelTable( [
  358. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true }, { contents: '02', isSelected: true } ],
  359. [ { contents: '10', isSelected: true }, { contents: '11', isSelected: true }, { contents: '12', isSelected: true } ],
  360. [ { contents: '20', isSelected: true }, { contents: '21', isSelected: true }, { contents: '22', isSelected: true } ]
  361. ] ) );
  362. assertEqualMarkup( getViewData( view ), viewTable( [
  363. [
  364. { contents: '00', class: 'selected', isSelected: true },
  365. { contents: '01', class: 'selected', isSelected: true },
  366. { contents: '02', class: 'selected', isSelected: true }
  367. ],
  368. [
  369. { contents: '10', class: 'selected', isSelected: true },
  370. { contents: '11', class: 'selected', isSelected: true },
  371. { contents: '12', class: 'selected', isSelected: true }
  372. ],
  373. [
  374. { contents: '20', class: 'selected', isSelected: true },
  375. { contents: '21', class: 'selected', isSelected: true },
  376. { contents: '22', class: 'selected', isSelected: true }
  377. ]
  378. ], { asWidget: true } ) );
  379. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  380. view.document.fire( 'mousemove', domEvtDataStub );
  381. assertEqualMarkup( getModelData( model ), modelTable( [
  382. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true }, '02' ],
  383. [ { contents: '10', isSelected: true }, { contents: '11', isSelected: true }, '12' ],
  384. [ '20', '21', '22' ]
  385. ] ) );
  386. assertEqualMarkup( getViewData( view ), viewTable( [
  387. [
  388. { contents: '00', class: 'selected', isSelected: true },
  389. { contents: '01', class: 'selected', isSelected: true },
  390. '02'
  391. ],
  392. [
  393. { contents: '10', class: 'selected', isSelected: true },
  394. { contents: '11', class: 'selected', isSelected: true },
  395. '12'
  396. ],
  397. [
  398. '20',
  399. '21',
  400. '22'
  401. ]
  402. ], { asWidget: true } ) );
  403. } );
  404. it( 'should stop selecting after "mouseup" event', () => {
  405. setModelData( model, modelTable( [
  406. [ '[]00', '01' ],
  407. [ '10', '11' ]
  408. ] ) );
  409. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  410. view.document.fire( 'mousedown', domEvtDataStub );
  411. assertEqualMarkup( getModelData( model ), modelTable( [
  412. [ '[]00', '01' ],
  413. [ '10', '11' ]
  414. ] ) );
  415. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 1 );
  416. view.document.fire( 'mousemove', domEvtDataStub );
  417. view.document.fire( 'mouseup', domEvtDataStub );
  418. assertEqualMarkup( getModelData( model ), modelTable( [
  419. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true } ],
  420. [ '10', '11' ]
  421. ] ) );
  422. assertEqualMarkup( getViewData( view ), viewTable( [
  423. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  424. [ '10', '11' ]
  425. ], { asWidget: true } ) );
  426. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  427. view.document.fire( 'mousemove', domEvtDataStub );
  428. assertEqualMarkup( getModelData( model ), modelTable( [
  429. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true } ],
  430. [ '10', '11' ]
  431. ] ) );
  432. } );
  433. it( 'should stop selection mode on "mouseleve" event', () => {
  434. setModelData( model, modelTable( [
  435. [ '[]00', '01' ],
  436. [ '10', '11' ]
  437. ] ) );
  438. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  439. view.document.fire( 'mousedown', domEvtDataStub );
  440. assertEqualMarkup( getModelData( model ), modelTable( [
  441. [ '[]00', '01' ],
  442. [ '10', '11' ]
  443. ] ) );
  444. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 1 );
  445. view.document.fire( 'mousemove', domEvtDataStub );
  446. view.document.fire( 'mouseleave', domEvtDataStub );
  447. assertEqualMarkup( getModelData( model ), modelTable( [
  448. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true } ],
  449. [ '10', '11' ]
  450. ] ) );
  451. assertEqualMarkup( getViewData( view ), viewTable( [
  452. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  453. [ '10', '11' ]
  454. ], { asWidget: true } ) );
  455. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  456. view.document.fire( 'mousemove', domEvtDataStub );
  457. assertEqualMarkup( getModelData( model ), modelTable( [
  458. [ { contents: '00', isSelected: true }, { contents: '01', isSelected: true } ],
  459. [ '10', '11' ]
  460. ] ) );
  461. } );
  462. it( 'should clear view table selection after mouse click outside table', () => {
  463. setModelData( model, modelTable( [
  464. [ '[]00', '01' ],
  465. [ '10', '11' ]
  466. ] ) + '<paragraph>foo</paragraph>' );
  467. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  468. view.document.fire( 'mousedown', domEvtDataStub );
  469. assertEqualMarkup( getModelData( model ), modelTable( [
  470. [ '[]00', '01' ],
  471. [ '10', '11' ]
  472. ] ) + '<paragraph>foo</paragraph>' );
  473. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 1 );
  474. view.document.fire( 'mousemove', domEvtDataStub );
  475. domEvtDataStub.target = view.document.getRoot().getChild( 1 );
  476. view.document.fire( 'mousemove', domEvtDataStub );
  477. view.document.fire( 'mousedown', domEvtDataStub );
  478. view.document.fire( 'mouseup', domEvtDataStub );
  479. // The click in the DOM would trigger selection change and it will set the selection:
  480. model.change( writer => {
  481. writer.setSelection( writer.createRange( writer.createPositionAt( model.document.getRoot().getChild( 1 ), 0 ) ) );
  482. } );
  483. assertEqualMarkup( getViewData( view ), viewTable( [
  484. [ '00', '01' ],
  485. [ '10', '11' ]
  486. ], { asWidget: true } ) + '<p>{}foo</p>' );
  487. } );
  488. } );
  489. // Helper method for asserting selected table cells.
  490. //
  491. // To check if a table has expected cells selected pass two dimensional array of truthy and falsy values:
  492. //
  493. // assertSelectedCells( [
  494. // [ 0, 1 ],
  495. // [ 0, 1 ]
  496. // ] );
  497. //
  498. // The above call will check if table has second column selected (assuming no spans).
  499. //
  500. // **Note**: This function operates on child indexes - not rows/columns.
  501. function assertSelectedCells( tableMap ) {
  502. const tableIndex = 0;
  503. for ( let rowIndex = 0; rowIndex < tableMap.length; rowIndex++ ) {
  504. const row = tableMap[ rowIndex ];
  505. for ( let cellIndex = 0; cellIndex < row.length; cellIndex++ ) {
  506. const expectSelected = row[ cellIndex ];
  507. if ( expectSelected ) {
  508. assertNodeIsSelected( [ tableIndex, rowIndex, cellIndex ] );
  509. } else {
  510. assertNodeIsNotSelected( [ tableIndex, rowIndex, cellIndex ] );
  511. }
  512. }
  513. }
  514. }
  515. function assertNodeIsSelected( path ) {
  516. const node = modelRoot.getNodeByPath( path );
  517. const selectionRanges = Array.from( model.document.selection.getRanges() );
  518. expect( selectionRanges.some( range => range.containsItem( node ) ), `Expected node [${ path }] to be selected` ).to.be.true;
  519. }
  520. function assertNodeIsNotSelected( path ) {
  521. const node = modelRoot.getNodeByPath( path );
  522. const selectionRanges = Array.from( model.document.selection.getRanges() );
  523. expect( selectionRanges.every( range => !range.containsItem( node ) ), `Expected node [${ path }] to be not selected` ).to.be.true;
  524. }
  525. } );
  526. function markTableCellAsSelectedInEvent( domEvtDataStub, view, tableIndex, sectionIndex, rowInSectionIndex, tableCellIndex ) {
  527. domEvtDataStub.target = view.document.getRoot()
  528. .getChild( tableIndex )
  529. .getChild( 1 ) // Table is second in widget
  530. .getChild( sectionIndex )
  531. .getChild( rowInSectionIndex )
  532. .getChild( tableCellIndex );
  533. }