8
0

widget.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  6. import Widget from '../src/widget';
  7. import MouseObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver';
  8. import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
  9. import { toWidget } from '../src/utils';
  10. import ViewContainer from '@ckeditor/ckeditor5-engine/src/view/containerelement';
  11. import ViewEditable from '@ckeditor/ckeditor5-engine/src/view/editableelement';
  12. import DomEventData from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata';
  13. import AttributeContainer from '@ckeditor/ckeditor5-engine/src/view/attributeelement';
  14. import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  15. import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  16. import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
  17. /* global document */
  18. describe( 'Widget', () => {
  19. let editor, doc, viewDocument;
  20. beforeEach( () => {
  21. return VirtualTestEditor.create( {
  22. plugins: [ Widget ]
  23. } )
  24. .then( newEditor => {
  25. editor = newEditor;
  26. doc = editor.document;
  27. viewDocument = editor.editing.view;
  28. doc.schema.registerItem( 'widget', '$block' );
  29. doc.schema.objects.add( 'widget' );
  30. doc.schema.registerItem( 'paragraph', '$block' );
  31. doc.schema.registerItem( 'inline', '$inline' );
  32. doc.schema.objects.add( 'inline' );
  33. doc.schema.registerItem( 'nested' );
  34. doc.schema.allow( { name: '$inline', inside: 'nested' } );
  35. doc.schema.allow( { name: 'nested', inside: 'widget' } );
  36. doc.schema.registerItem( 'editable' );
  37. doc.schema.allow( { name: '$inline', inside: 'editable' } );
  38. doc.schema.allow( { name: 'editable', inside: 'widget' } );
  39. doc.schema.allow( { name: 'editable', inside: '$root' } );
  40. buildModelConverter().for( editor.editing.modelToView )
  41. .fromElement( 'paragraph' )
  42. .toElement( 'p' );
  43. buildModelConverter().for( editor.editing.modelToView )
  44. .fromElement( 'widget' )
  45. .toElement( () => {
  46. const b = new AttributeContainer( 'b' );
  47. const div = new ViewContainer( 'div', null, b );
  48. return toWidget( div, { label: 'element label' } );
  49. } );
  50. buildModelConverter().for( editor.editing.modelToView )
  51. .fromElement( 'inline' )
  52. .toElement( 'figure' );
  53. buildModelConverter().for( editor.editing.modelToView )
  54. .fromElement( 'nested' )
  55. .toElement( () => new ViewEditable( 'figcaption', { contenteditable: true } ) );
  56. buildModelConverter().for( editor.editing.modelToView )
  57. .fromElement( 'editable' )
  58. .toElement( () => new ViewEditable( 'figcaption', { contenteditable: true } ) );
  59. } );
  60. } );
  61. it( 'should be loaded', () => {
  62. expect( editor.plugins.get( Widget ) ).to.be.instanceOf( Widget );
  63. } );
  64. it( 'should add MouseObserver', () => {
  65. expect( editor.editing.view.getObserver( MouseObserver ) ).to.be.instanceof( MouseObserver );
  66. } );
  67. it( 'should create selection over clicked widget', () => {
  68. setModelData( doc, '[]<widget></widget>' );
  69. const viewDiv = viewDocument.getRoot().getChild( 0 );
  70. const domEventDataMock = {
  71. target: viewDiv,
  72. preventDefault: sinon.spy()
  73. };
  74. viewDocument.fire( 'mousedown', domEventDataMock );
  75. expect( getModelData( doc ) ).to.equal( '[<widget></widget>]' );
  76. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  77. } );
  78. it( 'should create selection when clicked in nested element', () => {
  79. setModelData( doc, '[]<widget></widget>' );
  80. const viewDiv = viewDocument.getRoot().getChild( 0 );
  81. const viewB = viewDiv.getChild( 0 );
  82. const domEventDataMock = {
  83. target: viewB,
  84. preventDefault: sinon.spy()
  85. };
  86. viewDocument.fire( 'mousedown', domEventDataMock );
  87. expect( getModelData( doc ) ).to.equal( '[<widget></widget>]' );
  88. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  89. } );
  90. it( 'should do nothing if clicked inside nested editable', () => {
  91. setModelData( doc, '[]<widget><nested>foo bar</nested></widget>' );
  92. const viewDiv = viewDocument.getRoot().getChild( 0 );
  93. const viewFigcaption = viewDiv.getChild( 0 );
  94. const domEventDataMock = {
  95. target: viewFigcaption,
  96. preventDefault: sinon.spy()
  97. };
  98. viewDocument.fire( 'mousedown', domEventDataMock );
  99. sinon.assert.notCalled( domEventDataMock.preventDefault );
  100. } );
  101. it( 'should do nothing if clicked in non-widget element', () => {
  102. setModelData( doc, '<paragraph>[]foo bar</paragraph><widget></widget>' );
  103. const viewP = viewDocument.getRoot().getChild( 0 );
  104. const domEventDataMock = {
  105. target: viewP,
  106. preventDefault: sinon.spy()
  107. };
  108. viewDocument.focus();
  109. viewDocument.fire( 'mousedown', domEventDataMock );
  110. expect( getModelData( doc ) ).to.equal( '<paragraph>[]foo bar</paragraph><widget></widget>' );
  111. sinon.assert.notCalled( domEventDataMock.preventDefault );
  112. } );
  113. it( 'should not focus editable if already is focused', () => {
  114. setModelData( doc, '<widget></widget>' );
  115. const widget = viewDocument.getRoot().getChild( 0 );
  116. const domEventDataMock = {
  117. target: widget,
  118. preventDefault: sinon.spy()
  119. };
  120. const focusSpy = sinon.spy( viewDocument, 'focus' );
  121. viewDocument.isFocused = true;
  122. viewDocument.fire( 'mousedown', domEventDataMock );
  123. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  124. sinon.assert.notCalled( focusSpy );
  125. expect( getModelData( doc ) ).to.equal( '[<widget></widget>]' );
  126. } );
  127. it( 'should apply fake view selection if model selection is on widget element', () => {
  128. setModelData( doc, '[<widget>foo bar</widget>]' );
  129. expect( getViewData( viewDocument ) ).to.equal(
  130. '[<div class="ck-widget ck-widget_selected" contenteditable="false">foo bar<b></b></div>]'
  131. );
  132. expect( viewDocument.selection.isFake ).to.be.true;
  133. } );
  134. it( 'should use element\'s label to set fake selection if one is provided', () => {
  135. setModelData( doc, '[<widget>foo bar</widget>]' );
  136. expect( viewDocument.selection.fakeSelectionLabel ).to.equal( 'element label' );
  137. } );
  138. it( 'fake selection should be empty if widget is not selected', () => {
  139. setModelData( doc, '<widget>foo bar</widget>' );
  140. expect( viewDocument.selection.fakeSelectionLabel ).to.equal( '' );
  141. } );
  142. it( 'should toggle selected class', () => {
  143. setModelData( doc, '[<widget>foo</widget>]' );
  144. expect( getViewData( viewDocument ) ).to.equal(
  145. '[<div class="ck-widget ck-widget_selected" contenteditable="false">foo<b></b></div>]'
  146. );
  147. doc.enqueueChanges( () => {
  148. doc.selection.collapseToStart();
  149. } );
  150. expect( getViewData( viewDocument ) ).to.equal(
  151. '[]<div class="ck-widget" contenteditable="false">foo<b></b></div>'
  152. );
  153. } );
  154. it( 'should do nothing when selection is placed in other editable', () => {
  155. setModelData( doc, '<widget><editable>foo bar</editable></widget><editable>[baz]</editable>' );
  156. expect( getViewData( viewDocument ) ).to.equal(
  157. '<div class="ck-widget" contenteditable="false">' +
  158. '<figcaption contenteditable="true">foo bar</figcaption>' +
  159. '<b></b>' +
  160. '</div>' +
  161. '<figcaption contenteditable="true">{baz}</figcaption>'
  162. );
  163. } );
  164. describe( 'keys handling', () => {
  165. describe( 'delete and backspace', () => {
  166. test(
  167. 'should select widget when backspace is pressed',
  168. '<widget></widget><paragraph>[]foo</paragraph>',
  169. keyCodes.backspace,
  170. '[<widget></widget>]<paragraph>foo</paragraph>'
  171. );
  172. test(
  173. 'should remove empty element after selecting widget when backspace is pressed',
  174. '<widget></widget><paragraph>[]</paragraph>',
  175. keyCodes.backspace,
  176. '[<widget></widget>]'
  177. );
  178. test(
  179. 'should select widget when delete is pressed',
  180. '<paragraph>foo[]</paragraph><widget></widget>',
  181. keyCodes.delete,
  182. '<paragraph>foo</paragraph>[<widget></widget>]'
  183. );
  184. test(
  185. 'should remove empty element after selecting widget when delete is pressed',
  186. '<paragraph>[]</paragraph><widget></widget>',
  187. keyCodes.delete,
  188. '[<widget></widget>]'
  189. );
  190. test(
  191. 'should not respond to other keys',
  192. '<widget></widget><paragraph>[]foo</paragraph>',
  193. 65,
  194. '<widget></widget><paragraph>[]foo</paragraph>'
  195. );
  196. test(
  197. 'should do nothing on non-collapsed selection',
  198. '<widget></widget><paragraph>[f]oo</paragraph>',
  199. keyCodes.backspace,
  200. '<widget></widget><paragraph>[f]oo</paragraph>'
  201. );
  202. test(
  203. 'should do nothing on non-object elements',
  204. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>',
  205. keyCodes.backspace,
  206. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>'
  207. );
  208. test(
  209. 'should work correctly with modifier key: backspace + ctrl',
  210. '<widget></widget><paragraph>[]foo</paragraph>',
  211. { keyCode: keyCodes.backspace, ctrlKey: true },
  212. '[<widget></widget>]<paragraph>foo</paragraph>'
  213. );
  214. test(
  215. 'should work correctly with modifier key: backspace + alt',
  216. '<widget></widget><paragraph>[]foo</paragraph>',
  217. { keyCode: keyCodes.backspace, altKey: true },
  218. '[<widget></widget>]<paragraph>foo</paragraph>'
  219. );
  220. test(
  221. 'should work correctly with modifier key: backspace + shift',
  222. '<widget></widget><paragraph>[]foo</paragraph>',
  223. { keyCode: keyCodes.backspace, shiftKey: true },
  224. '[<widget></widget>]<paragraph>foo</paragraph>'
  225. );
  226. test(
  227. 'should work correctly with modifier key: delete + ctrl',
  228. '<paragraph>foo[]</paragraph><widget></widget>',
  229. { keyCode: keyCodes.delete, ctrlKey: true },
  230. '<paragraph>foo</paragraph>[<widget></widget>]'
  231. );
  232. test(
  233. 'should work correctly with modifier key: delete + alt',
  234. '<paragraph>foo[]</paragraph><widget></widget>',
  235. { keyCode: keyCodes.delete, altKey: true },
  236. '<paragraph>foo</paragraph>[<widget></widget>]'
  237. );
  238. test(
  239. 'should work correctly with modifier key: delete + shift',
  240. '<paragraph>foo[]</paragraph><widget></widget>',
  241. { keyCode: keyCodes.delete, shiftKey: true },
  242. '<paragraph>foo</paragraph>[<widget></widget>]'
  243. );
  244. test(
  245. 'should not modify backspace default behaviour in single paragraph boundaries',
  246. '<paragraph>[]foo</paragraph>',
  247. keyCodes.backspace,
  248. '<paragraph>[]foo</paragraph>'
  249. );
  250. test(
  251. 'should not modify delete default behaviour in single paragraph boundaries',
  252. '<paragraph>foo[]</paragraph>',
  253. keyCodes.delete,
  254. '<paragraph>foo[]</paragraph>'
  255. );
  256. test(
  257. 'should do nothing on selected widget preceded by a paragraph - backspace',
  258. '<paragraph>foo</paragraph>[<widget></widget>]',
  259. keyCodes.backspace,
  260. '<paragraph>foo</paragraph>[<widget></widget>]'
  261. );
  262. test(
  263. 'should do nothing on selected widget preceded by another widget - backspace',
  264. '<widget></widget>[<widget></widget>]',
  265. keyCodes.backspace,
  266. '<widget></widget>[<widget></widget>]'
  267. );
  268. test(
  269. 'should do nothing on selected widget before paragraph - backspace',
  270. '[<widget></widget>]<paragraph>foo</paragraph>',
  271. keyCodes.backspace,
  272. '[<widget></widget>]<paragraph>foo</paragraph>'
  273. );
  274. test(
  275. 'should do nothing on selected widget before another widget - backspace',
  276. '[<widget></widget>]<widget></widget>',
  277. keyCodes.backspace,
  278. '[<widget></widget>]<widget></widget>'
  279. );
  280. test(
  281. 'should do nothing on selected widget between paragraphs - backspace',
  282. '<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>',
  283. keyCodes.backspace,
  284. '<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>'
  285. );
  286. test(
  287. 'should do nothing on selected widget between other widgets - backspace',
  288. '<widget></widget>[<widget></widget>]<widget></widget>',
  289. keyCodes.backspace,
  290. '<widget></widget>[<widget></widget>]<widget></widget>'
  291. );
  292. test(
  293. 'should do nothing on selected widget preceded by a paragraph - delete',
  294. '<paragraph>foo</paragraph>[<widget></widget>]',
  295. keyCodes.delete,
  296. '<paragraph>foo</paragraph>[<widget></widget>]'
  297. );
  298. test(
  299. 'should do nothing on selected widget preceded by another widget - delete',
  300. '<widget></widget>[<widget></widget>]',
  301. keyCodes.delete,
  302. '<widget></widget>[<widget></widget>]'
  303. );
  304. test(
  305. 'should do nothing on selected widget before paragraph - delete',
  306. '[<widget></widget>]<paragraph>foo</paragraph>',
  307. keyCodes.delete,
  308. '[<widget></widget>]<paragraph>foo</paragraph>'
  309. );
  310. test(
  311. 'should do nothing on selected widget before another widget - delete',
  312. '[<widget></widget>]<widget></widget>',
  313. keyCodes.delete,
  314. '[<widget></widget>]<widget></widget>'
  315. );
  316. test(
  317. 'should do nothing on selected widget between paragraphs - delete',
  318. '<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>',
  319. keyCodes.delete,
  320. '<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>'
  321. );
  322. test(
  323. 'should do nothing on selected widget between other widgets - delete',
  324. '<widget></widget>[<widget></widget>]<widget></widget>',
  325. keyCodes.delete,
  326. '<widget></widget>[<widget></widget>]<widget></widget>'
  327. );
  328. test(
  329. 'should select inline objects - backspace',
  330. '<paragraph>foo<inline></inline>[]bar</paragraph>',
  331. keyCodes.backspace,
  332. '<paragraph>foo[<inline></inline>]bar</paragraph>'
  333. );
  334. test(
  335. 'should select inline objects - delete',
  336. '<paragraph>foo[]<inline></inline>bar</paragraph>',
  337. keyCodes.delete,
  338. '<paragraph>foo[<inline></inline>]bar</paragraph>'
  339. );
  340. test(
  341. 'should do nothing on selected inline objects - backspace',
  342. '<paragraph>foo[<inline></inline>]bar</paragraph>',
  343. keyCodes.backspace,
  344. '<paragraph>foo[<inline></inline>]bar</paragraph>'
  345. );
  346. test(
  347. 'should do nothing on selected inline objects - delete',
  348. '<paragraph>foo[<inline></inline>]bar</paragraph>',
  349. keyCodes.delete,
  350. '<paragraph>foo[<inline></inline>]bar</paragraph>'
  351. );
  352. test(
  353. 'should do nothing if selection is placed after first letter - backspace',
  354. '<paragraph>a[]</paragraph>',
  355. keyCodes.backspace,
  356. '<paragraph>a[]</paragraph>'
  357. );
  358. test(
  359. 'should do nothing if selection is placed before first letter - delete',
  360. '<paragraph>[]a</paragraph>',
  361. keyCodes.delete,
  362. '<paragraph>[]a</paragraph>'
  363. );
  364. it( 'should prevent default behaviour and stop event propagation', () => {
  365. const keydownHandler = sinon.spy();
  366. const domEventDataMock = {
  367. keyCode: keyCodes.delete,
  368. preventDefault: sinon.spy(),
  369. };
  370. setModelData( doc, '<paragraph>foo[]</paragraph><widget></widget>' );
  371. viewDocument.on( 'keydown', keydownHandler );
  372. viewDocument.fire( 'keydown', domEventDataMock );
  373. expect( getModelData( doc ) ).to.equal( '<paragraph>foo</paragraph>[<widget></widget>]' );
  374. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  375. sinon.assert.notCalled( keydownHandler );
  376. } );
  377. } );
  378. describe( 'arrows', () => {
  379. test(
  380. 'should move selection forward from selected object - right arrow',
  381. '[<widget></widget>]<paragraph>foo</paragraph>',
  382. keyCodes.arrowright,
  383. '<widget></widget><paragraph>[]foo</paragraph>'
  384. );
  385. test(
  386. 'should move selection forward from selected object - down arrow',
  387. '[<widget></widget>]<paragraph>foo</paragraph>',
  388. keyCodes.arrowdown,
  389. '<widget></widget><paragraph>[]foo</paragraph>'
  390. );
  391. test(
  392. 'should move selection backward from selected object - left arrow',
  393. '<paragraph>foo</paragraph>[<widget></widget>]',
  394. keyCodes.arrowleft,
  395. '<paragraph>foo[]</paragraph><widget></widget>'
  396. );
  397. test(
  398. 'should move selection backward from selected object - up arrow',
  399. '<paragraph>foo</paragraph>[<widget></widget>]',
  400. keyCodes.arrowup,
  401. '<paragraph>foo[]</paragraph><widget></widget>'
  402. );
  403. test(
  404. 'should move selection to next widget - right arrow',
  405. '[<widget></widget>]<widget></widget>',
  406. keyCodes.arrowright,
  407. '<widget></widget>[<widget></widget>]'
  408. );
  409. test(
  410. 'should move selection to next widget - down arrow',
  411. '[<widget></widget>]<widget></widget>',
  412. keyCodes.arrowdown,
  413. '<widget></widget>[<widget></widget>]'
  414. );
  415. test(
  416. 'should move selection to previous widget - left arrow',
  417. '<widget></widget>[<widget></widget>]',
  418. keyCodes.arrowleft,
  419. '[<widget></widget>]<widget></widget>'
  420. );
  421. test(
  422. 'should move selection to previous widget - up arrow',
  423. '<widget></widget>[<widget></widget>]',
  424. keyCodes.arrowup,
  425. '[<widget></widget>]<widget></widget>'
  426. );
  427. test(
  428. 'should do nothing on non-collapsed selection next to object - right arrow',
  429. '<paragraph>ba[r]</paragraph><widget></widget>',
  430. keyCodes.arrowright,
  431. '<paragraph>ba[r]</paragraph><widget></widget>'
  432. );
  433. test(
  434. 'should do nothing on non-collapsed selection next to object - down arrow',
  435. '<paragraph>ba[r]</paragraph><widget></widget>',
  436. keyCodes.arrowdown,
  437. '<paragraph>ba[r]</paragraph><widget></widget>'
  438. );
  439. test(
  440. 'should do nothing on non-collapsed selection next to object - left arrow',
  441. '<widget></widget><paragraph>[b]ar</paragraph>',
  442. keyCodes.arrowleft,
  443. '<widget></widget><paragraph>[b]ar</paragraph>'
  444. );
  445. test(
  446. 'should do nothing on non-collapsed selection next to object - up arrow',
  447. '<widget></widget><paragraph>[b]ar</paragraph>',
  448. keyCodes.arrowup,
  449. '<widget></widget><paragraph>[b]ar</paragraph>'
  450. );
  451. test(
  452. 'should not move selection if there is no correct location - right arrow',
  453. '<paragraph>foo</paragraph>[<widget></widget>]',
  454. keyCodes.arrowright,
  455. '<paragraph>foo</paragraph>[<widget></widget>]'
  456. );
  457. test(
  458. 'should not move selection if there is no correct location - down arrow',
  459. '<paragraph>foo</paragraph>[<widget></widget>]',
  460. keyCodes.arrowdown,
  461. '<paragraph>foo</paragraph>[<widget></widget>]'
  462. );
  463. test(
  464. 'should not move selection if there is no correct location - left arrow',
  465. '[<widget></widget>]<paragraph>foo</paragraph>',
  466. keyCodes.arrowleft,
  467. '[<widget></widget>]<paragraph>foo</paragraph>'
  468. );
  469. test(
  470. 'should not move selection if there is no correct location - up arrow',
  471. '[<widget></widget>]<paragraph>foo</paragraph>',
  472. keyCodes.arrowup,
  473. '[<widget></widget>]<paragraph>foo</paragraph>'
  474. );
  475. it( 'should prevent default behaviour when there is no correct location - document end', () => {
  476. const keydownHandler = sinon.spy();
  477. const domEventDataMock = {
  478. keyCode: keyCodes.arrowright,
  479. preventDefault: sinon.spy(),
  480. };
  481. setModelData( doc, '<paragraph>foo</paragraph>[<widget></widget>]' );
  482. viewDocument.on( 'keydown', keydownHandler );
  483. viewDocument.fire( 'keydown', domEventDataMock );
  484. expect( getModelData( doc ) ).to.equal( '<paragraph>foo</paragraph>[<widget></widget>]' );
  485. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  486. sinon.assert.notCalled( keydownHandler );
  487. } );
  488. it( 'should prevent default behaviour when there is no correct location - document start', () => {
  489. const keydownHandler = sinon.spy();
  490. const domEventDataMock = {
  491. keyCode: keyCodes.arrowleft,
  492. preventDefault: sinon.spy(),
  493. };
  494. setModelData( doc, '[<widget></widget>]<paragraph>foo</paragraph>' );
  495. viewDocument.on( 'keydown', keydownHandler );
  496. viewDocument.fire( 'keydown', domEventDataMock );
  497. expect( getModelData( doc ) ).to.equal( '[<widget></widget>]<paragraph>foo</paragraph>' );
  498. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  499. sinon.assert.notCalled( keydownHandler );
  500. } );
  501. test(
  502. 'should move selection to object element - right arrow',
  503. '<paragraph>foo[]</paragraph><widget></widget>',
  504. keyCodes.arrowright,
  505. '<paragraph>foo</paragraph>[<widget></widget>]'
  506. );
  507. test(
  508. 'should move selection to object element - down arrow',
  509. '<paragraph>foo[]</paragraph><widget></widget>',
  510. keyCodes.arrowdown,
  511. '<paragraph>foo</paragraph>[<widget></widget>]'
  512. );
  513. test(
  514. 'should move selection to object element - left arrow',
  515. '<widget></widget><paragraph>[]foo</paragraph>',
  516. keyCodes.arrowleft,
  517. '[<widget></widget>]<paragraph>foo</paragraph>'
  518. );
  519. test(
  520. 'should move selection to object element - up arrow',
  521. '<widget></widget><paragraph>[]foo</paragraph>',
  522. keyCodes.arrowup,
  523. '[<widget></widget>]<paragraph>foo</paragraph>'
  524. );
  525. test(
  526. 'do nothing on non objects - right arrow',
  527. '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>',
  528. keyCodes.arrowright,
  529. '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>'
  530. );
  531. test(
  532. 'do nothing on non objects - down arrow',
  533. '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>',
  534. keyCodes.arrowdown,
  535. '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>'
  536. );
  537. test(
  538. 'do nothing on non objects - left arrow',
  539. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>',
  540. keyCodes.arrowleft,
  541. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>'
  542. );
  543. test(
  544. 'do nothing on non objects - up arrow',
  545. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>',
  546. keyCodes.arrowup,
  547. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>'
  548. );
  549. test(
  550. 'should work correctly with modifier key: right arrow + ctrl',
  551. '[<widget></widget>]<paragraph>foo</paragraph>',
  552. { keyCode: keyCodes.arrowright, ctrlKey: true },
  553. '<widget></widget><paragraph>[]foo</paragraph>'
  554. );
  555. test(
  556. 'should work correctly with modifier key: right arrow + alt',
  557. '[<widget></widget>]<paragraph>foo</paragraph>',
  558. { keyCode: keyCodes.arrowright, altKey: true },
  559. '<widget></widget><paragraph>[]foo</paragraph>'
  560. );
  561. test(
  562. 'should work correctly with modifier key: right arrow + shift',
  563. '[<widget></widget>]<paragraph>foo</paragraph>',
  564. { keyCode: keyCodes.arrowright, shiftKey: true },
  565. '<widget></widget><paragraph>[]foo</paragraph>'
  566. );
  567. test(
  568. 'should work correctly with modifier key: down arrow + ctrl',
  569. '[<widget></widget>]<paragraph>foo</paragraph>',
  570. { keyCode: keyCodes.arrowdown, ctrlKey: true },
  571. '<widget></widget><paragraph>[]foo</paragraph>'
  572. );
  573. test(
  574. 'should work correctly with modifier key: down arrow + alt',
  575. '[<widget></widget>]<paragraph>foo</paragraph>',
  576. { keyCode: keyCodes.arrowdown, altKey: true },
  577. '<widget></widget><paragraph>[]foo</paragraph>'
  578. );
  579. test(
  580. 'should work correctly with modifier key: down arrow + shift',
  581. '[<widget></widget>]<paragraph>foo</paragraph>',
  582. { keyCode: keyCodes.arrowdown, shiftKey: true },
  583. '<widget></widget><paragraph>[]foo</paragraph>'
  584. );
  585. test(
  586. 'should work correctly with modifier key: left arrow + ctrl',
  587. '<paragraph>foo</paragraph>[<widget></widget>]',
  588. { keyCode: keyCodes.arrowleft, ctrlKey: true },
  589. '<paragraph>foo[]</paragraph><widget></widget>'
  590. );
  591. test(
  592. 'should work correctly with modifier key: left arrow + alt',
  593. '<paragraph>foo</paragraph>[<widget></widget>]',
  594. { keyCode: keyCodes.arrowleft, altKey: true },
  595. '<paragraph>foo[]</paragraph><widget></widget>'
  596. );
  597. test(
  598. 'should work correctly with modifier key: left arrow + shift',
  599. '<paragraph>foo</paragraph>[<widget></widget>]',
  600. { keyCode: keyCodes.arrowleft, shiftKey: true },
  601. '<paragraph>foo[]</paragraph><widget></widget>'
  602. );
  603. test(
  604. 'should work correctly with modifier key: up arrow + ctrl',
  605. '<paragraph>foo</paragraph>[<widget></widget>]',
  606. { keyCode: keyCodes.arrowup, ctrlKey: true },
  607. '<paragraph>foo[]</paragraph><widget></widget>'
  608. );
  609. test(
  610. 'should work correctly with modifier key: up arrow + alt',
  611. '<paragraph>foo</paragraph>[<widget></widget>]',
  612. { keyCode: keyCodes.arrowup, altKey: true },
  613. '<paragraph>foo[]</paragraph><widget></widget>'
  614. );
  615. test(
  616. 'should work correctly with modifier key: up arrow + shift',
  617. '<paragraph>foo</paragraph>[<widget></widget>]',
  618. { keyCode: keyCodes.arrowup, shiftKey: true },
  619. '<paragraph>foo[]</paragraph><widget></widget>'
  620. );
  621. test(
  622. 'should do nothing if there is more than one selection in model',
  623. '<paragraph>[foo]</paragraph><widget></widget><paragraph>[bar]</paragraph>',
  624. keyCodes.arrowright,
  625. '<paragraph>[foo]</paragraph><widget></widget><paragraph>[bar]</paragraph>'
  626. );
  627. } );
  628. function test( name, data, keyCodeOrMock, expected ) {
  629. it( name, () => {
  630. const domEventDataMock = ( typeof keyCodeOrMock == 'object' ) ? keyCodeOrMock : {
  631. keyCode: keyCodeOrMock
  632. };
  633. setModelData( doc, data );
  634. viewDocument.fire( 'keydown', new DomEventData(
  635. viewDocument,
  636. { target: document.createElement( 'div' ), preventDefault: () => {} },
  637. domEventDataMock
  638. ) );
  639. expect( getModelData( doc ) ).to.equal( expected );
  640. } );
  641. }
  642. } );
  643. } );