8
0

verticalnavigation.js 48 KB

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