widget.js 25 KB

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