verticalnavigation.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. import { toWidget, toWidgetEditable } from '../src/utils';
  6. import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  7. import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
  8. import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline';
  9. import Image from '@ckeditor/ckeditor5-image/src/image';
  10. import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
  11. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  12. import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
  13. import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  14. import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  15. import global from '@ckeditor/ckeditor5-utils/src/dom/global';
  16. import env from '@ckeditor/ckeditor5-utils/src/env';
  17. describe( 'Widget - vertical keyboard navigation near widgets', () => {
  18. let editorElement, editor, model, styleElement;
  19. let leftArrowDomEvtDataStub, rightArrowDomEvtDataStub, upArrowDomEvtDataStub, downArrowDomEvtDataStub;
  20. const imageUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAUCAQAAADRyVAeAAAAKklEQVR42u3PAQ0AAAwCI' +
  21. 'O0f+u/hoAHNZUJFRERERERERERERERERLYiD9N4FAFj2iK6AAAAAElFTkSuQmCC';
  22. beforeEach( async () => {
  23. editorElement = global.document.createElement( 'div' );
  24. global.document.body.appendChild( editorElement );
  25. editor = await ClassicTestEditor.create( editorElement, {
  26. plugins: [ Paragraph, Image, ImageCaption, HorizontalLine, BlockQuote, BlockWidgetWithNestedEditable ]
  27. } );
  28. model = editor.model;
  29. // The editing view must be focused because otherwise in Chrome the DOM selection will not contain
  30. // any ranges and jumpOverUiElement will crash (for the right arrow when shift is pressed).
  31. editor.editing.view.focus();
  32. leftArrowDomEvtDataStub = {
  33. keyCode: getCode( 'ArrowLeft' ),
  34. preventDefault: sinon.spy(),
  35. stopPropagation: sinon.spy(),
  36. domTarget: global.document.body
  37. };
  38. rightArrowDomEvtDataStub = {
  39. keyCode: getCode( 'ArrowRight' ),
  40. preventDefault: sinon.spy(),
  41. stopPropagation: sinon.spy(),
  42. domTarget: global.document.body
  43. };
  44. upArrowDomEvtDataStub = {
  45. keyCode: getCode( 'ArrowUp' ),
  46. preventDefault: sinon.spy(),
  47. stopPropagation: sinon.spy(),
  48. domTarget: global.document.body
  49. };
  50. downArrowDomEvtDataStub = {
  51. keyCode: getCode( 'ArrowDown' ),
  52. preventDefault: sinon.spy(),
  53. stopPropagation: sinon.spy(),
  54. domTarget: global.document.body
  55. };
  56. // Those tests are checking text line wrapping so forcing some sizes are needed to make those tests stable.
  57. // Some tests are excluded for Gecko because of differences in font rendering (text line wraps in different places).
  58. styleElement = global.document.createElement( 'style' );
  59. styleElement.appendChild( global.document.createTextNode(
  60. `
  61. * {
  62. font-size: 12px !important;
  63. font-family: serif !important;
  64. margin: 0 !important;
  65. padding: 0 !important;
  66. border: 0 !important
  67. }
  68. .ck.ck-editor__editable { width: 300px !important; }
  69. `
  70. ) );
  71. global.document.querySelector( 'head' ).appendChild( styleElement );
  72. } );
  73. afterEach( async () => {
  74. editorElement.remove();
  75. styleElement.remove();
  76. await editor.destroy();
  77. } );
  78. it( 'should do nothing if pressed left-arrow key', () => {
  79. setModelData( model,
  80. '<paragraph>foo</paragraph>' +
  81. '<paragraph>b[]ar</paragraph>' +
  82. '<paragraph>abc</paragraph>'
  83. );
  84. editor.editing.view.document.fire( 'keydown', leftArrowDomEvtDataStub );
  85. sinon.assert.notCalled( leftArrowDomEvtDataStub.preventDefault );
  86. sinon.assert.notCalled( leftArrowDomEvtDataStub.stopPropagation );
  87. assertEqualMarkup(
  88. getModelData( model ),
  89. '<paragraph>foo</paragraph>' +
  90. '<paragraph>b[]ar</paragraph>' +
  91. '<paragraph>abc</paragraph>'
  92. );
  93. } );
  94. it( 'should do nothing if pressed right-arrow key', () => {
  95. setModelData( model,
  96. '<paragraph>foo</paragraph>' +
  97. '<paragraph>b[]ar</paragraph>' +
  98. '<paragraph>abc</paragraph>'
  99. );
  100. editor.editing.view.document.fire( 'keydown', rightArrowDomEvtDataStub );
  101. sinon.assert.notCalled( rightArrowDomEvtDataStub.preventDefault );
  102. sinon.assert.notCalled( rightArrowDomEvtDataStub.stopPropagation );
  103. assertEqualMarkup(
  104. getModelData( model ),
  105. '<paragraph>foo</paragraph>' +
  106. '<paragraph>b[]ar</paragraph>' +
  107. '<paragraph>abc</paragraph>'
  108. );
  109. } );
  110. it( 'should do nothing if shrinking non-collapsed forward selection', () => {
  111. setModelData( model, '<paragraph>fo[ob]ar</paragraph>' );
  112. upArrowDomEvtDataStub.shiftKey = true;
  113. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  114. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  115. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  116. } );
  117. it( 'should do nothing if shrinking non-collapsed backward selection', () => {
  118. setModelData( model, '<paragraph>fo[ob]ar</paragraph>', { lastRangeBackward: true } );
  119. downArrowDomEvtDataStub.shiftKey = true;
  120. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  121. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  122. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  123. } );
  124. describe( 'with selection inside root content editable', () => {
  125. describe( 'single paragraph surrounded with objects', () => {
  126. describe( 'collapsed selection', () => {
  127. beforeEach( () => {
  128. setModelData( model,
  129. '<horizontalLine></horizontalLine>' +
  130. '<paragraph>foo[]bar</paragraph>' +
  131. '<horizontalLine></horizontalLine>'
  132. );
  133. } );
  134. it( 'should move caret to the position closest to object (navigating forward)', () => {
  135. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  136. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  137. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  138. assertEqualMarkup( getModelData( model ),
  139. '<horizontalLine></horizontalLine>' +
  140. '<paragraph>foobar[]</paragraph>' +
  141. '<horizontalLine></horizontalLine>'
  142. );
  143. } );
  144. it( 'should move caret to the position closest to object (navigating backward)', () => {
  145. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  146. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  147. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  148. assertEqualMarkup( getModelData( model ),
  149. '<horizontalLine></horizontalLine>' +
  150. '<paragraph>[]foobar</paragraph>' +
  151. '<horizontalLine></horizontalLine>'
  152. );
  153. } );
  154. } );
  155. describe( 'non-collapsed forward selection', () => {
  156. beforeEach( () => {
  157. setModelData( model,
  158. '<horizontalLine></horizontalLine>' +
  159. '<paragraph>fo[ob]ar</paragraph>' +
  160. '<horizontalLine></horizontalLine>'
  161. );
  162. } );
  163. it( 'should move caret to the position closest to object (navigating forward)', () => {
  164. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  165. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  166. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  167. assertEqualMarkup( getModelData( model ),
  168. '<horizontalLine></horizontalLine>' +
  169. '<paragraph>foobar[]</paragraph>' +
  170. '<horizontalLine></horizontalLine>'
  171. );
  172. } );
  173. it( 'should move caret to the position closest to object (navigating backward)', () => {
  174. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  175. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  176. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  177. assertEqualMarkup( getModelData( model ),
  178. '<horizontalLine></horizontalLine>' +
  179. '<paragraph>[]foobar</paragraph>' +
  180. '<horizontalLine></horizontalLine>'
  181. );
  182. } );
  183. describe( 'with shift pressed', () => {
  184. it( 'should expand the selection to the position closest to object (navigating forward)', () => {
  185. downArrowDomEvtDataStub.shiftKey = true;
  186. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  187. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  188. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  189. assertEqualMarkup( getModelData( model ),
  190. '<horizontalLine></horizontalLine>' +
  191. '<paragraph>fo[obar]</paragraph>' +
  192. '<horizontalLine></horizontalLine>'
  193. );
  194. } );
  195. it( 'should not prevent default browser behavior while navigating backward', () => {
  196. upArrowDomEvtDataStub.shiftKey = true;
  197. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  198. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  199. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  200. } );
  201. } );
  202. } );
  203. describe( 'non-collapsed backward selection', () => {
  204. beforeEach( () => {
  205. setModelData( model,
  206. '<horizontalLine></horizontalLine>' +
  207. '<paragraph>fo[ob]ar</paragraph>' +
  208. '<horizontalLine></horizontalLine>',
  209. { lastRangeBackward: true }
  210. );
  211. } );
  212. it( 'should move caret to the position closest to object (navigating forward)', () => {
  213. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  214. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  215. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  216. assertEqualMarkup( getModelData( model ),
  217. '<horizontalLine></horizontalLine>' +
  218. '<paragraph>foobar[]</paragraph>' +
  219. '<horizontalLine></horizontalLine>'
  220. );
  221. } );
  222. it( 'should move caret to the position closest to object (navigating backward)', () => {
  223. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  224. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  225. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  226. assertEqualMarkup( getModelData( model ),
  227. '<horizontalLine></horizontalLine>' +
  228. '<paragraph>[]foobar</paragraph>' +
  229. '<horizontalLine></horizontalLine>'
  230. );
  231. } );
  232. describe( 'with shift pressed', () => {
  233. it( 'should not prevent default browser behavior while navigating forward', () => {
  234. downArrowDomEvtDataStub.shiftKey = true;
  235. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  236. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  237. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  238. } );
  239. it( 'should expand the selection to the position closest to object (navigating backward)', () => {
  240. upArrowDomEvtDataStub.shiftKey = true;
  241. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  242. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  243. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  244. assertEqualMarkup( getModelData( model ),
  245. '<horizontalLine></horizontalLine>' +
  246. '<paragraph>[foob]ar</paragraph>' +
  247. '<horizontalLine></horizontalLine>'
  248. );
  249. } );
  250. } );
  251. } );
  252. } );
  253. describe( 'multiple paragraphs with object inside', () => {
  254. describe( 'caret in the first paragraph', () => {
  255. beforeEach( () => {
  256. setModelData( model,
  257. '<paragraph>fo[]oo</paragraph>' +
  258. '<paragraph>bar</paragraph>' +
  259. '<horizontalLine></horizontalLine>' +
  260. '<paragraph>FOO</paragraph>' +
  261. '<paragraph>BAR</paragraph>'
  262. );
  263. } );
  264. it( 'should not prevent default on forward navigation', () => {
  265. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  266. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  267. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  268. } );
  269. it( 'should not prevent default on backward navigation', () => {
  270. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  271. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  272. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  273. } );
  274. } );
  275. describe( 'caret in the second paragraph', () => {
  276. beforeEach( () => {
  277. setModelData( model,
  278. '<paragraph>foo</paragraph>' +
  279. '<paragraph>ba[]ar</paragraph>' +
  280. '<horizontalLine></horizontalLine>' +
  281. '<paragraph>FOO</paragraph>' +
  282. '<paragraph>BAR</paragraph>'
  283. );
  284. } );
  285. it( 'should move caret to the position closest to object on forward navigation', () => {
  286. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  287. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  288. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  289. assertEqualMarkup( getModelData( model ),
  290. '<paragraph>foo</paragraph>' +
  291. '<paragraph>baar[]</paragraph>' +
  292. '<horizontalLine></horizontalLine>' +
  293. '<paragraph>FOO</paragraph>' +
  294. '<paragraph>BAR</paragraph>'
  295. );
  296. } );
  297. it( 'should not prevent default on backward navigation', () => {
  298. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  299. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  300. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  301. } );
  302. } );
  303. describe( 'caret in the third paragraph', () => {
  304. beforeEach( () => {
  305. setModelData( model,
  306. '<paragraph>foo</paragraph>' +
  307. '<paragraph>bar</paragraph>' +
  308. '<horizontalLine></horizontalLine>' +
  309. '<paragraph>FO[]OO</paragraph>' +
  310. '<paragraph>BAR</paragraph>'
  311. );
  312. } );
  313. it( 'should not prevent default on forward navigation', () => {
  314. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  315. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  316. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  317. } );
  318. it( 'should move caret to the position closest to object on backward navigation', () => {
  319. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  320. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  321. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  322. assertEqualMarkup( getModelData( model ),
  323. '<paragraph>foo</paragraph>' +
  324. '<paragraph>bar</paragraph>' +
  325. '<horizontalLine></horizontalLine>' +
  326. '<paragraph>[]FOOO</paragraph>' +
  327. '<paragraph>BAR</paragraph>'
  328. );
  329. } );
  330. } );
  331. describe( 'caret in the forth paragraph', () => {
  332. beforeEach( () => {
  333. setModelData( model,
  334. '<paragraph>foo</paragraph>' +
  335. '<paragraph>bar</paragraph>' +
  336. '<horizontalLine></horizontalLine>' +
  337. '<paragraph>FOO</paragraph>' +
  338. '<paragraph>BA[]AR</paragraph>'
  339. );
  340. } );
  341. it( 'should not prevent default on backward navigation', () => {
  342. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  343. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  344. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  345. } );
  346. it( 'should not prevent default on forward navigation', () => {
  347. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  348. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  349. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  350. } );
  351. } );
  352. } );
  353. it( 'should integrate with the blockquote (forward navigation)', () => {
  354. setModelData( model,
  355. '<blockQuote>' +
  356. '<paragraph>f[]oo</paragraph>' +
  357. '</blockQuote>' +
  358. '<horizontalLine></horizontalLine>' +
  359. '<paragraph>bar</paragraph>'
  360. );
  361. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  362. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  363. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  364. assertEqualMarkup( getModelData( model ),
  365. '<blockQuote>' +
  366. '<paragraph>foo[]</paragraph>' +
  367. '</blockQuote>' +
  368. '<horizontalLine></horizontalLine>' +
  369. '<paragraph>bar</paragraph>'
  370. );
  371. } );
  372. it( 'should integrate with the blockquote (backward navigation)', () => {
  373. setModelData( model,
  374. '<paragraph>foo</paragraph>' +
  375. '<horizontalLine></horizontalLine>' +
  376. '<blockQuote>' +
  377. '<paragraph>ba[]r</paragraph>' +
  378. '</blockQuote>'
  379. );
  380. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  381. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  382. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  383. assertEqualMarkup( getModelData( model ),
  384. '<paragraph>foo</paragraph>' +
  385. '<horizontalLine></horizontalLine>' +
  386. '<blockQuote>' +
  387. '<paragraph>[]bar</paragraph>' +
  388. '</blockQuote>'
  389. );
  390. } );
  391. } );
  392. describe( 'with selection inside nested content editable', () => {
  393. describe( 'simple text content', () => {
  394. describe( 'with collapsed selection', () => {
  395. beforeEach( () => {
  396. setModelData( model, '<widget><nested><paragraph>foo[]bar</paragraph></nested></widget>' );
  397. } );
  398. it( 'should move caret to the beginning of the nested editable content', () => {
  399. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  400. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  401. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  402. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>[]foobar</paragraph></nested></widget>' );
  403. } );
  404. it( 'should move caret to the end of the nested editable content', () => {
  405. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  406. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  407. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  408. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>foobar[]</paragraph></nested></widget>' );
  409. } );
  410. describe( 'when shift key is pressed', () => {
  411. beforeEach( () => {
  412. upArrowDomEvtDataStub.shiftKey = true;
  413. downArrowDomEvtDataStub.shiftKey = true;
  414. } );
  415. it( 'should expand selection to the beginning of the nested editable content', () => {
  416. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  417. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  418. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  419. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>[foo]bar</paragraph></nested></widget>' );
  420. } );
  421. it( 'should expand selection to the end of the nested editable content', () => {
  422. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  423. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  424. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  425. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>foo[bar]</paragraph></nested></widget>' );
  426. } );
  427. } );
  428. } );
  429. describe( 'with non-collapsed forward selection', () => {
  430. beforeEach( () => {
  431. setModelData( model, '<widget><nested><paragraph>fo[ob]ar</paragraph></nested></widget>' );
  432. } );
  433. it( 'should move caret to the beginning of the nested editable content', () => {
  434. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  435. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  436. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  437. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>[]foobar</paragraph></nested></widget>' );
  438. } );
  439. it( 'should move caret to the end of the nested editable content', () => {
  440. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  441. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  442. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  443. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>foobar[]</paragraph></nested></widget>' );
  444. } );
  445. describe( 'when shift key is pressed', () => {
  446. beforeEach( () => {
  447. upArrowDomEvtDataStub.shiftKey = true;
  448. downArrowDomEvtDataStub.shiftKey = true;
  449. } );
  450. it( 'should not prevent default browser behavior on arrow up press', () => {
  451. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  452. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  453. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  454. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>fo[ob]ar</paragraph></nested></widget>' );
  455. } );
  456. it( 'should expand selection to the end of the nested editable content', () => {
  457. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  458. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  459. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  460. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>fo[obar]</paragraph></nested></widget>' );
  461. } );
  462. } );
  463. } );
  464. describe( 'with non-collapsed backward selection', () => {
  465. beforeEach( () => {
  466. setModelData( model, '<widget><nested><paragraph>fo[ob]ar</paragraph></nested></widget>', { lastRangeBackward: true } );
  467. } );
  468. it( 'should move caret to the beginning of the nested editable content', () => {
  469. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  470. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  471. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  472. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>[]foobar</paragraph></nested></widget>' );
  473. } );
  474. it( 'should move caret to the end of the nested editable content', () => {
  475. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  476. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  477. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  478. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>foobar[]</paragraph></nested></widget>' );
  479. } );
  480. describe( 'when shift key is pressed', () => {
  481. beforeEach( () => {
  482. upArrowDomEvtDataStub.shiftKey = true;
  483. downArrowDomEvtDataStub.shiftKey = true;
  484. } );
  485. it( 'should expand selection to the beginning of the nested editable content', () => {
  486. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  487. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  488. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  489. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>[foob]ar</paragraph></nested></widget>' );
  490. } );
  491. it( 'should not prevent default browser behavior on arrow down press', () => {
  492. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  493. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  494. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  495. assertEqualMarkup( getModelData( model ), '<widget><nested><paragraph>fo[ob]ar</paragraph></nested></widget>' );
  496. } );
  497. } );
  498. } );
  499. } );
  500. describe( 'selection inside paragraph', () => {
  501. const text = new Array( 20 ).fill( 0 ).map( () => 'word' ).join( ' ' );
  502. it( 'should not prevent default browser behavior if caret is in the middle line of a text', () => {
  503. setModelData( model, `<widget><nested><paragraph>${ text + '[] ' + text }</paragraph></nested></widget>` );
  504. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  505. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  506. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  507. } );
  508. it( 'should move caret to beginning of nested editable content if caret is in the first line of a text', () => {
  509. setModelData( model, `<widget><nested><paragraph>${ 'word[] word' + text }</paragraph></nested></widget>` );
  510. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  511. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  512. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  513. assertEqualMarkup( getModelData( model ),
  514. `<widget><nested><paragraph>${ '[]word word' + text }</paragraph></nested></widget>`
  515. );
  516. } );
  517. it( 'should move caret to end of nested editable content if caret is in the last line of a text', () => {
  518. setModelData( model, `<widget><nested><paragraph>${ text + 'word[] word' }</paragraph></nested></widget>` );
  519. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  520. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  521. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  522. assertEqualMarkup( getModelData( model ),
  523. `<widget><nested><paragraph>${ text + 'word word[]' }</paragraph></nested></widget>`
  524. );
  525. } );
  526. describe( 'when shift key is pressed', () => {
  527. beforeEach( () => {
  528. upArrowDomEvtDataStub.shiftKey = true;
  529. downArrowDomEvtDataStub.shiftKey = true;
  530. } );
  531. it( 'should not prevent default browser behavior for the up arrow in the middle lines of the text', () => {
  532. setModelData( model, `<widget><nested><paragraph>${ text + '[] ' + text }</paragraph></nested></widget>` );
  533. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  534. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  535. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  536. } );
  537. it( 'should not prevent default browser behavior for the down arrow in the middle lines of text', () => {
  538. setModelData( model, `<widget><nested><paragraph>${ text + '[] ' + text }</paragraph></nested></widget>` );
  539. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  540. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  541. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  542. } );
  543. it( 'should expand collapsed selection to the beginning of the nested editable content', () => {
  544. setModelData( model, `<widget><nested><paragraph>${ 'word[] word' + text }</paragraph></nested></widget>` );
  545. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  546. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  547. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  548. assertEqualMarkup( getModelData( model ),
  549. `<widget><nested><paragraph>${ '[word] word' + text }</paragraph></nested></widget>`
  550. );
  551. } );
  552. it( 'should not prevent default browser behavior for shrinking selection (up arrow)', () => {
  553. setModelData( model, `<widget><nested><paragraph>${ 'word [word]' + text }</paragraph></nested></widget>` );
  554. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  555. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  556. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  557. } );
  558. it( 'should expand not collapsed selection to the beginning of the editable content from the selection anchor', () => {
  559. setModelData( model,
  560. `<widget><nested><paragraph>${ 'word [word]' + text }</paragraph></nested></widget>`,
  561. { lastRangeBackward: true }
  562. );
  563. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  564. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  565. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  566. assertEqualMarkup( getModelData( model ),
  567. `<widget><nested><paragraph>${ '[word word]' + text }</paragraph></nested></widget>`
  568. );
  569. } );
  570. it( 'should expand collapsed selection to the end of the nested editable content', () => {
  571. setModelData( model, `<widget><nested><paragraph>${ text + 'word[] word' }</paragraph></nested></widget>` );
  572. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  573. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  574. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  575. assertEqualMarkup( getModelData( model ),
  576. `<widget><nested><paragraph>${ text + 'word[ word]' }</paragraph></nested></widget>`
  577. );
  578. } );
  579. it( 'should not prevent default browser behavior for shrinking selection (down arrow)', () => {
  580. setModelData( model,
  581. `<widget><nested><paragraph>${ text + '[word] word' }</paragraph></nested></widget>`,
  582. { lastRangeBackward: true }
  583. );
  584. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  585. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  586. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  587. } );
  588. it( 'should expand not collapsed selection to the end of the nested editable content from the selection anchor', () => {
  589. setModelData( model, `<widget><nested><paragraph>${ text + '[word] word' }</paragraph></nested></widget>` );
  590. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  591. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  592. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  593. assertEqualMarkup( getModelData( model ),
  594. `<widget><nested><paragraph>${ text + '[word word]' }</paragraph></nested></widget>`
  595. );
  596. } );
  597. } );
  598. } );
  599. if ( !env.isGecko ) {
  600. // These tests fails on Travis. They work correctly when started on local machine.
  601. // Issue is probably related to text rendering and wrapping.
  602. describe( 'with selection in the wrap area', () => {
  603. const text = new Array( 10 ).fill( 0 ).map( () => 'word' ).join( ' ' );
  604. it( 'should move the caret to end if the caret is after the last space in the line next to the last one', () => {
  605. // This is also first position in the last line.
  606. setModelData( model, `<widget><nested><paragraph>${ text + ' []word word word' }</paragraph></nested></widget>` );
  607. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  608. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  609. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  610. assertEqualMarkup( getModelData( model ),
  611. `<widget><nested><paragraph>${ text + ' word word word[]' }</paragraph></nested></widget>`
  612. );
  613. } );
  614. it( 'should move the caret to end if the caret is at the last space in the line next to last one', () => {
  615. setModelData( model, `<widget><nested><paragraph>${ text + '[] word word word' }</paragraph></nested></widget>` );
  616. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  617. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  618. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  619. assertEqualMarkup( getModelData( model ),
  620. `<widget><nested><paragraph>${ text + ' word word word[]' }</paragraph></nested></widget>`
  621. );
  622. } );
  623. it( 'should not move the caret if it\'s just before the last space in the line next to last one', () => {
  624. setModelData( model,
  625. '<widget><nested><paragraph>' +
  626. text.substring( 0, text.length - 1 ) + '[]d word word word' +
  627. '</paragraph></nested></widget>'
  628. );
  629. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  630. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  631. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  632. } );
  633. describe( 'when shift key is pressed', () => {
  634. beforeEach( () => {
  635. upArrowDomEvtDataStub.shiftKey = true;
  636. downArrowDomEvtDataStub.shiftKey = true;
  637. } );
  638. it( 'should expand collapsed selection to the end of the nested editable content', () => {
  639. setModelData( model, `<widget><nested><paragraph>${ text + '[] word word word' }</paragraph></nested></widget>` );
  640. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  641. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  642. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  643. assertEqualMarkup( getModelData( model ),
  644. `<widget><nested><paragraph>${ text + '[ word word word]' }</paragraph></nested></widget>`
  645. );
  646. } );
  647. it( 'should expand not collapsed selection to the end of the nested editable content from the selection anchor', () => {
  648. setModelData( model, `<widget><nested><paragraph>${ text + '[ word] word word' }</paragraph></nested></widget>` );
  649. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  650. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  651. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  652. assertEqualMarkup( getModelData( model ),
  653. `<widget><nested><paragraph>${ text + '[ word word word]' }</paragraph></nested></widget>`
  654. );
  655. } );
  656. } );
  657. } );
  658. }
  659. describe( 'with multiple paragraphs of text', () => {
  660. const text = new Array( 100 ).fill( 0 ).map( () => 'word' ).join( ' ' );
  661. it( 'should not prevent default browser behavior if caret is in the middle of a line of text', () => {
  662. setModelData( model,
  663. '<widget><nested>' +
  664. `<paragraph>${ text }[]${ text }</paragraph>` +
  665. '<paragraph>foobar</paragraph>' +
  666. '</nested></widget>'
  667. );
  668. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  669. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  670. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  671. } );
  672. it( 'should move the caret to the beginning of a nested editable content if the caret is in the first line of text', () => {
  673. setModelData( model,
  674. '<widget><nested>' +
  675. `<paragraph>word[]${ text }</paragraph>` +
  676. '<paragraph>foobar</paragraph>' +
  677. '</nested></widget>'
  678. );
  679. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  680. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  681. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  682. assertEqualMarkup( getModelData( model ),
  683. '<widget><nested>' +
  684. `<paragraph>[]word${ text }</paragraph>` +
  685. '<paragraph>foobar</paragraph>' +
  686. '</nested></widget>'
  687. );
  688. } );
  689. it( 'should not move the caret to the end of a nested editable content if the caret is not in the last line of text', () => {
  690. setModelData( model,
  691. '<widget><nested>' +
  692. `<paragraph>${ text }word []word</paragraph>` +
  693. '<paragraph>foobar</paragraph>' +
  694. '</nested></widget>'
  695. );
  696. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  697. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  698. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  699. } );
  700. it( 'should move the caret to end of a nested editable content if the caret is in the last line of text', () => {
  701. setModelData( model,
  702. '<widget><nested>' +
  703. '<paragraph>foobar</paragraph>' +
  704. `<paragraph>${ text }word []word</paragraph>` +
  705. '</nested></widget>'
  706. );
  707. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  708. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  709. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  710. assertEqualMarkup( getModelData( model ),
  711. '<widget><nested>' +
  712. '<paragraph>foobar</paragraph>' +
  713. `<paragraph>${ text }word word[]</paragraph>` +
  714. '</nested></widget>'
  715. );
  716. } );
  717. describe( 'when shift key is pressed', () => {
  718. beforeEach( () => {
  719. upArrowDomEvtDataStub.shiftKey = true;
  720. downArrowDomEvtDataStub.shiftKey = true;
  721. } );
  722. it( 'should expand selection to the beginning of the nested editable content', () => {
  723. setModelData( model,
  724. '<widget><nested>' +
  725. `<paragraph>word[] ${ text }</paragraph>` +
  726. `<paragraph>${ text }</paragraph>` +
  727. '</nested></widget>'
  728. );
  729. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  730. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  731. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  732. assertEqualMarkup( getModelData( model ),
  733. '<widget><nested>' +
  734. `<paragraph>[word] ${ text }</paragraph>` +
  735. `<paragraph>${ text }</paragraph>` +
  736. '</nested></widget>'
  737. );
  738. } );
  739. it( 'should expand selection to the end of the nested editable content', () => {
  740. setModelData( model,
  741. '<widget><nested>' +
  742. `<paragraph>${ text }</paragraph>` +
  743. `<paragraph>${ text } []word</paragraph>` +
  744. '</nested></widget>'
  745. );
  746. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  747. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  748. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  749. assertEqualMarkup( getModelData( model ),
  750. '<widget><nested>' +
  751. `<paragraph>${ text }</paragraph>` +
  752. `<paragraph>${ text } [word]</paragraph>` +
  753. '</nested></widget>'
  754. );
  755. } );
  756. } );
  757. } );
  758. describe( 'with horizontal line widget', () => {
  759. const text = new Array( 100 ).fill( 0 ).map( () => 'word' ).join( ' ' );
  760. it( 'should not navigate if the caret is in the middle line of text', () => {
  761. setModelData( model,
  762. '<widget><nested>' +
  763. '<horizontalLine></horizontalLine>' +
  764. `<paragraph>${ text }[]${ text }</paragraph>` +
  765. '</nested></widget>'
  766. );
  767. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  768. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  769. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  770. } );
  771. it( 'should move the caret to the beginning of the editable non-object content if it\'s is in the first line of text', () => {
  772. setModelData( model,
  773. '<widget><nested>' +
  774. '<horizontalLine></horizontalLine>' +
  775. `<paragraph>word[] ${ text }</paragraph>` +
  776. '</nested></widget>'
  777. );
  778. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  779. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  780. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  781. assertEqualMarkup( getModelData( model ),
  782. '<widget><nested>' +
  783. '<horizontalLine></horizontalLine>' +
  784. `<paragraph>[]word ${ text }</paragraph>` +
  785. '</nested></widget>'
  786. );
  787. } );
  788. it( 'should move the caret to the end of the editable non-object content if the caret is in the last line of text', () => {
  789. setModelData( model,
  790. '<widget><nested>' +
  791. `<paragraph>${ text } word []word</paragraph>` +
  792. '<horizontalLine></horizontalLine>' +
  793. '</nested></widget>'
  794. );
  795. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  796. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  797. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  798. assertEqualMarkup( getModelData( model ),
  799. '<widget><nested>' +
  800. `<paragraph>${ text } word word[]</paragraph>` +
  801. '<horizontalLine></horizontalLine>' +
  802. '</nested></widget>'
  803. );
  804. } );
  805. it( 'should not move the caret to the end of nested editable content if widget is selected in middle of that content', () => {
  806. setModelData( model,
  807. '<widget><nested>' +
  808. `<paragraph>${ text }</paragraph>` +
  809. '[<horizontalLine></horizontalLine>]' +
  810. `<paragraph>${ text }</paragraph>` +
  811. '<horizontalLine></horizontalLine>' +
  812. '</nested></widget>'
  813. );
  814. // Note: Two keydowns are necessary because the first one is handled by the WidgetTypeAround plugin
  815. // to activate the "fake caret".
  816. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  817. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  818. assertEqualMarkup( getModelData( model ),
  819. '<widget><nested>' +
  820. `<paragraph>${ text }</paragraph>` +
  821. '<horizontalLine></horizontalLine>' +
  822. `<paragraph>[]${ text }</paragraph>` +
  823. '<horizontalLine></horizontalLine>' +
  824. '</nested></widget>'
  825. );
  826. } );
  827. it( 'should not move the caret to the end of nested editable content if widget is next to the selection', () => {
  828. setModelData( model,
  829. '<widget><nested>' +
  830. `<paragraph>${ text }</paragraph>` +
  831. '[]<horizontalLine></horizontalLine>' +
  832. '</nested></widget>'
  833. );
  834. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  835. assertEqualMarkup( getModelData( model ),
  836. '<widget><nested>' +
  837. `<paragraph>${ text }</paragraph>` +
  838. '[<horizontalLine></horizontalLine>]' +
  839. '</nested></widget>'
  840. );
  841. } );
  842. describe( 'when shift key is pressed', () => {
  843. beforeEach( () => {
  844. upArrowDomEvtDataStub.shiftKey = true;
  845. downArrowDomEvtDataStub.shiftKey = true;
  846. } );
  847. it( 'should expand selection to the beginning of the nested editable content', () => {
  848. setModelData( model,
  849. '<widget><nested>' +
  850. '<horizontalLine></horizontalLine>' +
  851. '<paragraph>foo[]bar</paragraph>' +
  852. '</nested></widget>'
  853. );
  854. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  855. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  856. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  857. assertEqualMarkup( getModelData( model ),
  858. '<widget><nested>' +
  859. '<horizontalLine></horizontalLine>' +
  860. '<paragraph>[foo]bar</paragraph>' +
  861. '</nested></widget>'
  862. );
  863. } );
  864. it( 'should expand selection to the end of the nested editable content', () => {
  865. setModelData( model,
  866. '<widget><nested>' +
  867. '<paragraph>foo[]bar</paragraph>' +
  868. '<horizontalLine></horizontalLine>' +
  869. '</nested></widget>'
  870. );
  871. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  872. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  873. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  874. assertEqualMarkup( getModelData( model ),
  875. '<widget><nested>' +
  876. '<paragraph>foo[bar]</paragraph>' +
  877. '<horizontalLine></horizontalLine>' +
  878. '</nested></widget>'
  879. );
  880. } );
  881. } );
  882. } );
  883. describe( 'contains image widget with caption and selection inside the caption', () => {
  884. it( 'should move caret to the closest limit boundary', () => {
  885. setModelData( model,
  886. '<widget><nested>' +
  887. '<paragraph>foo</paragraph>' +
  888. `<image src="${ imageUrl }"><caption>bar[]baz</caption></image>` +
  889. '</nested></widget>'
  890. );
  891. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  892. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  893. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  894. assertEqualMarkup( getModelData( model ), '<widget><nested>' +
  895. '<paragraph>foo</paragraph>' +
  896. `<image src="${ imageUrl }"><caption>[]barbaz</caption></image>` +
  897. '</nested></widget>'
  898. );
  899. } );
  900. it( 'should not prevent default browser behavior when caret at the beginning of nested editable', () => {
  901. setModelData( model,
  902. '<widget><nested>' +
  903. '<paragraph>foo</paragraph>' +
  904. `<image src="${ imageUrl }"><caption>[]barbaz</caption></image>` +
  905. '</nested></widget>'
  906. );
  907. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  908. sinon.assert.notCalled( upArrowDomEvtDataStub.preventDefault );
  909. sinon.assert.notCalled( upArrowDomEvtDataStub.stopPropagation );
  910. } );
  911. it( 'should move the caret to the first position of the image caption', () => {
  912. setModelData( model,
  913. '<widget><nested>' +
  914. `<image src="${ imageUrl }"><caption>bar[]baz</caption></image>` +
  915. '<paragraph>foo</paragraph>' +
  916. '</nested></widget>'
  917. );
  918. editor.editing.view.document.fire( 'keydown', upArrowDomEvtDataStub );
  919. sinon.assert.calledOnce( upArrowDomEvtDataStub.preventDefault );
  920. sinon.assert.calledOnce( upArrowDomEvtDataStub.stopPropagation );
  921. assertEqualMarkup( getModelData( model ),
  922. '<widget><nested>' +
  923. `<image src="${ imageUrl }"><caption>[]barbaz</caption></image>` +
  924. '<paragraph>foo</paragraph>' +
  925. '</nested></widget>'
  926. );
  927. } );
  928. it( 'should move caret to the end of image caption', () => {
  929. setModelData( model,
  930. '<widget><nested>' +
  931. `<image src="${ imageUrl }"><caption>bar[]baz</caption></image>` +
  932. '<paragraph>foo</paragraph>' +
  933. '</nested></widget>'
  934. );
  935. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  936. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  937. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  938. assertEqualMarkup( getModelData( model ),
  939. '<widget><nested>' +
  940. `<image src="${ imageUrl }"><caption>barbaz[]</caption></image>` +
  941. '<paragraph>foo</paragraph>' +
  942. '</nested></widget>'
  943. );
  944. } );
  945. it( 'should move caret to the end of image caption when caret is on the position next to the last one', () => {
  946. setModelData( model,
  947. '<widget><nested>' +
  948. `<image src="${ imageUrl }"><caption>barba[]z</caption></image>` +
  949. '<paragraph>foo</paragraph>' +
  950. '</nested></widget>'
  951. );
  952. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  953. sinon.assert.calledOnce( downArrowDomEvtDataStub.preventDefault );
  954. sinon.assert.calledOnce( downArrowDomEvtDataStub.stopPropagation );
  955. assertEqualMarkup( getModelData( model ),
  956. '<widget><nested>' +
  957. `<image src="${ imageUrl }"><caption>barbaz[]</caption></image>` +
  958. '<paragraph>foo</paragraph>' +
  959. '</nested></widget>'
  960. );
  961. } );
  962. it( 'should not prevent default browser behavior when caret inside image caption when followed by a paragraph', () => {
  963. setModelData( model,
  964. '<widget><nested>' +
  965. `<image src="${ imageUrl }"><caption>barbaz[]</caption></image>` +
  966. '<paragraph>foo</paragraph>' +
  967. '</nested></widget>'
  968. );
  969. editor.editing.view.document.fire( 'keydown', downArrowDomEvtDataStub );
  970. sinon.assert.notCalled( downArrowDomEvtDataStub.preventDefault );
  971. sinon.assert.notCalled( downArrowDomEvtDataStub.stopPropagation );
  972. } );
  973. } );
  974. } );
  975. function BlockWidgetWithNestedEditable( editor ) {
  976. const model = editor.model;
  977. model.schema.register( 'widget', {
  978. inheritAllFrom: '$block',
  979. isObject: true
  980. } );
  981. model.schema.register( 'nested', {
  982. allowIn: 'widget',
  983. isLimit: true
  984. } );
  985. model.schema.extend( '$block', {
  986. allowIn: 'nested'
  987. } );
  988. editor.conversion.for( 'dataDowncast' )
  989. .elementToElement( {
  990. model: 'widget',
  991. view: ( modelItem, writer ) => {
  992. return writer.createContainerElement( 'figure' );
  993. }
  994. } )
  995. .elementToElement( {
  996. model: 'nested',
  997. view: ( modelItem, writer ) => {
  998. return writer.createContainerElement( 'figcaption' );
  999. }
  1000. } );
  1001. editor.conversion.for( 'editingDowncast' )
  1002. .elementToElement( {
  1003. model: 'widget',
  1004. view: ( modelItem, writer ) => {
  1005. const div = writer.createContainerElement( 'figure' );
  1006. return toWidget( div, writer, { label: 'widget label' } );
  1007. }
  1008. } )
  1009. .elementToElement( {
  1010. model: 'nested',
  1011. view: ( modelItem, writer ) => {
  1012. const nested = writer.createEditableElement( 'figcaption' );
  1013. return toWidgetEditable( nested, writer );
  1014. }
  1015. } );
  1016. editor.conversion.for( 'upcast' )
  1017. .elementToElement( {
  1018. view: 'figure',
  1019. model: 'widget'
  1020. } )
  1021. .elementToElement( {
  1022. view: 'figcaption',
  1023. model: 'nested'
  1024. } );
  1025. }
  1026. } );