8
0

tableselection.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  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( 'startSelectingFrom()', () => {
  33. it( 'should not change model selection', () => {
  34. const spy = sinon.spy();
  35. model.document.selection.on( 'change', spy );
  36. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  37. sinon.assert.notCalled( spy );
  38. } );
  39. } );
  40. describe( 'setSelectingTo()', () => {
  41. it( 'should not change model selection if selection is not started', () => {
  42. const spy = sinon.spy();
  43. model.document.selection.on( 'change', spy );
  44. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  45. sinon.assert.notCalled( spy );
  46. } );
  47. it( 'should change model selection if valid selection will be set', () => {
  48. const spy = sinon.spy();
  49. model.document.selection.on( 'change', spy );
  50. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  51. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  52. sinon.assert.calledOnce( spy );
  53. } );
  54. it( 'should not change model selection if passed table cell is from other table then start cell', () => {
  55. setModelData( model,
  56. modelTable( [
  57. [ '11[]', '12', '13' ],
  58. [ '21', '22', '23' ],
  59. [ '31', '32', '33' ]
  60. ] ) +
  61. modelTable( [
  62. [ 'a', 'b' ],
  63. [ 'c', 'd' ]
  64. ] )
  65. );
  66. const spy = sinon.spy();
  67. model.document.selection.on( 'change', spy );
  68. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  69. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 1, 0, 1 ] ) );
  70. sinon.assert.notCalled( spy );
  71. } );
  72. it( 'should select two table cells', () => {
  73. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  74. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  75. assertSelectedCells( [
  76. [ 1, 1, 0 ],
  77. [ 0, 0, 0 ],
  78. [ 0, 0, 0 ]
  79. ] );
  80. } );
  81. it( 'should select four table cells for diagonal selection', () => {
  82. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  83. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
  84. assertSelectedCells( [
  85. [ 1, 1, 0 ],
  86. [ 1, 1, 0 ],
  87. [ 0, 0, 0 ]
  88. ] );
  89. } );
  90. it( 'should select row table cells', () => {
  91. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  92. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 2 ] ) );
  93. assertSelectedCells( [
  94. [ 1, 1, 1 ],
  95. [ 0, 0, 0 ],
  96. [ 0, 0, 0 ]
  97. ] );
  98. } );
  99. it( 'should select column table cells', () => {
  100. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  101. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
  102. assertSelectedCells( [
  103. [ 0, 1, 0 ],
  104. [ 0, 1, 0 ],
  105. [ 0, 1, 0 ]
  106. ] );
  107. } );
  108. it( 'should create proper selection on consecutive changes', () => {
  109. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 1, 1 ] ) );
  110. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 1 ] ) );
  111. assertSelectedCells( [
  112. [ 0, 0, 0 ],
  113. [ 0, 1, 0 ],
  114. [ 0, 1, 0 ]
  115. ] );
  116. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  117. assertSelectedCells( [
  118. [ 0, 1, 0 ],
  119. [ 0, 1, 0 ],
  120. [ 0, 0, 0 ]
  121. ] );
  122. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 2, 2 ] ) );
  123. assertSelectedCells( [
  124. [ 0, 0, 0 ],
  125. [ 0, 1, 1 ],
  126. [ 0, 1, 1 ]
  127. ] );
  128. } );
  129. } );
  130. describe( 'stopSelection()', () => {
  131. it( 'should not clear currently selected cells if not cell was passed', () => {
  132. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  133. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  134. tableSelection.stopSelection();
  135. assertSelectedCells( [
  136. [ 1, 1, 0 ],
  137. [ 0, 0, 0 ],
  138. [ 0, 0, 0 ]
  139. ] );
  140. } );
  141. it( 'should change model selection if cell was passed', () => {
  142. const spy = sinon.spy();
  143. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  144. model.document.selection.on( 'change', spy );
  145. tableSelection.stopSelection( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  146. sinon.assert.calledOnce( spy );
  147. } );
  148. it( 'should extend selection to passed table cell', () => {
  149. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  150. tableSelection.stopSelection( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  151. assertSelectedCells( [
  152. [ 1, 1, 0 ],
  153. [ 0, 0, 0 ],
  154. [ 0, 0, 0 ]
  155. ] );
  156. } );
  157. } );
  158. describe( 'clearSelection()', () => {
  159. it( 'should not change model selection', () => {
  160. const spy = sinon.spy();
  161. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  162. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  163. model.document.selection.on( 'change', spy );
  164. tableSelection.clearSelection();
  165. sinon.assert.notCalled( spy );
  166. } );
  167. it( 'should stop selecting mode', () => {
  168. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  169. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  170. tableSelection.clearSelection();
  171. expect( tableSelection.isSelecting ).to.be.false;
  172. } );
  173. it( 'should not reset model selections', () => {
  174. tableSelection.startSelectingFrom( modelRoot.getNodeByPath( [ 0, 0, 0 ] ) );
  175. tableSelection.setSelectingTo( modelRoot.getNodeByPath( [ 0, 0, 1 ] ) );
  176. tableSelection.clearSelection();
  177. assertSelectedCells( [
  178. [ 1, 1, 0 ],
  179. [ 0, 0, 0 ],
  180. [ 0, 0, 0 ]
  181. ] );
  182. } );
  183. } );
  184. describe( '* getSelectedTableCells()', () => {
  185. it( 'should return nothing if selection is not started', () => {
  186. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [] );
  187. } );
  188. it( 'should return two table cells', () => {
  189. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  190. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  191. tableSelection.startSelectingFrom( firstCell );
  192. tableSelection.setSelectingTo( lastCell );
  193. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [
  194. firstCell, lastCell
  195. ] );
  196. } );
  197. it( 'should return four table cells for diagonal selection', () => {
  198. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  199. const lastCell = modelRoot.getNodeByPath( [ 0, 1, 1 ] );
  200. tableSelection.startSelectingFrom( firstCell );
  201. tableSelection.setSelectingTo( lastCell );
  202. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [
  203. firstCell, modelRoot.getNodeByPath( [ 0, 0, 1 ] ), modelRoot.getNodeByPath( [ 0, 1, 0 ] ), lastCell
  204. ] );
  205. } );
  206. it( 'should return row table cells', () => {
  207. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
  208. const lastCell = modelRoot.getNodeByPath( [ 0, 0, 2 ] );
  209. tableSelection.startSelectingFrom( firstCell );
  210. tableSelection.setSelectingTo( lastCell );
  211. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [
  212. firstCell, modelRoot.getNodeByPath( [ 0, 0, 1 ] ), lastCell
  213. ] );
  214. } );
  215. it( 'should return column table cells', () => {
  216. const firstCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
  217. const lastCell = modelRoot.getNodeByPath( [ 0, 2, 1 ] );
  218. tableSelection.startSelectingFrom( firstCell );
  219. tableSelection.setSelectingTo( lastCell );
  220. expect( Array.from( tableSelection.getSelectedTableCells() ) ).to.deep.equal( [
  221. firstCell, modelRoot.getNodeByPath( [ 0, 1, 1 ] ), lastCell
  222. ] );
  223. } );
  224. } );
  225. } );
  226. describe( 'mouse selection', () => {
  227. let view, domEvtDataStub;
  228. beforeEach( () => {
  229. view = editor.editing.view;
  230. domEvtDataStub = {
  231. domEvent: {
  232. buttons: 1
  233. },
  234. target: undefined,
  235. preventDefault: sinon.spy(),
  236. stopPropagation: sinon.spy()
  237. };
  238. } );
  239. it( 'should not start table selection when mouse move is inside one table cell', () => {
  240. setModelData( model, modelTable( [
  241. [ '[]00', '01' ],
  242. [ '10', '11' ]
  243. ] ) );
  244. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  245. view.document.fire( 'mousedown', domEvtDataStub );
  246. assertEqualMarkup( getModelData( model ), modelTable( [
  247. [ '[]00', '01' ],
  248. [ '10', '11' ]
  249. ] ) );
  250. view.document.fire( 'mousemove', domEvtDataStub );
  251. assertEqualMarkup( getModelData( model ), modelTable( [
  252. [ '[]00', '01' ],
  253. [ '10', '11' ]
  254. ] ) );
  255. } );
  256. it( 'should start table selection when mouse move expands over two cells', () => {
  257. setModelData( model, modelTable( [
  258. [ '[]00', '01' ],
  259. [ '10', '11' ]
  260. ] ) );
  261. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  262. view.document.fire( 'mousedown', domEvtDataStub );
  263. assertEqualMarkup( getModelData( model ), modelTable( [
  264. [ '[]00', '01' ],
  265. [ '10', '11' ]
  266. ] ) );
  267. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 1 );
  268. view.document.fire( 'mousemove', domEvtDataStub );
  269. assertSelectedCells( [
  270. [ 1, 1 ],
  271. [ 0, 0 ]
  272. ] );
  273. assertEqualMarkup( getViewData( view ), viewTable( [
  274. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  275. [ '10', '11' ]
  276. ], { asWidget: true } ) );
  277. } );
  278. it( 'should select rectangular table cells when mouse moved to diagonal cell (up -> down)', () => {
  279. setModelData( model, modelTable( [
  280. [ '[]00', '01' ],
  281. [ '10', '11' ]
  282. ] ) );
  283. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  284. view.document.fire( 'mousedown', domEvtDataStub );
  285. assertEqualMarkup( getModelData( model ), modelTable( [
  286. [ '[]00', '01' ],
  287. [ '10', '11' ]
  288. ] ) );
  289. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  290. view.document.fire( 'mousemove', domEvtDataStub );
  291. assertSelectedCells( [
  292. [ 1, 1 ],
  293. [ 1, 1 ]
  294. ] );
  295. assertEqualMarkup( getViewData( view ), viewTable( [
  296. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  297. [ { contents: '10', class: 'selected', isSelected: true }, { contents: '11', class: 'selected', isSelected: true } ]
  298. ], { asWidget: true } ) );
  299. } );
  300. it( 'should select rectangular table cells when mouse moved to diagonal cell (down -> up)', () => {
  301. setModelData( model, modelTable( [
  302. [ '00', '01' ],
  303. [ '10', '[]11' ]
  304. ] ) );
  305. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  306. view.document.fire( 'mousedown', domEvtDataStub );
  307. assertEqualMarkup( getModelData( model ), modelTable( [
  308. [ '00', '01' ],
  309. [ '10', '[]11' ]
  310. ] ) );
  311. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  312. view.document.fire( 'mousemove', domEvtDataStub );
  313. assertSelectedCells( [
  314. [ 1, 1 ],
  315. [ 1, 1 ]
  316. ] );
  317. assertEqualMarkup( getViewData( view ), viewTable( [
  318. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  319. [ { contents: '10', class: 'selected', isSelected: true }, { contents: '11', class: 'selected', isSelected: true } ]
  320. ], { asWidget: true } ) );
  321. } );
  322. it( 'should update view selection after changing selection rect', () => {
  323. setModelData( model, modelTable( [
  324. [ '[]00', '01', '02' ],
  325. [ '10', '11', '12' ],
  326. [ '20', '21', '22' ]
  327. ] ) );
  328. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  329. view.document.fire( 'mousedown', domEvtDataStub );
  330. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 2, 2 );
  331. view.document.fire( 'mousemove', domEvtDataStub );
  332. assertSelectedCells( [
  333. [ 1, 1, 1 ],
  334. [ 1, 1, 1 ],
  335. [ 1, 1, 1 ]
  336. ] );
  337. assertEqualMarkup( getViewData( view ), viewTable( [
  338. [
  339. { contents: '00', class: 'selected', isSelected: true },
  340. { contents: '01', class: 'selected', isSelected: true },
  341. { contents: '02', class: 'selected', isSelected: true }
  342. ],
  343. [
  344. { contents: '10', class: 'selected', isSelected: true },
  345. { contents: '11', class: 'selected', isSelected: true },
  346. { contents: '12', class: 'selected', isSelected: true }
  347. ],
  348. [
  349. { contents: '20', class: 'selected', isSelected: true },
  350. { contents: '21', class: 'selected', isSelected: true },
  351. { contents: '22', class: 'selected', isSelected: true }
  352. ]
  353. ], { asWidget: true } ) );
  354. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  355. view.document.fire( 'mousemove', domEvtDataStub );
  356. assertSelectedCells( [
  357. [ 1, 1, 0 ],
  358. [ 1, 1, 0 ],
  359. [ 0, 0, 0 ]
  360. ] );
  361. assertEqualMarkup( getViewData( view ), viewTable( [
  362. [
  363. { contents: '00', class: 'selected', isSelected: true },
  364. { contents: '01', class: 'selected', isSelected: true },
  365. '02'
  366. ],
  367. [
  368. { contents: '10', class: 'selected', isSelected: true },
  369. { contents: '11', class: 'selected', isSelected: true },
  370. '12'
  371. ],
  372. [
  373. '20',
  374. '21',
  375. '22'
  376. ]
  377. ], { asWidget: true } ) );
  378. } );
  379. it( 'should stop selecting after "mouseup" event', () => {
  380. setModelData( model, modelTable( [
  381. [ '[]00', '01' ],
  382. [ '10', '11' ]
  383. ] ) );
  384. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  385. view.document.fire( 'mousedown', domEvtDataStub );
  386. assertEqualMarkup( getModelData( model ), modelTable( [
  387. [ '[]00', '01' ],
  388. [ '10', '11' ]
  389. ] ) );
  390. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 1 );
  391. view.document.fire( 'mousemove', domEvtDataStub );
  392. view.document.fire( 'mouseup', domEvtDataStub );
  393. assertSelectedCells( [
  394. [ 1, 1 ],
  395. [ 0, 0 ]
  396. ] );
  397. assertEqualMarkup( getViewData( view ), viewTable( [
  398. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  399. [ '10', '11' ]
  400. ], { asWidget: true } ) );
  401. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  402. view.document.fire( 'mousemove', domEvtDataStub );
  403. assertSelectedCells( [
  404. [ 1, 1 ],
  405. [ 0, 0 ]
  406. ] );
  407. } );
  408. it( 'should stop selection mode on "mouseleve" event', () => {
  409. setModelData( model, modelTable( [
  410. [ '[]00', '01' ],
  411. [ '10', '11' ]
  412. ] ) );
  413. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 0 );
  414. view.document.fire( 'mousedown', domEvtDataStub );
  415. assertEqualMarkup( getModelData( model ), modelTable( [
  416. [ '[]00', '01' ],
  417. [ '10', '11' ]
  418. ] ) );
  419. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 0, 1 );
  420. view.document.fire( 'mousemove', domEvtDataStub );
  421. view.document.fire( 'mouseleave', domEvtDataStub );
  422. assertSelectedCells( [
  423. [ 1, 1 ],
  424. [ 0, 0 ]
  425. ] );
  426. assertEqualMarkup( getViewData( view ), viewTable( [
  427. [ { contents: '00', class: 'selected', isSelected: true }, { contents: '01', class: 'selected', isSelected: true } ],
  428. [ '10', '11' ]
  429. ], { asWidget: true } ) );
  430. markTableCellAsSelectedInEvent( domEvtDataStub, view, 0, 0, 1, 1 );
  431. view.document.fire( 'mousemove', domEvtDataStub );
  432. assertSelectedCells( [
  433. [ 1, 1 ],
  434. [ 0, 0 ]
  435. ] );
  436. } );
  437. it( 'should do nothing on "mouseleve" event', () => {
  438. setModelData( model, modelTable( [
  439. [ '[]00', '01' ],
  440. [ '10', '11' ]
  441. ] ) );
  442. view.document.fire( 'mouseleave', domEvtDataStub );
  443. assertSelectedCells( [
  444. [ 0, 0 ],
  445. [ 0, 0 ]
  446. ] );
  447. } );
  448. it( 'should do nothing on "mousedown" event over ui element (click on selection handle)', () => {
  449. setModelData( model, modelTable( [
  450. [ '[]00', '01' ],
  451. [ '10', '11' ]
  452. ] ) );
  453. domEvtDataStub.target = view.document.getRoot()
  454. .getChild( 0 )
  455. .getChild( 0 ); // selection handler;
  456. view.document.fire( 'mousedown', domEvtDataStub );
  457. assertEqualMarkup( getModelData( model ), '[' + modelTable( [
  458. [ '00', '01' ],
  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. }