input.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  6. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  7. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  8. import List from '@ckeditor/ckeditor5-list/src/list';
  9. import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
  10. import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
  11. import Input from '../src/input';
  12. import Writer from '@ckeditor/ckeditor5-engine/src/model/writer';
  13. import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
  14. import ViewText from '@ckeditor/ckeditor5-engine/src/view/text';
  15. import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element';
  16. import ViewSelection from '@ckeditor/ckeditor5-engine/src/view/selection';
  17. import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
  18. import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
  19. import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  20. import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  21. /* global document */
  22. describe( 'Input feature', () => {
  23. let editor, model, modelRoot, view, viewDocument, viewRoot, listenter;
  24. testUtils.createSinonSandbox();
  25. beforeEach( () => {
  26. listenter = Object.create( EmitterMixin );
  27. const domElement = document.createElement( 'div' );
  28. document.body.appendChild( domElement );
  29. return ClassicTestEditor.create( domElement, { plugins: [ Input, Paragraph, Bold, List, ShiftEnter ] } )
  30. .then( newEditor => {
  31. // Mock image feature.
  32. newEditor.model.schema.register( 'image', { allowWhere: '$text' } );
  33. newEditor.conversion.elementToElement( {
  34. model: 'image',
  35. view: 'img'
  36. } );
  37. editor = newEditor;
  38. model = editor.model;
  39. modelRoot = model.document.getRoot();
  40. view = editor.editing.view;
  41. viewDocument = view.document;
  42. viewRoot = viewDocument.getRoot();
  43. editor.setData( '<p>foobar</p>' );
  44. model.change( writer => {
  45. writer.setSelection(
  46. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 3, modelRoot.getChild( 0 ), 3 )
  47. );
  48. } );
  49. } );
  50. } );
  51. afterEach( () => {
  52. listenter.stopListening();
  53. return editor.destroy();
  54. } );
  55. describe( 'mutations handling', () => {
  56. it( 'should handle text mutation', () => {
  57. viewDocument.fire( 'mutations', [
  58. {
  59. type: 'text',
  60. oldText: 'foobar',
  61. newText: 'fooxbar',
  62. node: viewRoot.getChild( 0 ).getChild( 0 )
  63. }
  64. ] );
  65. expect( getModelData( model ) ).to.equal( '<paragraph>foox[]bar</paragraph>' );
  66. expect( getViewData( view ) ).to.equal( '<p>foox{}bar</p>' );
  67. } );
  68. it( 'should handle text mutation change', () => {
  69. viewDocument.fire( 'mutations', [
  70. {
  71. type: 'text',
  72. oldText: 'foobar',
  73. newText: 'foodar',
  74. node: viewRoot.getChild( 0 ).getChild( 0 )
  75. }
  76. ] );
  77. expect( getModelData( model ) ).to.equal( '<paragraph>food[]ar</paragraph>' );
  78. expect( getViewData( view ) ).to.equal( '<p>food{}ar</p>' );
  79. } );
  80. it( 'should handle text node insertion', () => {
  81. editor.setData( '<p></p>' );
  82. viewDocument.fire( 'mutations', [
  83. {
  84. type: 'children',
  85. oldChildren: [],
  86. newChildren: [ new ViewText( 'x' ) ],
  87. node: viewRoot.getChild( 0 )
  88. }
  89. ] );
  90. expect( getModelData( model ) ).to.equal( '<paragraph>x[]</paragraph>' );
  91. expect( getViewData( view ) ).to.equal( '<p>x{}</p>' );
  92. } );
  93. it( 'should apply selection attributes to the inserted text', () => {
  94. setModelData( model, '<paragraph>[]</paragraph>', {
  95. selectionAttributes: {
  96. bold: true,
  97. italic: true
  98. }
  99. } );
  100. viewDocument.fire( 'mutations', [
  101. {
  102. type: 'children',
  103. oldChildren: [],
  104. newChildren: [ new ViewText( 'x' ) ],
  105. node: viewRoot.getChild( 0 )
  106. }
  107. ] );
  108. expect( getModelData( model ) ).to.equal(
  109. '<paragraph><$text bold="true" italic="true">x[]</$text></paragraph>'
  110. );
  111. } );
  112. it( 'should handle multiple text mutations', () => {
  113. editor.setData( '<p>foo<strong>bar</strong></p>' );
  114. viewDocument.fire( 'mutations', [
  115. {
  116. type: 'text',
  117. oldText: 'foo',
  118. newText: 'foob',
  119. node: viewRoot.getChild( 0 ).getChild( 0 )
  120. },
  121. {
  122. type: 'text',
  123. oldText: 'bar',
  124. newText: 'ar',
  125. node: viewRoot.getChild( 0 ).getChild( 1 ).getChild( 0 )
  126. }
  127. ] );
  128. expect( getModelData( model ) ).to.equal( '<paragraph>foob[]<$text bold="true">ar</$text></paragraph>' );
  129. expect( getViewData( view ) ).to.equal( '<p>foob{}<strong>ar</strong></p>' );
  130. } );
  131. it( 'should handle multiple text node insertion', () => {
  132. editor.setData( '<p></p><p></p>' );
  133. viewDocument.fire( 'mutations', [
  134. {
  135. type: 'children',
  136. oldChildren: [],
  137. newChildren: [ new ViewText( 'x' ) ],
  138. node: viewRoot.getChild( 0 )
  139. },
  140. {
  141. type: 'children',
  142. oldChildren: [],
  143. newChildren: [ new ViewText( 'y' ) ],
  144. node: viewRoot.getChild( 1 )
  145. }
  146. ] );
  147. expect( getModelData( model ) ).to.equal( '<paragraph>x</paragraph><paragraph>y[]</paragraph>' );
  148. expect( getViewData( view ) ).to.equal( '<p>x</p><p>y{}</p>' );
  149. } );
  150. it( 'should do nothing when two nodes were inserted', () => {
  151. editor.setData( '<p></p>' );
  152. viewDocument.fire( 'mutations', [
  153. {
  154. type: 'children',
  155. oldChildren: [],
  156. newChildren: [ new ViewText( 'x' ), new ViewElement( 'img' ) ],
  157. node: viewRoot.getChild( 0 )
  158. }
  159. ] );
  160. expect( getModelData( model ) ).to.equal( '<paragraph>[]</paragraph>' );
  161. expect( getViewData( view ) ).to.equal( '<p>[]</p>' );
  162. } );
  163. it( 'should do nothing when two nodes were inserted and one removed', () => {
  164. viewDocument.fire( 'mutations', [
  165. {
  166. type: 'children',
  167. oldChildren: [ new ViewText( 'foobar' ) ],
  168. newChildren: [ new ViewText( 'x' ), new ViewElement( 'img' ) ],
  169. node: viewRoot.getChild( 0 )
  170. }
  171. ] );
  172. expect( getModelData( model ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
  173. expect( getViewData( view ) ).to.equal( '<p>foo{}bar</p>' );
  174. } );
  175. it( 'should handle multiple children in the node', () => {
  176. editor.setData( '<p>foo<img></img></p>' );
  177. viewDocument.fire( 'mutations', [
  178. {
  179. type: 'children',
  180. oldChildren: [ new ViewText( 'foo' ), viewRoot.getChild( 0 ).getChild( 1 ) ],
  181. newChildren: [ new ViewText( 'foo' ), viewRoot.getChild( 0 ).getChild( 1 ), new ViewText( 'x' ) ],
  182. node: viewRoot.getChild( 0 )
  183. }
  184. ] );
  185. expect( getModelData( model ) ).to.equal( '<paragraph>foo<image></image>x[]</paragraph>' );
  186. expect( getViewData( view ) ).to.equal( '<p>foo<img></img>x{}</p>' );
  187. } );
  188. it( 'should do nothing when node was removed', () => {
  189. viewDocument.fire( 'mutations', [
  190. {
  191. type: 'children',
  192. oldChildren: [ new ViewText( 'foobar' ) ],
  193. newChildren: [],
  194. node: viewRoot.getChild( 0 )
  195. }
  196. ] );
  197. expect( getModelData( model ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
  198. expect( getViewData( view ) ).to.equal( '<p>foo{}bar</p>' );
  199. } );
  200. it( 'should do nothing when element was inserted', () => {
  201. editor.setData( '<p></p>' );
  202. viewDocument.fire( 'mutations', [
  203. {
  204. type: 'children',
  205. oldChildren: [],
  206. newChildren: [ new ViewElement( 'img' ) ],
  207. node: viewRoot.getChild( 0 )
  208. }
  209. ] );
  210. expect( getModelData( model ) ).to.equal( '<paragraph>[]</paragraph>' );
  211. expect( getViewData( view ) ).to.equal( '<p>[]</p>' );
  212. } );
  213. it( 'should set model selection appropriately to view selection passed in mutations event', () => {
  214. // This test case emulates spellchecker correction.
  215. const viewSelection = new ViewSelection();
  216. viewSelection.setTo( viewRoot.getChild( 0 ).getChild( 0 ), 6 );
  217. viewDocument.fire( 'mutations',
  218. [ {
  219. type: 'text',
  220. oldText: 'foobar',
  221. newText: 'foodar',
  222. node: viewRoot.getChild( 0 ).getChild( 0 )
  223. } ],
  224. viewSelection
  225. );
  226. expect( getModelData( model ) ).to.equal( '<paragraph>foodar[]</paragraph>' );
  227. expect( getViewData( view ) ).to.equal( '<p>foodar{}</p>' );
  228. } );
  229. it( 'should use up to one insert and remove operations (spellchecker)', () => {
  230. // This test case emulates spellchecker correction.
  231. const viewSelection = new ViewSelection();
  232. viewSelection.setTo( viewRoot.getChild( 0 ).getChild( 0 ), 6 );
  233. testUtils.sinon.spy( Writer.prototype, 'insert' );
  234. testUtils.sinon.spy( Writer.prototype, 'remove' );
  235. viewDocument.fire( 'mutations',
  236. [ {
  237. type: 'text',
  238. oldText: 'foobar',
  239. newText: 'fxobxr',
  240. node: viewRoot.getChild( 0 ).getChild( 0 )
  241. } ],
  242. viewSelection
  243. );
  244. expect( Writer.prototype.insert.calledOnce ).to.be.true;
  245. expect( Writer.prototype.remove.calledOnce ).to.be.true;
  246. } );
  247. it( 'should place selection after when correcting to longer word (spellchecker)', () => {
  248. // This test case emulates spellchecker correction.
  249. editor.setData( '<p>Foo hous a</p>' );
  250. const viewSelection = new ViewSelection();
  251. viewSelection.setTo( viewRoot.getChild( 0 ).getChild( 0 ), 9 );
  252. viewDocument.fire( 'mutations',
  253. [ {
  254. type: 'text',
  255. oldText: 'Foo hous a',
  256. newText: 'Foo house a',
  257. node: viewRoot.getChild( 0 ).getChild( 0 )
  258. } ],
  259. viewSelection
  260. );
  261. expect( getModelData( model ) ).to.equal( '<paragraph>Foo house[] a</paragraph>' );
  262. expect( getViewData( view ) ).to.equal( '<p>Foo house{} a</p>' );
  263. } );
  264. it( 'should place selection after when correcting to shorter word (spellchecker)', () => {
  265. // This test case emulates spellchecker correction.
  266. editor.setData( '<p>Bar athat foo</p>' );
  267. const viewSelection = new ViewSelection();
  268. viewSelection.setTo( viewRoot.getChild( 0 ).getChild( 0 ), 8 );
  269. viewDocument.fire( 'mutations',
  270. [ {
  271. type: 'text',
  272. oldText: 'Bar athat foo',
  273. newText: 'Bar that foo',
  274. node: viewRoot.getChild( 0 ).getChild( 0 )
  275. } ],
  276. viewSelection
  277. );
  278. expect( getModelData( model ) ).to.equal( '<paragraph>Bar that[] foo</paragraph>' );
  279. expect( getViewData( view ) ).to.equal( '<p>Bar that{} foo</p>' );
  280. } );
  281. it( 'should place selection after when merging two words (spellchecker)', () => {
  282. // This test case emulates spellchecker correction.
  283. editor.setData( '<p>Foo hous e</p>' );
  284. const viewSelection = new ViewSelection();
  285. viewSelection.setTo( viewRoot.getChild( 0 ).getChild( 0 ), 9 );
  286. viewDocument.fire( 'mutations',
  287. [ {
  288. type: 'text',
  289. oldText: 'Foo hous e',
  290. newText: 'Foo house',
  291. node: viewRoot.getChild( 0 ).getChild( 0 )
  292. } ],
  293. viewSelection
  294. );
  295. expect( getModelData( model ) ).to.equal( '<paragraph>Foo house[]</paragraph>' );
  296. expect( getViewData( view ) ).to.equal( '<p>Foo house{}</p>' );
  297. } );
  298. it( 'should place non-collapsed selection after changing single character (composition)', () => {
  299. editor.setData( '<p>Foo house</p>' );
  300. const viewSelection = new ViewSelection();
  301. viewSelection.setTo( viewRoot.getChild( 0 ).getChild( 0 ), 8 );
  302. viewSelection.setFocus( viewRoot.getChild( 0 ).getChild( 0 ), 9 );
  303. viewDocument.fire( 'mutations',
  304. [ {
  305. type: 'text',
  306. oldText: 'Foo house',
  307. newText: 'Foo housa',
  308. node: viewRoot.getChild( 0 ).getChild( 0 )
  309. } ],
  310. viewSelection
  311. );
  312. expect( getModelData( model ) ).to.equal( '<paragraph>Foo hous[a]</paragraph>' );
  313. expect( getViewData( view ) ).to.equal( '<p>Foo hous{a}</p>' );
  314. } );
  315. it( 'should replace last &nbsp; with space', () => {
  316. model.change( writer => {
  317. writer.setSelection(
  318. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 6, modelRoot.getChild( 0 ), 6 )
  319. );
  320. } );
  321. viewDocument.fire( 'mutations', [
  322. {
  323. type: 'text',
  324. oldText: 'foobar',
  325. newText: 'foobar\u00A0',
  326. node: viewRoot.getChild( 0 ).getChild( 0 )
  327. }
  328. ] );
  329. expect( getModelData( model ) ).to.equal( '<paragraph>foobar []</paragraph>' );
  330. expect( getViewData( view ) ).to.equal( '<p>foobar {}</p>' );
  331. } );
  332. it( 'should replace first &nbsp; with space', () => {
  333. model.change( writer => {
  334. writer.setSelection(
  335. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 0, modelRoot.getChild( 0 ), 0 )
  336. );
  337. } );
  338. viewDocument.fire( 'mutations', [
  339. {
  340. type: 'text',
  341. oldText: 'foobar',
  342. newText: '\u00A0foobar',
  343. node: viewRoot.getChild( 0 ).getChild( 0 )
  344. }
  345. ] );
  346. expect( getModelData( model ) ).to.equal( '<paragraph> []foobar</paragraph>' );
  347. expect( getViewData( view ) ).to.equal( '<p> {}foobar</p>' );
  348. } );
  349. it( 'should replace all &nbsp; with spaces', () => {
  350. model.change( writer => {
  351. writer.setSelection(
  352. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 6, modelRoot.getChild( 0 ), 6 )
  353. );
  354. } );
  355. viewDocument.fire( 'mutations', [
  356. {
  357. type: 'text',
  358. oldText: 'foobar',
  359. newText: 'foobar\u00A0\u00A0\u00A0baz',
  360. node: viewRoot.getChild( 0 ).getChild( 0 )
  361. }
  362. ] );
  363. expect( getModelData( model ) ).to.equal( '<paragraph>foobar baz[]</paragraph>' );
  364. expect( getViewData( view ) ).to.equal( '<p>foobar baz{}</p>' );
  365. } );
  366. // ckeditor5#718.
  367. it( 'should not crash and prevent all changes if view common ancestor of mutations cannot be mapped to model', () => {
  368. editor.setData( '<p>Foo</p><ul><li>Bar</li><li>Baz</li></ul>' );
  369. const ul = viewRoot.getChild( 1 );
  370. viewDocument.fire( 'mutations', [
  371. {
  372. type: 'text',
  373. oldText: 'Bar',
  374. newText: 'Bx',
  375. node: ul.getChild( 0 )
  376. },
  377. {
  378. type: 'children',
  379. oldChildren: [ ul.getChild( 0 ), ul.getChild( 1 ) ],
  380. newChildren: [ ul.getChild( 0 ) ],
  381. node: ul
  382. }
  383. ] );
  384. expect( getViewData( view ) ).to.equal( '<p>{}Foo</p><ul><li>Bar</li><li>Baz</li></ul>' );
  385. } );
  386. it( 'should handle bogus br correctly', () => {
  387. editor.setData( '<p><strong>Foo</strong></p>' );
  388. editor.model.change( writer => {
  389. writer.setSelection( editor.model.document.getRoot().getChild( 0 ), 'end' );
  390. writer.removeSelectionAttribute( 'bold' );
  391. } );
  392. // We need to change the DOM content manually because typing algorithm actually does not check
  393. // `newChildren` and `oldChildren` list but takes them from DOM and model.
  394. const p = viewRoot.getChild( 0 );
  395. const domP = editor.editing.view.domConverter.mapViewToDom( p );
  396. domP.appendChild( document.createTextNode( ' ' ) );
  397. domP.appendChild( document.createElement( 'br' ) );
  398. viewDocument.fire( 'mutations', [
  399. {
  400. type: 'children',
  401. oldChildren: [ viewRoot.getChild( 0 ).getChild( 0 ) ],
  402. newChildren: [
  403. new ViewElement( 'strong', null, new ViewText( 'Foo' ) ),
  404. new ViewText( ' ' ),
  405. new ViewElement( 'br' )
  406. ],
  407. node: viewRoot.getChild( 0 )
  408. }
  409. ] );
  410. expect( getViewData( view ) ).to.equal( '<p><strong>Foo</strong> {}</p>' );
  411. } );
  412. it( 'should handle children mutation correctly if there are soft breaks in the mutated container', () => {
  413. editor.setData( '<p><strong>Foo</strong><br /><strong>Bar</strong></p>' );
  414. editor.model.change( writer => {
  415. writer.setSelection( editor.model.document.getRoot().getChild( 0 ), 'end' );
  416. writer.removeSelectionAttribute( 'bold' );
  417. } );
  418. // We need to change the DOM content manually because typing algorithm actually does not check
  419. // `newChildren` and `oldChildren` list but takes them from DOM and model.
  420. const p = viewRoot.getChild( 0 );
  421. const domP = editor.editing.view.domConverter.mapViewToDom( p );
  422. domP.appendChild( document.createTextNode( ' ' ) );
  423. domP.appendChild( document.createElement( 'br' ) );
  424. viewDocument.fire( 'mutations', [
  425. {
  426. type: 'children',
  427. oldChildren: [ ...viewRoot.getChild( 0 ).getChildren() ],
  428. newChildren: [
  429. new ViewElement( 'strong', null, new ViewText( 'Foo' ) ),
  430. new ViewElement( 'br' ),
  431. new ViewElement( 'strong', null, new ViewText( 'Bar' ) ),
  432. new ViewText( ' ' ),
  433. new ViewElement( 'br' )
  434. ],
  435. node: viewRoot.getChild( 0 )
  436. }
  437. ] );
  438. expect( getViewData( view ) ).to.equal( '<p><strong>Foo</strong><br></br><strong>Bar</strong> {}</p>' );
  439. } );
  440. } );
  441. describe( 'keystroke handling', () => {
  442. it( 'should remove contents', () => {
  443. model.change( writer => {
  444. writer.setSelection(
  445. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 2, modelRoot.getChild( 0 ), 4 ) );
  446. } );
  447. listenter.listenTo( viewDocument, 'keydown', () => {
  448. expect( getModelData( model ) ).to.equal( '<paragraph>fo[]ar</paragraph>' );
  449. }, { priority: 'lowest' } );
  450. } );
  451. // #97
  452. it( 'should remove contents and merge blocks', () => {
  453. setModelData( model, '<paragraph>fo[o</paragraph><paragraph>b]ar</paragraph>' );
  454. listenter.listenTo( viewDocument, 'keydown', () => {
  455. expect( getModelData( model ) ).to.equal( '<paragraph>fo[]ar</paragraph>' );
  456. viewDocument.fire( 'mutations', [
  457. {
  458. type: 'text',
  459. oldText: 'foar',
  460. newText: 'foyar',
  461. node: viewRoot.getChild( 0 ).getChild( 0 )
  462. }
  463. ] );
  464. }, { priority: 'lowest' } );
  465. viewDocument.fire( 'keydown', { keyCode: getCode( 'y' ) } );
  466. expect( getModelData( model ) ).to.equal( '<paragraph>foy[]ar</paragraph>' );
  467. expect( getViewData( view ) ).to.equal( '<p>foy{}ar</p>' );
  468. } );
  469. it( 'should do nothing on arrow key', () => {
  470. model.change( writer => {
  471. writer.setSelection(
  472. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 2, modelRoot.getChild( 0 ), 4 ) );
  473. } );
  474. viewDocument.fire( 'keydown', { keyCode: getCode( 'arrowdown' ) } );
  475. expect( getModelData( model ) ).to.equal( '<paragraph>fo[ob]ar</paragraph>' );
  476. } );
  477. it( 'should do nothing on ctrl combinations', () => {
  478. model.change( writer => {
  479. writer.setSelection(
  480. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 2, modelRoot.getChild( 0 ), 4 ) );
  481. } );
  482. viewDocument.fire( 'keydown', { ctrlKey: true, keyCode: getCode( 'c' ) } );
  483. expect( getModelData( model ) ).to.equal( '<paragraph>fo[ob]ar</paragraph>' );
  484. } );
  485. it( 'should do nothing on non printable keys', () => {
  486. model.change( writer => {
  487. writer.setSelection(
  488. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 2, modelRoot.getChild( 0 ), 4 ) );
  489. } );
  490. viewDocument.fire( 'keydown', { keyCode: 16 } ); // Shift
  491. viewDocument.fire( 'keydown', { keyCode: 35 } ); // Home
  492. viewDocument.fire( 'keydown', { keyCode: 112 } ); // F1
  493. expect( getModelData( model ) ).to.equal( '<paragraph>fo[ob]ar</paragraph>' );
  494. } );
  495. // #69
  496. it( 'should do nothing on tab key', () => {
  497. model.change( writer => {
  498. writer.setSelection(
  499. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 2, modelRoot.getChild( 0 ), 4 ) );
  500. } );
  501. viewDocument.fire( 'keydown', { keyCode: 9 } ); // Tab
  502. expect( getModelData( model ) ).to.equal( '<paragraph>fo[ob]ar</paragraph>' );
  503. } );
  504. // #82
  505. it( 'should do nothing on composition start key', () => {
  506. model.change( writer => {
  507. writer.setSelection(
  508. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 2, modelRoot.getChild( 0 ), 4 ) );
  509. } );
  510. viewDocument.fire( 'keydown', { keyCode: 229 } );
  511. expect( getModelData( model ) ).to.equal( '<paragraph>fo[ob]ar</paragraph>' );
  512. } );
  513. it( 'should do nothing if selection is collapsed', () => {
  514. viewDocument.fire( 'keydown', { ctrlKey: true, keyCode: getCode( 'c' ) } );
  515. expect( getModelData( model ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
  516. } );
  517. it( 'should lock buffer if selection is not collapsed', () => {
  518. const buffer = editor.commands.get( 'input' )._buffer;
  519. const lockSpy = testUtils.sinon.spy( buffer, 'lock' );
  520. const unlockSpy = testUtils.sinon.spy( buffer, 'unlock' );
  521. model.change( writer => {
  522. writer.setSelection(
  523. ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 2, modelRoot.getChild( 0 ), 4 ) );
  524. } );
  525. viewDocument.fire( 'keydown', { keyCode: getCode( 'y' ) } );
  526. expect( lockSpy.calledOnce ).to.be.true;
  527. expect( unlockSpy.calledOnce ).to.be.true;
  528. } );
  529. it( 'should not lock buffer on non printable keys', () => {
  530. const buffer = editor.commands.get( 'input' )._buffer;
  531. const lockSpy = testUtils.sinon.spy( buffer, 'lock' );
  532. const unlockSpy = testUtils.sinon.spy( buffer, 'unlock' );
  533. viewDocument.fire( 'keydown', { keyCode: 16 } ); // Shift
  534. viewDocument.fire( 'keydown', { keyCode: 35 } ); // Home
  535. viewDocument.fire( 'keydown', { keyCode: 112 } ); // F1
  536. expect( lockSpy.callCount ).to.be.equal( 0 );
  537. expect( unlockSpy.callCount ).to.be.equal( 0 );
  538. } );
  539. it( 'should not lock buffer on collapsed selection', () => {
  540. const buffer = editor.commands.get( 'input' )._buffer;
  541. const lockSpy = testUtils.sinon.spy( buffer, 'lock' );
  542. const unlockSpy = testUtils.sinon.spy( buffer, 'unlock' );
  543. viewDocument.fire( 'keydown', { keyCode: getCode( 'b' ) } );
  544. viewDocument.fire( 'keydown', { keyCode: getCode( 'a' ) } );
  545. viewDocument.fire( 'keydown', { keyCode: getCode( 'z' ) } );
  546. expect( lockSpy.callCount ).to.be.equal( 0 );
  547. expect( unlockSpy.callCount ).to.be.equal( 0 );
  548. } );
  549. it( 'should not modify document when input command is disabled and selection is collapsed', () => {
  550. setModelData( model, '<paragraph>foo[]bar</paragraph>' );
  551. editor.commands.get( 'input' ).isEnabled = false;
  552. viewDocument.fire( 'keydown', { keyCode: getCode( 'b' ) } );
  553. expect( getModelData( model ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );
  554. } );
  555. it( 'should not modify document when input command is disabled and selection is non-collapsed', () => {
  556. setModelData( model, '<paragraph>fo[ob]ar</paragraph>' );
  557. editor.commands.get( 'input' ).isEnabled = false;
  558. viewDocument.fire( 'keydown', { keyCode: getCode( 'b' ) } );
  559. expect( getModelData( model ) ).to.equal( '<paragraph>fo[ob]ar</paragraph>' );
  560. } );
  561. } );
  562. } );