8
0

undo.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import { Client, expectClients, clearBuffer } from './utils.js';
  6. import Element from '../../../../src/model/element';
  7. import Text from '../../../../src/model/text';
  8. describe( 'transform', () => {
  9. let john;
  10. beforeEach( () => {
  11. return Client.get( 'john' ).then( client => ( john = client ) );
  12. } );
  13. afterEach( () => {
  14. clearBuffer();
  15. return john.destroy();
  16. } );
  17. it( 'split, remove', () => {
  18. john.setData( '<paragraph>Foo[]Bar</paragraph>' );
  19. john.split();
  20. john.setSelection( [ 1 ], [ 2 ] );
  21. john.remove();
  22. john.undo();
  23. john.undo();
  24. expectClients( '<paragraph>FooBar</paragraph>' );
  25. } );
  26. it( 'move, merge', () => {
  27. john.setData( '[<paragraph>Foo</paragraph>]<paragraph>Bar</paragraph>' );
  28. john.move( [ 2 ] );
  29. john.setSelection( [ 1 ] );
  30. john.merge();
  31. john.undo();
  32. john.undo();
  33. expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  34. } );
  35. it.skip( 'move multiple, merge', () => {
  36. john.setData( '[<paragraph>Foo</paragraph><paragraph>Bar</paragraph>]<paragraph>Xyz</paragraph>' );
  37. john.move( [ 3 ] );
  38. expectClients( '<paragraph>Xyz</paragraph><paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  39. john.setSelection( [ 1 ] );
  40. john.merge();
  41. expectClients( '<paragraph>XyzFoo</paragraph><paragraph>Bar</paragraph>' );
  42. john.undo();
  43. expectClients( '<paragraph>Xyz</paragraph><paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  44. john.undo();
  45. // Wrong move is done.
  46. expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph><paragraph>Xyz</paragraph>' );
  47. } );
  48. it( 'move inside unwrapped content', () => {
  49. john.setData( '<blockQuote>[<paragraph>Foo</paragraph>]<paragraph>Bar</paragraph></blockQuote>' );
  50. john.move( [ 0, 2 ] );
  51. john.setSelection( [ 0, 0 ] );
  52. john.unwrap();
  53. john.undo();
  54. john.undo();
  55. expectClients(
  56. '<blockQuote>' +
  57. '<paragraph>Foo</paragraph>' +
  58. '<paragraph>Bar</paragraph>' +
  59. '</blockQuote>'
  60. );
  61. } );
  62. it( 'remove node, merge', () => {
  63. john.setData( '<paragraph>Foo</paragraph><paragraph>[Bar]</paragraph>' );
  64. john.remove();
  65. john.setSelection( [ 1 ] );
  66. john.merge();
  67. john.undo();
  68. john.undo();
  69. expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  70. } );
  71. it( 'merge, merge #1', () => {
  72. john.setData(
  73. '<blockQuote>' +
  74. '<paragraph>Foo</paragraph>' +
  75. '<paragraph>Bar</paragraph>' +
  76. '</blockQuote>' +
  77. '[]' +
  78. '<blockQuote>' +
  79. '<paragraph>Xyz</paragraph>' +
  80. '</blockQuote>'
  81. );
  82. john.merge();
  83. john.setSelection( [ 0, 2 ] );
  84. john.merge();
  85. expectClients(
  86. '<blockQuote>' +
  87. '<paragraph>Foo</paragraph>' +
  88. '<paragraph>BarXyz</paragraph>' +
  89. '</blockQuote>'
  90. );
  91. john.undo();
  92. john.undo();
  93. expectClients(
  94. '<blockQuote>' +
  95. '<paragraph>Foo</paragraph>' +
  96. '<paragraph>Bar</paragraph>' +
  97. '</blockQuote>' +
  98. '<blockQuote>' +
  99. '<paragraph>Xyz</paragraph>' +
  100. '</blockQuote>'
  101. );
  102. } );
  103. it( 'merge, merge #2', () => {
  104. john.setData(
  105. '<blockQuote>' +
  106. '<paragraph>Foo</paragraph>' +
  107. '</blockQuote>' +
  108. '[]' +
  109. '<blockQuote>' +
  110. '<paragraph>Bar</paragraph>' +
  111. '<paragraph>Xyz</paragraph>' +
  112. '</blockQuote>'
  113. );
  114. john.merge();
  115. john.setSelection( [ 0, 1 ] );
  116. john.merge();
  117. expectClients(
  118. '<blockQuote>' +
  119. '<paragraph>FooBar</paragraph>' +
  120. '<paragraph>Xyz</paragraph>' +
  121. '</blockQuote>'
  122. );
  123. john.undo();
  124. john.undo();
  125. expectClients(
  126. '<blockQuote>' +
  127. '<paragraph>Foo</paragraph>' +
  128. '</blockQuote>' +
  129. '<blockQuote>' +
  130. '<paragraph>Bar</paragraph>' +
  131. '<paragraph>Xyz</paragraph>' +
  132. '</blockQuote>'
  133. );
  134. } );
  135. it( 'merge, unwrap', () => {
  136. john.setData( '<paragraph></paragraph>[]<paragraph>Foo</paragraph>' );
  137. john.merge();
  138. john.setSelection( [ 0, 0 ] );
  139. john.unwrap();
  140. john.undo();
  141. john.undo();
  142. expectClients( '<paragraph></paragraph><paragraph>Foo</paragraph>' );
  143. } );
  144. it( 'remove node at the split position #1', () => {
  145. john.setData( '<paragraph>Ab</paragraph>[]<paragraph>Xy</paragraph>' );
  146. john.merge();
  147. john.setSelection( [ 0, 1 ], [ 0, 2 ] );
  148. john.remove();
  149. john.undo();
  150. john.undo();
  151. expectClients( '<paragraph>Ab</paragraph><paragraph>Xy</paragraph>' );
  152. } );
  153. it( 'remove node at the split position #2', () => {
  154. john.setData( '<paragraph>Ab</paragraph>[]<paragraph>Xy</paragraph>' );
  155. john.merge();
  156. john.setSelection( [ 0, 2 ], [ 0, 3 ] );
  157. john.remove();
  158. john.undo();
  159. john.undo();
  160. expectClients( '<paragraph>Ab</paragraph><paragraph>Xy</paragraph>' );
  161. } );
  162. it( 'undoing split after the element created by split has been removed', () => {
  163. // This example is ported here from ckeditor5-undo to keep 100% CC in ckeditor5-engine alone.
  164. john.setData( '<paragraph>Foo[]bar</paragraph>' );
  165. john.split();
  166. john.setSelection( [ 0, 3 ], [ 1, 3 ] );
  167. john.delete();
  168. expectClients( '<paragraph>Foo</paragraph>' );
  169. john.undo();
  170. expectClients( '<paragraph>Foo</paragraph><paragraph>bar</paragraph>' );
  171. john.undo();
  172. expectClients( '<paragraph>Foobar</paragraph>' );
  173. } );
  174. it( 'remove text from paragraph and merge it', () => {
  175. john.setData( '<paragraph>Foo</paragraph><paragraph>[Bar]</paragraph>' );
  176. john.remove();
  177. john.setSelection( [ 1 ] );
  178. john.merge();
  179. expectClients( '<paragraph>Foo</paragraph>' );
  180. john.undo();
  181. expectClients( '<paragraph>Foo</paragraph><paragraph></paragraph>' );
  182. john.undo();
  183. expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  184. } );
  185. it( 'delete split paragraphs', () => {
  186. john.setData( '<paragraph>Foo</paragraph><paragraph>B[]ar</paragraph>' );
  187. john.split();
  188. john.setSelection( [ 2, 1 ] );
  189. john.split();
  190. john.setSelection( [ 1, 0 ], [ 3, 1 ] );
  191. john.delete();
  192. john.setSelection( [ 1 ] );
  193. john.merge();
  194. expectClients( '<paragraph>Foo</paragraph>' );
  195. john.undo();
  196. expectClients( '<paragraph>Foo</paragraph><paragraph></paragraph>' );
  197. john.undo();
  198. expectClients( '<paragraph>Foo</paragraph><paragraph>B</paragraph><paragraph>a</paragraph><paragraph>r</paragraph>' );
  199. john.undo();
  200. expectClients( '<paragraph>Foo</paragraph><paragraph>B</paragraph><paragraph>ar</paragraph>' );
  201. john.undo();
  202. expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  203. john.redo();
  204. expectClients( '<paragraph>Foo</paragraph><paragraph>B</paragraph><paragraph>ar</paragraph>' );
  205. john.redo();
  206. expectClients( '<paragraph>Foo</paragraph><paragraph>B</paragraph><paragraph>a</paragraph><paragraph>r</paragraph>' );
  207. john.redo();
  208. expectClients( '<paragraph>Foo</paragraph><paragraph></paragraph>' );
  209. john.redo();
  210. expectClients( '<paragraph>Foo</paragraph>' );
  211. } );
  212. it( 'pasting on collapsed selection undo and redo', () => {
  213. john.setData( '<paragraph>Foo[]Bar</paragraph>' );
  214. // Below simulates pasting.
  215. john.editor.model.change( () => {
  216. john.split();
  217. john.setSelection( [ 1 ] );
  218. john.insert( '<paragraph>1</paragraph>' );
  219. john.setSelection( [ 1 ] );
  220. john.merge();
  221. john.setSelection( [ 1 ] );
  222. john.insert( '<paragraph>2</paragraph>' );
  223. john.setSelection( [ 2 ] );
  224. john.merge();
  225. } );
  226. expectClients( '<paragraph>Foo1</paragraph><paragraph>2Bar</paragraph>' );
  227. john.undo();
  228. expectClients( '<paragraph>FooBar</paragraph>' );
  229. john.redo();
  230. expectClients( '<paragraph>Foo1</paragraph><paragraph>2Bar</paragraph>' );
  231. john.undo();
  232. expectClients( '<paragraph>FooBar</paragraph>' );
  233. john.redo();
  234. expectClients( '<paragraph>Foo1</paragraph><paragraph>2Bar</paragraph>' );
  235. } );
  236. it( 'selection attribute setting: split, bold, merge, undo, undo, undo', () => {
  237. // This test is ported from undo to keep 100% CC in engine.
  238. john.setData( '<paragraph>Foo[]</paragraph><paragraph>Bar</paragraph>' );
  239. john.split();
  240. john.setSelection( [ 1, 0 ] );
  241. john._processExecute( 'bold' );
  242. john._processExecute( 'forwardDelete' );
  243. expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  244. john.undo();
  245. expectClients( '<paragraph>Foo</paragraph><paragraph selection:bold="true"></paragraph><paragraph>Bar</paragraph>' );
  246. john.undo();
  247. expectClients( '<paragraph>Foo</paragraph><paragraph></paragraph><paragraph>Bar</paragraph>' );
  248. john.undo();
  249. expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  250. } );
  251. // https://github.com/ckeditor/ckeditor5/issues/1288
  252. it( 'remove two groups of blocks then undo, undo', () => {
  253. john.setData(
  254. '<paragraph>X</paragraph><paragraph>A</paragraph><paragraph>B[</paragraph><paragraph>C</paragraph><paragraph>D]</paragraph>'
  255. );
  256. john.delete();
  257. john.setSelection( [ 0, 1 ], [ 2, 1 ] );
  258. john.delete();
  259. expectClients( '<paragraph>X</paragraph>' );
  260. john.undo();
  261. expectClients( '<paragraph>X</paragraph><paragraph>A</paragraph><paragraph>B</paragraph>' );
  262. john.undo();
  263. expectClients(
  264. '<paragraph>X</paragraph><paragraph>A</paragraph><paragraph>B</paragraph><paragraph>C</paragraph><paragraph>D</paragraph>'
  265. );
  266. } );
  267. // https://github.com/ckeditor/ckeditor5/issues/1287 TC1
  268. it( 'pasting on non-collapsed selection undo and redo', () => {
  269. john.setData( '<paragraph>Fo[o</paragraph><paragraph>B]ar</paragraph>' );
  270. // Below simulates pasting.
  271. john.editor.model.change( () => {
  272. john.editor.model.deleteContent( john.document.selection );
  273. john.setSelection( [ 0, 2 ] );
  274. john.split();
  275. john.setSelection( [ 1 ] );
  276. john.insert( '<paragraph>1</paragraph>' );
  277. john.setSelection( [ 1 ] );
  278. john.merge();
  279. john.setSelection( [ 1 ] );
  280. john.insert( '<paragraph>2</paragraph>' );
  281. john.setSelection( [ 2 ] );
  282. john.merge();
  283. } );
  284. expectClients( '<paragraph>Fo1</paragraph><paragraph>2ar</paragraph>' );
  285. john.undo();
  286. expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  287. john.redo();
  288. expectClients( '<paragraph>Fo1</paragraph><paragraph>2ar</paragraph>' );
  289. john.undo();
  290. expectClients( '<paragraph>Foo</paragraph><paragraph>Bar</paragraph>' );
  291. } );
  292. it( 'collapsed marker at the beginning of merged element then undo', () => {
  293. john.setData( '<paragraph>Foo</paragraph><paragraph>[]Bar</paragraph>' );
  294. john.setMarker( 'm1' );
  295. john.setSelection( [ 1 ] );
  296. john.merge();
  297. expectClients( '<paragraph>Foo<m1:start></m1:start>Bar</paragraph>' );
  298. john.undo();
  299. expectClients( '<paragraph>Foo</paragraph><paragraph><m1:start></m1:start>Bar</paragraph>' );
  300. } );
  301. it( 'collapsed marker at the end of merge-target element then undo', () => {
  302. john.setData( '<paragraph>Foo[]</paragraph><paragraph>Bar</paragraph>' );
  303. john.setMarker( 'm1' );
  304. john.setSelection( [ 1 ] );
  305. john.merge();
  306. expectClients( '<paragraph>Foo<m1:start></m1:start>Bar</paragraph>' );
  307. john.undo();
  308. expectClients( '<paragraph>Foo<m1:start></m1:start></paragraph><paragraph>Bar</paragraph>' );
  309. } );
  310. it( 'empty marker between merged elements then undo', () => {
  311. john.setData( '<paragraph>Foo[</paragraph><paragraph>]Bar</paragraph>' );
  312. john.setMarker( 'm1' );
  313. john.setSelection( [ 1 ] );
  314. john.merge();
  315. expectClients( '<paragraph>Foo<m1:start></m1:start>Bar</paragraph>' );
  316. john.undo();
  317. expectClients( '<paragraph>Foo<m1:start></m1:start></paragraph><paragraph><m1:end></m1:end>Bar</paragraph>' );
  318. } );
  319. it( 'left side of marker moved then undo', () => {
  320. john.setData( '<paragraph>Foo[bar]</paragraph><paragraph></paragraph>' );
  321. john.setMarker( 'm1' );
  322. john.setSelection( [ 0, 2 ], [ 0, 4 ] );
  323. john.move( [ 1, 0 ] );
  324. expectClients( '<paragraph>Fo<m1:start></m1:start>ar<m1:end></m1:end></paragraph><paragraph>ob</paragraph>' );
  325. john.undo();
  326. expectClients( '<paragraph>Foo<m1:start></m1:start>bar<m1:end></m1:end></paragraph><paragraph></paragraph>' );
  327. } );
  328. it( 'right side of marker moved then undo', () => {
  329. john.setData( '<paragraph>[Foo]bar</paragraph><paragraph></paragraph>' );
  330. john.setMarker( 'm1' );
  331. john.setSelection( [ 0, 2 ], [ 0, 4 ] );
  332. john.move( [ 1, 0 ] );
  333. expectClients( '<paragraph><m1:start></m1:start>Fo<m1:end></m1:end>ar</paragraph><paragraph>ob</paragraph>' );
  334. john.undo();
  335. expectClients( '<paragraph><m1:start></m1:start>Foo<m1:end></m1:end>bar</paragraph><paragraph></paragraph>' );
  336. } );
  337. it( 'marker on closing and opening tag - remove multiple elements #1', () => {
  338. john.setData(
  339. '<paragraph>Abc</paragraph>' +
  340. '<paragraph>Foo[</paragraph>' +
  341. '<paragraph>]Bar</paragraph>'
  342. );
  343. john.setMarker( 'm1' );
  344. john.setSelection( [ 0, 1 ], [ 2, 2 ] );
  345. john._processExecute( 'delete' );
  346. expectClients( '<paragraph>A<m1:start></m1:start>r</paragraph>' );
  347. john.undo();
  348. expectClients(
  349. '<paragraph>Abc</paragraph>' +
  350. '<paragraph>Foo<m1:start></m1:start></paragraph>' +
  351. '<paragraph><m1:end></m1:end>Bar</paragraph>'
  352. );
  353. } );
  354. it( 'marker on closing and opening tag - remove multiple elements #2', () => {
  355. john.setData(
  356. '<paragraph>Foo[</paragraph>' +
  357. '<paragraph>]Bar</paragraph>' +
  358. '<paragraph>Xyz</paragraph>'
  359. );
  360. john.setMarker( 'm1' );
  361. john.setSelection( [ 0, 1 ], [ 2, 2 ] );
  362. john._processExecute( 'delete' );
  363. expectClients( '<paragraph>F<m1:start></m1:start>z</paragraph>' );
  364. john.undo();
  365. expectClients(
  366. '<paragraph>Foo<m1:start></m1:start></paragraph>' +
  367. '<paragraph><m1:end></m1:end>Bar</paragraph>' +
  368. '<paragraph>Xyz</paragraph>'
  369. );
  370. } );
  371. it( 'marker on closing and opening tag + some text - merge elements + remove text', () => {
  372. john.setData(
  373. '<paragraph>Foo[</paragraph>' +
  374. '<paragraph>B]ar</paragraph>'
  375. );
  376. john.setMarker( 'm1' );
  377. john.setSelection( [ 0, 1 ], [ 1, 2 ] );
  378. john._processExecute( 'delete' );
  379. expectClients( '<paragraph>F<m1:start></m1:start>r</paragraph>' );
  380. john.undo();
  381. expectClients(
  382. '<paragraph>Foo<m1:start></m1:start></paragraph>' +
  383. '<paragraph>B<m1:end></m1:end>ar</paragraph>'
  384. );
  385. } );
  386. // https://github.com/ckeditor/ckeditor5-engine/issues/1668
  387. it( 'marker and moves with undo-redo-undo', () => {
  388. john.setData( '<paragraph>X[]Y</paragraph>' );
  389. const inputBufferBatch = john.editor.commands.get( 'input' ).buffer.batch;
  390. john.editor.model.enqueueChange( inputBufferBatch, () => {
  391. john.type( 'a' );
  392. john.type( 'b' );
  393. john.type( 'c' );
  394. john.setSelection( [ 0, 1 ], [ 0, 4 ] );
  395. john.setMarker( 'm1' );
  396. } );
  397. expectClients( '<paragraph>X<m1:start></m1:start>abc<m1:end></m1:end>Y</paragraph>' );
  398. john.setSelection( [ 0, 0 ], [ 0, 5 ] );
  399. john._processExecute( 'delete' );
  400. expectClients( '<paragraph></paragraph>' );
  401. john.undo();
  402. expectClients( '<paragraph>X<m1:start></m1:start>abc<m1:end></m1:end>Y</paragraph>' );
  403. john.undo();
  404. expectClients( '<paragraph>XY</paragraph>' );
  405. john.redo();
  406. expectClients( '<paragraph>X<m1:start></m1:start>abc<m1:end></m1:end>Y</paragraph>' );
  407. john.redo();
  408. expectClients( '<paragraph></paragraph>' );
  409. john.undo();
  410. expectClients( '<paragraph>X<m1:start></m1:start>abc<m1:end></m1:end>Y</paragraph>' );
  411. john.undo();
  412. expectClients( '<paragraph>XY</paragraph>' );
  413. } );
  414. // https://github.com/ckeditor/ckeditor5/issues/1385
  415. it( 'paste inside paste + undo, undo + redo, redo', () => {
  416. const model = john.editor.model;
  417. john.setData( '<paragraph>[]</paragraph>' );
  418. model.insertContent( getPastedContent() );
  419. john.setSelection( [ 0, 3 ] );
  420. model.insertContent( getPastedContent() );
  421. expectClients( '<heading1>FooFoobarbar</heading1>' );
  422. john.undo();
  423. expectClients( '<heading1>Foobar</heading1>' );
  424. john.undo();
  425. expectClients( '<paragraph></paragraph>' );
  426. john.redo();
  427. expectClients( '<heading1>Foobar</heading1>' );
  428. john.redo();
  429. expectClients( '<heading1>FooFoobarbar</heading1>' );
  430. function getPastedContent() {
  431. return new Element( 'heading1', null, new Text( 'Foobar' ) );
  432. }
  433. } );
  434. } );