widget.js 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  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.limits.add( 'nested' );
  33. doc.schema.allow( { name: '$inline', inside: 'nested' } );
  34. doc.schema.allow( { name: 'nested', inside: 'widget' } );
  35. doc.schema.registerItem( 'editable' );
  36. doc.schema.allow( { name: '$inline', inside: 'editable' } );
  37. doc.schema.allow( { name: 'editable', inside: 'widget' } );
  38. doc.schema.allow( { name: 'editable', inside: '$root' } );
  39. // Image feature.
  40. doc.schema.registerItem( 'image' );
  41. doc.schema.allow( { name: 'image', inside: '$root' } );
  42. doc.schema.objects.add( 'image' );
  43. // Block-quote feature.
  44. doc.schema.registerItem( 'blockQuote' );
  45. doc.schema.allow( { name: 'blockQuote', inside: '$root' } );
  46. doc.schema.allow( { name: '$block', inside: 'blockQuote' } );
  47. // Div element which helps nesting elements.
  48. doc.schema.registerItem( 'div' );
  49. doc.schema.allow( { name: 'div', inside: 'blockQuote' } );
  50. doc.schema.allow( { name: 'div', inside: 'div' } );
  51. doc.schema.allow( { name: 'paragraph', inside: 'div' } );
  52. buildModelConverter().for( editor.editing.modelToView )
  53. .fromElement( 'paragraph' )
  54. .toElement( 'p' );
  55. buildModelConverter().for( editor.editing.modelToView )
  56. .fromElement( 'widget' )
  57. .toElement( () => {
  58. const b = new AttributeContainer( 'b' );
  59. const div = new ViewContainer( 'div', null, b );
  60. return toWidget( div, { label: 'element label' } );
  61. } );
  62. buildModelConverter().for( editor.editing.modelToView )
  63. .fromElement( 'inline' )
  64. .toElement( 'figure' );
  65. buildModelConverter().for( editor.editing.modelToView )
  66. .fromElement( 'nested' )
  67. .toElement( () => new ViewEditable( 'figcaption', { contenteditable: true } ) );
  68. buildModelConverter().for( editor.editing.modelToView )
  69. .fromElement( 'editable' )
  70. .toElement( () => new ViewEditable( 'figcaption', { contenteditable: true } ) );
  71. buildModelConverter().for( editor.editing.modelToView )
  72. .fromElement( 'image' )
  73. .toElement( 'img' );
  74. buildModelConverter().for( editor.editing.modelToView )
  75. .fromElement( 'blockQuote' )
  76. .toElement( 'blockquote' );
  77. buildModelConverter().for( editor.editing.modelToView )
  78. .fromElement( 'div' )
  79. .toElement( 'div' );
  80. } );
  81. } );
  82. it( 'should be loaded', () => {
  83. expect( editor.plugins.get( Widget ) ).to.be.instanceOf( Widget );
  84. } );
  85. it( 'should add MouseObserver', () => {
  86. expect( editor.editing.view.getObserver( MouseObserver ) ).to.be.instanceof( MouseObserver );
  87. } );
  88. it( 'should create selection over clicked widget', () => {
  89. setModelData( doc, '[]<widget></widget>' );
  90. const viewDiv = viewDocument.getRoot().getChild( 0 );
  91. const domEventDataMock = {
  92. target: viewDiv,
  93. preventDefault: sinon.spy()
  94. };
  95. viewDocument.fire( 'mousedown', domEventDataMock );
  96. expect( getModelData( doc ) ).to.equal( '[<widget></widget>]' );
  97. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  98. } );
  99. it( 'should create selection when clicked in nested element', () => {
  100. setModelData( doc, '[]<widget></widget>' );
  101. const viewDiv = viewDocument.getRoot().getChild( 0 );
  102. const viewB = viewDiv.getChild( 0 );
  103. const domEventDataMock = {
  104. target: viewB,
  105. preventDefault: sinon.spy()
  106. };
  107. viewDocument.fire( 'mousedown', domEventDataMock );
  108. expect( getModelData( doc ) ).to.equal( '[<widget></widget>]' );
  109. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  110. } );
  111. it( 'should do nothing if clicked inside nested editable', () => {
  112. setModelData( doc, '[]<widget><nested>foo bar</nested></widget>' );
  113. const viewDiv = viewDocument.getRoot().getChild( 0 );
  114. const viewFigcaption = viewDiv.getChild( 0 );
  115. const domEventDataMock = {
  116. target: viewFigcaption,
  117. preventDefault: sinon.spy()
  118. };
  119. viewDocument.fire( 'mousedown', domEventDataMock );
  120. sinon.assert.notCalled( domEventDataMock.preventDefault );
  121. } );
  122. it( 'should do nothing if clicked in non-widget element', () => {
  123. setModelData( doc, '<paragraph>[]foo bar</paragraph><widget></widget>' );
  124. const viewP = viewDocument.getRoot().getChild( 0 );
  125. const domEventDataMock = {
  126. target: viewP,
  127. preventDefault: sinon.spy()
  128. };
  129. viewDocument.focus();
  130. viewDocument.fire( 'mousedown', domEventDataMock );
  131. expect( getModelData( doc ) ).to.equal( '<paragraph>[]foo bar</paragraph><widget></widget>' );
  132. sinon.assert.notCalled( domEventDataMock.preventDefault );
  133. } );
  134. it( 'should not focus editable if already is focused', () => {
  135. setModelData( doc, '<widget></widget>' );
  136. const widget = viewDocument.getRoot().getChild( 0 );
  137. const domEventDataMock = {
  138. target: widget,
  139. preventDefault: sinon.spy()
  140. };
  141. const focusSpy = sinon.spy( viewDocument, 'focus' );
  142. viewDocument.isFocused = true;
  143. viewDocument.fire( 'mousedown', domEventDataMock );
  144. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  145. sinon.assert.notCalled( focusSpy );
  146. expect( getModelData( doc ) ).to.equal( '[<widget></widget>]' );
  147. } );
  148. it( 'should apply fake view selection if model selection is on widget element', () => {
  149. setModelData( doc, '[<widget>foo bar</widget>]' );
  150. expect( getViewData( viewDocument ) ).to.equal(
  151. '[<div class="ck-widget ck-widget_selected" contenteditable="false">foo bar<b></b></div>]'
  152. );
  153. expect( viewDocument.selection.isFake ).to.be.true;
  154. } );
  155. it( 'should use element\'s label to set fake selection if one is provided', () => {
  156. setModelData( doc, '[<widget>foo bar</widget>]' );
  157. expect( viewDocument.selection.fakeSelectionLabel ).to.equal( 'element label' );
  158. } );
  159. it( 'should add selected class when no only a widget is selected', () => {
  160. setModelData( doc, '[<paragraph>foo</paragraph><widget></widget><widget></widget>]' );
  161. expect( viewDocument.selection.isFake ).to.be.false;
  162. expect( getViewData( viewDocument ) ).to.equal(
  163. '[' +
  164. '<p>foo</p>' +
  165. '<div class="ck-widget ck-widget_selected" contenteditable="false"><b></b></div>' +
  166. '<div class="ck-widget ck-widget_selected" contenteditable="false"><b></b></div>' +
  167. ']'
  168. );
  169. } );
  170. it( 'fake selection should be empty if widget is not selected', () => {
  171. setModelData( doc, '<paragraph>foo</paragraph><widget>foo bar</widget>' );
  172. expect( viewDocument.selection.fakeSelectionLabel ).to.equal( '' );
  173. } );
  174. it( 'should toggle selected class', () => {
  175. setModelData( doc, '<paragraph>foo</paragraph>[<widget>foo</widget>]' );
  176. expect( getViewData( viewDocument ) ).to.equal(
  177. '<p>foo</p>[<div class="ck-widget ck-widget_selected" contenteditable="false">foo<b></b></div>]'
  178. );
  179. doc.enqueueChanges( () => {
  180. doc.selection.removeAllRanges();
  181. } );
  182. expect( getViewData( viewDocument ) ).to.equal(
  183. '<p>{}foo</p><div class="ck-widget" contenteditable="false">foo<b></b></div>'
  184. );
  185. } );
  186. it( 'should do nothing when selection is placed in other editable', () => {
  187. setModelData( doc, '<widget><editable>foo bar</editable></widget><editable>[baz]</editable>' );
  188. expect( getViewData( viewDocument ) ).to.equal(
  189. '<div class="ck-widget" contenteditable="false">' +
  190. '<figcaption contenteditable="true">foo bar</figcaption>' +
  191. '<b></b>' +
  192. '</div>' +
  193. '<figcaption contenteditable="true">{baz}</figcaption>'
  194. );
  195. } );
  196. describe( 'keys handling', () => {
  197. describe( 'delete and backspace', () => {
  198. test(
  199. 'should select widget when backspace is pressed',
  200. '<widget></widget><paragraph>[]foo</paragraph>',
  201. keyCodes.backspace,
  202. '[<widget></widget>]<paragraph>foo</paragraph>'
  203. );
  204. test(
  205. 'should remove empty element after selecting widget when backspace is pressed',
  206. '<widget></widget><paragraph>[]</paragraph>',
  207. keyCodes.backspace,
  208. '[<widget></widget>]'
  209. );
  210. test(
  211. 'should select widget when delete is pressed',
  212. '<paragraph>foo[]</paragraph><widget></widget>',
  213. keyCodes.delete,
  214. '<paragraph>foo</paragraph>[<widget></widget>]'
  215. );
  216. test(
  217. 'should remove empty element after selecting widget when delete is pressed',
  218. '<paragraph>[]</paragraph><widget></widget>',
  219. keyCodes.delete,
  220. '[<widget></widget>]'
  221. );
  222. test(
  223. 'should not respond to other keys',
  224. '<widget></widget><paragraph>[]foo</paragraph>',
  225. 65,
  226. '<widget></widget><paragraph>[]foo</paragraph>'
  227. );
  228. test(
  229. 'should do nothing on non-collapsed selection',
  230. '<widget></widget><paragraph>[f]oo</paragraph>',
  231. keyCodes.backspace,
  232. '<widget></widget><paragraph>[f]oo</paragraph>'
  233. );
  234. test(
  235. 'should do nothing on non-object elements',
  236. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>',
  237. keyCodes.backspace,
  238. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>'
  239. );
  240. test(
  241. 'should work correctly with modifier key: backspace + ctrl',
  242. '<widget></widget><paragraph>[]foo</paragraph>',
  243. { keyCode: keyCodes.backspace, ctrlKey: true },
  244. '[<widget></widget>]<paragraph>foo</paragraph>'
  245. );
  246. test(
  247. 'should work correctly with modifier key: backspace + alt',
  248. '<widget></widget><paragraph>[]foo</paragraph>',
  249. { keyCode: keyCodes.backspace, altKey: true },
  250. '[<widget></widget>]<paragraph>foo</paragraph>'
  251. );
  252. test(
  253. 'should work correctly with modifier key: backspace + shift',
  254. '<widget></widget><paragraph>[]foo</paragraph>',
  255. { keyCode: keyCodes.backspace, shiftKey: true },
  256. '[<widget></widget>]<paragraph>foo</paragraph>'
  257. );
  258. test(
  259. 'should work correctly with modifier key: delete + ctrl',
  260. '<paragraph>foo[]</paragraph><widget></widget>',
  261. { keyCode: keyCodes.delete, ctrlKey: true },
  262. '<paragraph>foo</paragraph>[<widget></widget>]'
  263. );
  264. test(
  265. 'should work correctly with modifier key: delete + alt',
  266. '<paragraph>foo[]</paragraph><widget></widget>',
  267. { keyCode: keyCodes.delete, altKey: true },
  268. '<paragraph>foo</paragraph>[<widget></widget>]'
  269. );
  270. test(
  271. 'should work correctly with modifier key: delete + shift',
  272. '<paragraph>foo[]</paragraph><widget></widget>',
  273. { keyCode: keyCodes.delete, shiftKey: true },
  274. '<paragraph>foo</paragraph>[<widget></widget>]'
  275. );
  276. test(
  277. 'should not modify backspace default behaviour in single paragraph boundaries',
  278. '<paragraph>[]foo</paragraph>',
  279. keyCodes.backspace,
  280. '<paragraph>[]foo</paragraph>'
  281. );
  282. test(
  283. 'should not modify delete default behaviour in single paragraph boundaries',
  284. '<paragraph>foo[]</paragraph>',
  285. keyCodes.delete,
  286. '<paragraph>foo[]</paragraph>'
  287. );
  288. test(
  289. 'should do nothing on selected widget preceded by a paragraph - backspace',
  290. '<paragraph>foo</paragraph>[<widget></widget>]',
  291. keyCodes.backspace,
  292. '<paragraph>foo</paragraph>[<widget></widget>]'
  293. );
  294. test(
  295. 'should do nothing on selected widget preceded by another widget - backspace',
  296. '<widget></widget>[<widget></widget>]',
  297. keyCodes.backspace,
  298. '<widget></widget>[<widget></widget>]'
  299. );
  300. test(
  301. 'should do nothing on selected widget before paragraph - backspace',
  302. '[<widget></widget>]<paragraph>foo</paragraph>',
  303. keyCodes.backspace,
  304. '[<widget></widget>]<paragraph>foo</paragraph>'
  305. );
  306. test(
  307. 'should do nothing on selected widget before another widget - backspace',
  308. '[<widget></widget>]<widget></widget>',
  309. keyCodes.backspace,
  310. '[<widget></widget>]<widget></widget>'
  311. );
  312. test(
  313. 'should do nothing on selected widget between paragraphs - backspace',
  314. '<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>',
  315. keyCodes.backspace,
  316. '<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>'
  317. );
  318. test(
  319. 'should do nothing on selected widget between other widgets - backspace',
  320. '<widget></widget>[<widget></widget>]<widget></widget>',
  321. keyCodes.backspace,
  322. '<widget></widget>[<widget></widget>]<widget></widget>'
  323. );
  324. test(
  325. 'should do nothing on selected widget preceded by a paragraph - delete',
  326. '<paragraph>foo</paragraph>[<widget></widget>]',
  327. keyCodes.delete,
  328. '<paragraph>foo</paragraph>[<widget></widget>]'
  329. );
  330. test(
  331. 'should do nothing on selected widget preceded by another widget - delete',
  332. '<widget></widget>[<widget></widget>]',
  333. keyCodes.delete,
  334. '<widget></widget>[<widget></widget>]'
  335. );
  336. test(
  337. 'should do nothing on selected widget before paragraph - delete',
  338. '[<widget></widget>]<paragraph>foo</paragraph>',
  339. keyCodes.delete,
  340. '[<widget></widget>]<paragraph>foo</paragraph>'
  341. );
  342. test(
  343. 'should do nothing on selected widget before another widget - delete',
  344. '[<widget></widget>]<widget></widget>',
  345. keyCodes.delete,
  346. '[<widget></widget>]<widget></widget>'
  347. );
  348. test(
  349. 'should do nothing on selected widget between paragraphs - delete',
  350. '<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>',
  351. keyCodes.delete,
  352. '<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>'
  353. );
  354. test(
  355. 'should do nothing on selected widget between other widgets - delete',
  356. '<widget></widget>[<widget></widget>]<widget></widget>',
  357. keyCodes.delete,
  358. '<widget></widget>[<widget></widget>]<widget></widget>'
  359. );
  360. test(
  361. 'should select inline objects - backspace',
  362. '<paragraph>foo<inline></inline>[]bar</paragraph>',
  363. keyCodes.backspace,
  364. '<paragraph>foo[<inline></inline>]bar</paragraph>'
  365. );
  366. test(
  367. 'should select inline objects - delete',
  368. '<paragraph>foo[]<inline></inline>bar</paragraph>',
  369. keyCodes.delete,
  370. '<paragraph>foo[<inline></inline>]bar</paragraph>'
  371. );
  372. test(
  373. 'should do nothing on selected inline objects - backspace',
  374. '<paragraph>foo[<inline></inline>]bar</paragraph>',
  375. keyCodes.backspace,
  376. '<paragraph>foo[<inline></inline>]bar</paragraph>'
  377. );
  378. test(
  379. 'should do nothing on selected inline objects - delete',
  380. '<paragraph>foo[<inline></inline>]bar</paragraph>',
  381. keyCodes.delete,
  382. '<paragraph>foo[<inline></inline>]bar</paragraph>'
  383. );
  384. test(
  385. 'should do nothing if selection is placed after first letter - backspace',
  386. '<paragraph>a[]</paragraph>',
  387. keyCodes.backspace,
  388. '<paragraph>a[]</paragraph>'
  389. );
  390. test(
  391. 'should do nothing if selection is placed before first letter - delete',
  392. '<paragraph>[]a</paragraph>',
  393. keyCodes.delete,
  394. '<paragraph>[]a</paragraph>'
  395. );
  396. it( 'should prevent default behaviour and stop event propagation', () => {
  397. const keydownHandler = sinon.spy();
  398. const domEventDataMock = {
  399. keyCode: keyCodes.delete,
  400. preventDefault: sinon.spy(),
  401. };
  402. setModelData( doc, '<paragraph>foo[]</paragraph><widget></widget>' );
  403. viewDocument.on( 'keydown', keydownHandler );
  404. viewDocument.fire( 'keydown', domEventDataMock );
  405. expect( getModelData( doc ) ).to.equal( '<paragraph>foo</paragraph>[<widget></widget>]' );
  406. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  407. sinon.assert.notCalled( keydownHandler );
  408. } );
  409. test(
  410. 'should remove the entire empty element if it is next to a widget',
  411. '<paragraph>foo</paragraph>' +
  412. '<image></image>' +
  413. '<blockQuote><paragraph>[]</paragraph></blockQuote>' +
  414. '<paragraph>foo</paragraph>',
  415. keyCodes.backspace,
  416. '<paragraph>foo</paragraph>[<image></image>]<paragraph>foo</paragraph>'
  417. );
  418. test(
  419. 'should remove the entire empty element (deeper structure) if it is next to a widget',
  420. '<paragraph>foo</paragraph>' +
  421. '<image></image>' +
  422. '<blockQuote><div><div><paragraph>[]</paragraph></div></div></blockQuote>' +
  423. '<paragraph>foo</paragraph>',
  424. keyCodes.backspace,
  425. '<paragraph>foo</paragraph>' +
  426. '[<image></image>]' +
  427. '<paragraph>foo</paragraph>'
  428. );
  429. test(
  430. 'should remove the entire empty element (deeper structure) if it is next to a widget (forward delete)',
  431. '<paragraph>foo</paragraph>' +
  432. '<blockQuote><div><div><paragraph>[]</paragraph></div></div></blockQuote>' +
  433. '<image></image>' +
  434. '<paragraph>foo</paragraph>',
  435. keyCodes.delete,
  436. '<paragraph>foo</paragraph>' +
  437. '[<image></image>]' +
  438. '<paragraph>foo</paragraph>'
  439. );
  440. test(
  441. 'should not remove the entire element which is not empty and the element is next to a widget',
  442. '<paragraph>foo</paragraph>' +
  443. '<image></image>' +
  444. '<blockQuote><paragraph>[]</paragraph><paragraph></paragraph></blockQuote>' +
  445. '<paragraph>foo</paragraph>',
  446. keyCodes.backspace,
  447. '<paragraph>foo</paragraph>' +
  448. '[<image></image>]' +
  449. '<blockQuote><paragraph></paragraph></blockQuote>' +
  450. '<paragraph>foo</paragraph>'
  451. );
  452. test(
  453. 'should not remove the entire element which is not empty and the element is next to a widget (forward delete)',
  454. '<paragraph>foo</paragraph>' +
  455. '<blockQuote><paragraph>Foo</paragraph><paragraph>[]</paragraph></blockQuote>' +
  456. '<image></image>' +
  457. '<paragraph>foo</paragraph>',
  458. keyCodes.delete,
  459. '<paragraph>foo</paragraph>' +
  460. '<blockQuote><paragraph>Foo</paragraph></blockQuote>' +
  461. '[<image></image>]' +
  462. '<paragraph>foo</paragraph>'
  463. );
  464. test(
  465. 'should not remove the entire element (deeper structure) which is not empty and the element is next to a widget',
  466. '<paragraph>foo</paragraph>' +
  467. '<image></image>' +
  468. '<blockQuote>' +
  469. '<div>' +
  470. '<div>' +
  471. '<paragraph>[]</paragraph>' +
  472. '</div>' +
  473. '</div>' +
  474. '<paragraph></paragraph>' +
  475. '</blockQuote>' +
  476. '<paragraph>foo</paragraph>',
  477. keyCodes.backspace,
  478. '<paragraph>foo</paragraph>' +
  479. '[<image></image>]' +
  480. '<blockQuote>' +
  481. '<paragraph></paragraph>' +
  482. '</blockQuote>' +
  483. '<paragraph>foo</paragraph>'
  484. );
  485. test(
  486. 'should do nothing if the nested element is not empty and the element is next to a widget',
  487. '<paragraph>foo</paragraph>' +
  488. '<image></image>' +
  489. '<blockQuote>' +
  490. '<div>' +
  491. '<div>' +
  492. '<paragraph>Foo[]</paragraph>' +
  493. '</div>' +
  494. '</div>' +
  495. '</blockQuote>' +
  496. '<paragraph>foo</paragraph>',
  497. keyCodes.backspace,
  498. '<paragraph>foo</paragraph>' +
  499. '<image></image>' +
  500. '<blockQuote>' +
  501. '<div>' +
  502. '<div>' +
  503. '<paragraph>Foo[]</paragraph>' +
  504. '</div>' +
  505. '</div>' +
  506. '</blockQuote>' +
  507. '<paragraph>foo</paragraph>'
  508. );
  509. it( 'does nothing when editor when read only mode is enabled (delete)', () => {
  510. setModelData( doc,
  511. '<paragraph>foo</paragraph>' +
  512. '<image></image>' +
  513. '<blockQuote><paragraph>[]</paragraph></blockQuote>' +
  514. '<paragraph>foo</paragraph>'
  515. );
  516. editor.isReadOnly = true;
  517. viewDocument.fire( 'keydown', new DomEventData(
  518. viewDocument,
  519. { target: document.createElement( 'div' ), preventDefault: () => {} },
  520. { keyCode: keyCodes.backspace }
  521. ) );
  522. expect( getModelData( doc ) ).to.equal(
  523. '<paragraph>foo</paragraph>' +
  524. '<image></image>' +
  525. '<blockQuote><paragraph>[]</paragraph></blockQuote>' +
  526. '<paragraph>foo</paragraph>'
  527. );
  528. } );
  529. it( 'does nothing when editor when read only mode is enabled (forward delete)', () => {
  530. setModelData( doc,
  531. '<paragraph>foo</paragraph>' +
  532. '<blockQuote><paragraph>[]</paragraph></blockQuote>' +
  533. '<image></image>' +
  534. '<paragraph>foo</paragraph>'
  535. );
  536. editor.isReadOnly = true;
  537. viewDocument.fire( 'keydown', new DomEventData(
  538. viewDocument,
  539. { target: document.createElement( 'div' ), preventDefault: () => {} },
  540. { keyCode: keyCodes.delete }
  541. ) );
  542. expect( getModelData( doc ) ).to.equal(
  543. '<paragraph>foo</paragraph>' +
  544. '<blockQuote><paragraph>[]</paragraph></blockQuote>' +
  545. '<image></image>' +
  546. '<paragraph>foo</paragraph>'
  547. );
  548. } );
  549. } );
  550. describe( 'arrows', () => {
  551. test(
  552. 'should move selection forward from selected object - right arrow',
  553. '[<widget></widget>]<paragraph>foo</paragraph>',
  554. keyCodes.arrowright,
  555. '<widget></widget><paragraph>[]foo</paragraph>'
  556. );
  557. test(
  558. 'should move selection forward from selected object - down arrow',
  559. '[<widget></widget>]<paragraph>foo</paragraph>',
  560. keyCodes.arrowdown,
  561. '<widget></widget><paragraph>[]foo</paragraph>'
  562. );
  563. test(
  564. 'should move selection backward from selected object - left arrow',
  565. '<paragraph>foo</paragraph>[<widget></widget>]',
  566. keyCodes.arrowleft,
  567. '<paragraph>foo[]</paragraph><widget></widget>'
  568. );
  569. test(
  570. 'should move selection backward from selected object - up arrow',
  571. '<paragraph>foo</paragraph>[<widget></widget>]',
  572. keyCodes.arrowup,
  573. '<paragraph>foo[]</paragraph><widget></widget>'
  574. );
  575. test(
  576. 'should move selection to next widget - right arrow',
  577. '[<widget></widget>]<widget></widget>',
  578. keyCodes.arrowright,
  579. '<widget></widget>[<widget></widget>]'
  580. );
  581. test(
  582. 'should move selection to next widget - down arrow',
  583. '[<widget></widget>]<widget></widget>',
  584. keyCodes.arrowdown,
  585. '<widget></widget>[<widget></widget>]'
  586. );
  587. test(
  588. 'should move selection to previous widget - left arrow',
  589. '<widget></widget>[<widget></widget>]',
  590. keyCodes.arrowleft,
  591. '[<widget></widget>]<widget></widget>'
  592. );
  593. test(
  594. 'should move selection to previous widget - up arrow',
  595. '<widget></widget>[<widget></widget>]',
  596. keyCodes.arrowup,
  597. '[<widget></widget>]<widget></widget>'
  598. );
  599. test(
  600. 'should do nothing on non-collapsed selection next to object - right arrow',
  601. '<paragraph>ba[r]</paragraph><widget></widget>',
  602. keyCodes.arrowright,
  603. '<paragraph>ba[r]</paragraph><widget></widget>'
  604. );
  605. test(
  606. 'should do nothing on non-collapsed selection next to object - down arrow',
  607. '<paragraph>ba[r]</paragraph><widget></widget>',
  608. keyCodes.arrowdown,
  609. '<paragraph>ba[r]</paragraph><widget></widget>'
  610. );
  611. test(
  612. 'should do nothing on non-collapsed selection next to object - left arrow',
  613. '<widget></widget><paragraph>[b]ar</paragraph>',
  614. keyCodes.arrowleft,
  615. '<widget></widget><paragraph>[b]ar</paragraph>'
  616. );
  617. test(
  618. 'should do nothing on non-collapsed selection next to object - up arrow',
  619. '<widget></widget><paragraph>[b]ar</paragraph>',
  620. keyCodes.arrowup,
  621. '<widget></widget><paragraph>[b]ar</paragraph>'
  622. );
  623. test(
  624. 'should not move selection if there is no correct location - right arrow',
  625. '<paragraph>foo</paragraph>[<widget></widget>]',
  626. keyCodes.arrowright,
  627. '<paragraph>foo</paragraph>[<widget></widget>]'
  628. );
  629. test(
  630. 'should not move selection if there is no correct location - down arrow',
  631. '<paragraph>foo</paragraph>[<widget></widget>]',
  632. keyCodes.arrowdown,
  633. '<paragraph>foo</paragraph>[<widget></widget>]'
  634. );
  635. test(
  636. 'should not move selection if there is no correct location - left arrow',
  637. '[<widget></widget>]<paragraph>foo</paragraph>',
  638. keyCodes.arrowleft,
  639. '[<widget></widget>]<paragraph>foo</paragraph>'
  640. );
  641. test(
  642. 'should not move selection if there is no correct location - up arrow',
  643. '[<widget></widget>]<paragraph>foo</paragraph>',
  644. keyCodes.arrowup,
  645. '[<widget></widget>]<paragraph>foo</paragraph>'
  646. );
  647. it( 'should prevent default behaviour when there is no correct location - document end', () => {
  648. const keydownHandler = sinon.spy();
  649. const domEventDataMock = {
  650. keyCode: keyCodes.arrowright,
  651. preventDefault: sinon.spy(),
  652. };
  653. setModelData( doc, '<paragraph>foo</paragraph>[<widget></widget>]' );
  654. viewDocument.on( 'keydown', keydownHandler );
  655. viewDocument.fire( 'keydown', domEventDataMock );
  656. expect( getModelData( doc ) ).to.equal( '<paragraph>foo</paragraph>[<widget></widget>]' );
  657. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  658. sinon.assert.notCalled( keydownHandler );
  659. } );
  660. it( 'should prevent default behaviour when there is no correct location - document start', () => {
  661. const keydownHandler = sinon.spy();
  662. const domEventDataMock = {
  663. keyCode: keyCodes.arrowleft,
  664. preventDefault: sinon.spy(),
  665. };
  666. setModelData( doc, '[<widget></widget>]<paragraph>foo</paragraph>' );
  667. viewDocument.on( 'keydown', keydownHandler );
  668. viewDocument.fire( 'keydown', domEventDataMock );
  669. expect( getModelData( doc ) ).to.equal( '[<widget></widget>]<paragraph>foo</paragraph>' );
  670. sinon.assert.calledOnce( domEventDataMock.preventDefault );
  671. sinon.assert.notCalled( keydownHandler );
  672. } );
  673. test(
  674. 'should move selection to object element - right arrow',
  675. '<paragraph>foo[]</paragraph><widget></widget>',
  676. keyCodes.arrowright,
  677. '<paragraph>foo</paragraph>[<widget></widget>]'
  678. );
  679. test(
  680. 'should move selection to object element - down arrow',
  681. '<paragraph>foo[]</paragraph><widget></widget>',
  682. keyCodes.arrowdown,
  683. '<paragraph>foo</paragraph>[<widget></widget>]'
  684. );
  685. test(
  686. 'should move selection to object element - left arrow',
  687. '<widget></widget><paragraph>[]foo</paragraph>',
  688. keyCodes.arrowleft,
  689. '[<widget></widget>]<paragraph>foo</paragraph>'
  690. );
  691. test(
  692. 'should move selection to object element - up arrow',
  693. '<widget></widget><paragraph>[]foo</paragraph>',
  694. keyCodes.arrowup,
  695. '[<widget></widget>]<paragraph>foo</paragraph>'
  696. );
  697. test(
  698. 'do nothing on non objects - right arrow',
  699. '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>',
  700. keyCodes.arrowright,
  701. '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>'
  702. );
  703. test(
  704. 'do nothing on non objects - down arrow',
  705. '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>',
  706. keyCodes.arrowdown,
  707. '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>'
  708. );
  709. test(
  710. 'do nothing on non objects - left arrow',
  711. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>',
  712. keyCodes.arrowleft,
  713. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>'
  714. );
  715. test(
  716. 'do nothing on non objects - up arrow',
  717. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>',
  718. keyCodes.arrowup,
  719. '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>'
  720. );
  721. test(
  722. 'should work correctly with modifier key: right arrow + ctrl',
  723. '[<widget></widget>]<paragraph>foo</paragraph>',
  724. { keyCode: keyCodes.arrowright, ctrlKey: true },
  725. '<widget></widget><paragraph>[]foo</paragraph>'
  726. );
  727. test(
  728. 'should work correctly with modifier key: right arrow + alt',
  729. '[<widget></widget>]<paragraph>foo</paragraph>',
  730. { keyCode: keyCodes.arrowright, altKey: true },
  731. '<widget></widget><paragraph>[]foo</paragraph>'
  732. );
  733. test(
  734. 'should work correctly with modifier key: right arrow + shift',
  735. '[<widget></widget>]<paragraph>foo</paragraph>',
  736. { keyCode: keyCodes.arrowright, shiftKey: true },
  737. '<widget></widget><paragraph>[]foo</paragraph>'
  738. );
  739. test(
  740. 'should work correctly with modifier key: down arrow + ctrl',
  741. '[<widget></widget>]<paragraph>foo</paragraph>',
  742. { keyCode: keyCodes.arrowdown, ctrlKey: true },
  743. '<widget></widget><paragraph>[]foo</paragraph>'
  744. );
  745. test(
  746. 'should work correctly with modifier key: down arrow + alt',
  747. '[<widget></widget>]<paragraph>foo</paragraph>',
  748. { keyCode: keyCodes.arrowdown, altKey: true },
  749. '<widget></widget><paragraph>[]foo</paragraph>'
  750. );
  751. test(
  752. 'should work correctly with modifier key: down arrow + shift',
  753. '[<widget></widget>]<paragraph>foo</paragraph>',
  754. { keyCode: keyCodes.arrowdown, shiftKey: true },
  755. '<widget></widget><paragraph>[]foo</paragraph>'
  756. );
  757. test(
  758. 'should work correctly with modifier key: left arrow + ctrl',
  759. '<paragraph>foo</paragraph>[<widget></widget>]',
  760. { keyCode: keyCodes.arrowleft, ctrlKey: true },
  761. '<paragraph>foo[]</paragraph><widget></widget>'
  762. );
  763. test(
  764. 'should work correctly with modifier key: left arrow + alt',
  765. '<paragraph>foo</paragraph>[<widget></widget>]',
  766. { keyCode: keyCodes.arrowleft, altKey: true },
  767. '<paragraph>foo[]</paragraph><widget></widget>'
  768. );
  769. test(
  770. 'should work correctly with modifier key: left arrow + shift',
  771. '<paragraph>foo</paragraph>[<widget></widget>]',
  772. { keyCode: keyCodes.arrowleft, shiftKey: true },
  773. '<paragraph>foo[]</paragraph><widget></widget>'
  774. );
  775. test(
  776. 'should work correctly with modifier key: up arrow + ctrl',
  777. '<paragraph>foo</paragraph>[<widget></widget>]',
  778. { keyCode: keyCodes.arrowup, ctrlKey: true },
  779. '<paragraph>foo[]</paragraph><widget></widget>'
  780. );
  781. test(
  782. 'should work correctly with modifier key: up arrow + alt',
  783. '<paragraph>foo</paragraph>[<widget></widget>]',
  784. { keyCode: keyCodes.arrowup, altKey: true },
  785. '<paragraph>foo[]</paragraph><widget></widget>'
  786. );
  787. test(
  788. 'should work correctly with modifier key: up arrow + shift',
  789. '<paragraph>foo</paragraph>[<widget></widget>]',
  790. { keyCode: keyCodes.arrowup, shiftKey: true },
  791. '<paragraph>foo[]</paragraph><widget></widget>'
  792. );
  793. test(
  794. 'should do nothing if there is more than one selection in model',
  795. '<paragraph>[foo]</paragraph><widget></widget><paragraph>[bar]</paragraph>',
  796. keyCodes.arrowright,
  797. '<paragraph>[foo]</paragraph><widget></widget><paragraph>[bar]</paragraph>'
  798. );
  799. test(
  800. 'should work if selection is in nested element (left arrow)',
  801. '<paragraph>foo</paragraph>' +
  802. '<image></image>' +
  803. '<blockQuote>' +
  804. '<div>' +
  805. '<div>' +
  806. '<paragraph>[]</paragraph>' +
  807. '</div>' +
  808. '</div>' +
  809. '</blockQuote>' +
  810. '<paragraph>foo</paragraph>',
  811. keyCodes.arrowleft,
  812. '<paragraph>foo</paragraph>' +
  813. '[<image></image>]' +
  814. '<blockQuote>' +
  815. '<div>' +
  816. '<div>' +
  817. '<paragraph></paragraph>' +
  818. '</div>' +
  819. '</div>' +
  820. '</blockQuote>' +
  821. '<paragraph>foo</paragraph>'
  822. );
  823. test(
  824. 'should work if selection is in nested element (up arrow)',
  825. '<paragraph>foo</paragraph>' +
  826. '<image></image>' +
  827. '<blockQuote>' +
  828. '<div>' +
  829. '<div>' +
  830. '<paragraph>[]</paragraph>' +
  831. '</div>' +
  832. '</div>' +
  833. '</blockQuote>' +
  834. '<paragraph>foo</paragraph>',
  835. keyCodes.arrowup,
  836. '<paragraph>foo</paragraph>' +
  837. '[<image></image>]' +
  838. '<blockQuote>' +
  839. '<div>' +
  840. '<div>' +
  841. '<paragraph></paragraph>' +
  842. '</div>' +
  843. '</div>' +
  844. '</blockQuote>' +
  845. '<paragraph>foo</paragraph>'
  846. );
  847. test(
  848. 'should work if selection is in nested element (right arrow)',
  849. '<paragraph>foo</paragraph>' +
  850. '<blockQuote>' +
  851. '<div>' +
  852. '<div>' +
  853. '<paragraph>[]</paragraph>' +
  854. '</div>' +
  855. '</div>' +
  856. '</blockQuote>' +
  857. '<image></image>' +
  858. '<paragraph>foo</paragraph>',
  859. keyCodes.arrowright,
  860. '<paragraph>foo</paragraph>' +
  861. '<blockQuote>' +
  862. '<div>' +
  863. '<div>' +
  864. '<paragraph></paragraph>' +
  865. '</div>' +
  866. '</div>' +
  867. '</blockQuote>' +
  868. '[<image></image>]' +
  869. '<paragraph>foo</paragraph>'
  870. );
  871. test(
  872. 'should work if selection is in nested element (down arrow)',
  873. '<paragraph>foo</paragraph>' +
  874. '<blockQuote>' +
  875. '<div>' +
  876. '<div>' +
  877. '<paragraph>[]</paragraph>' +
  878. '</div>' +
  879. '</div>' +
  880. '</blockQuote>' +
  881. '<image></image>' +
  882. '<paragraph>foo</paragraph>',
  883. keyCodes.arrowdown,
  884. '<paragraph>foo</paragraph>' +
  885. '<blockQuote>' +
  886. '<div>' +
  887. '<div>' +
  888. '<paragraph></paragraph>' +
  889. '</div>' +
  890. '</div>' +
  891. '</blockQuote>' +
  892. '[<image></image>]' +
  893. '<paragraph>foo</paragraph>'
  894. );
  895. } );
  896. describe( 'Ctrl+A', () => {
  897. test(
  898. 'should select the entire content of the nested editable',
  899. '<widget><nested>foo[]</nested></widget><paragraph>bar</paragraph>',
  900. { keyCode: keyCodes.a, ctrlKey: true },
  901. '<widget><nested>[foo]</nested></widget><paragraph>bar</paragraph>'
  902. );
  903. test(
  904. 'should not change the selection if outside of the nested editable',
  905. '<widget><nested>foo</nested></widget><paragraph>[]bar</paragraph>',
  906. { keyCode: keyCodes.a, ctrlKey: true },
  907. '<widget><nested>foo</nested></widget><paragraph>[]bar</paragraph>'
  908. );
  909. test(
  910. 'should selected whole content when widget is selected',
  911. '<paragraph>foo</paragraph>[<widget></widget>]<paragraph>bar</paragraph>',
  912. { keyCode: keyCodes.a, ctrlKey: true },
  913. '[<paragraph>foo</paragraph><widget></widget><paragraph>bar</paragraph>]',
  914. '[<p>foo</p><div class="ck-widget ck-widget_selected" contenteditable="false"><b></b></div><p>bar</p>]'
  915. );
  916. } );
  917. function test( name, data, keyCodeOrMock, expected, expectedView ) {
  918. it( name, () => {
  919. const domEventDataMock = ( typeof keyCodeOrMock == 'object' ) ? keyCodeOrMock : {
  920. keyCode: keyCodeOrMock
  921. };
  922. setModelData( doc, data );
  923. viewDocument.fire( 'keydown', new DomEventData(
  924. viewDocument,
  925. { target: document.createElement( 'div' ), preventDefault: () => {} },
  926. domEventDataMock
  927. ) );
  928. expect( getModelData( doc ) ).to.equal( expected );
  929. if ( expectedView ) {
  930. expect( getViewData( viewDocument ) ).to.equal( expectedView );
  931. }
  932. } );
  933. }
  934. } );
  935. } );