liverange.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import Batch from '../../src/model/batch';
  6. import Model from '../../src/model/model';
  7. import Element from '../../src/model/element';
  8. import Position from '../../src/model/position';
  9. import LiveRange from '../../src/model/liverange';
  10. import Range from '../../src/model/range';
  11. import Text from '../../src/model/text';
  12. import { stringify, setData } from '../../src/dev-utils/model';
  13. describe( 'LiveRange', () => {
  14. let model, doc, root, ul, p;
  15. beforeEach( () => {
  16. model = new Model();
  17. doc = model.document;
  18. root = doc.createRoot();
  19. const lis = [
  20. new Element( 'li', [], new Text( 'aaaaaaaaaa' ) ),
  21. new Element( 'li', [], new Text( 'bbbbbbbbbb' ) ),
  22. new Element( 'li', [], new Text( 'cccccccccc' ) ),
  23. new Element( 'li', [], new Text( 'dddddddddd' ) ),
  24. new Element( 'li', [], new Text( 'eeeeeeeeee' ) ),
  25. new Element( 'li', [], new Text( 'ffffffffff' ) ),
  26. new Element( 'li', [], new Text( 'gggggggggg' ) ),
  27. new Element( 'li', [], new Text( 'hhhhhhhhhh' ) )
  28. ];
  29. ul = new Element( 'ul', [], lis );
  30. p = new Element( 'p', [], new Text( 'qwertyuiop' ) );
  31. root._insertChild( 0, [ ul, p, new Text( 'xyzxyz' ) ] );
  32. } );
  33. it( 'should be an instance of Range', () => {
  34. const live = new LiveRange( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) );
  35. live.detach();
  36. expect( live ).to.be.instanceof( Range );
  37. } );
  38. it( 'should listen to the model applyOperation event', () => {
  39. sinon.spy( LiveRange.prototype, 'listenTo' );
  40. const live = new LiveRange( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) );
  41. live.detach();
  42. expect( live.listenTo.calledWith( model, 'applyOperation' ) ).to.be.true;
  43. LiveRange.prototype.listenTo.restore();
  44. } );
  45. it( 'should stop listening when detached', () => {
  46. sinon.spy( LiveRange.prototype, 'stopListening' );
  47. const live = new LiveRange( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) );
  48. live.detach();
  49. expect( live.stopListening.called ).to.be.true;
  50. LiveRange.prototype.stopListening.restore();
  51. } );
  52. it( 'createIn should return LiveRange', () => {
  53. const range = LiveRange.createIn( p );
  54. expect( range ).to.be.instanceof( LiveRange );
  55. range.detach();
  56. } );
  57. it( 'createFromParentsAndOffsets should return LiveRange', () => {
  58. const range = LiveRange.createFromParentsAndOffsets( root, 0, p, 2 );
  59. expect( range ).to.be.instanceof( LiveRange );
  60. range.detach();
  61. } );
  62. it( 'createFromPositionAndShift should return LiveRange', () => {
  63. const range = LiveRange.createFromPositionAndShift( new Position( root, [ 0, 1 ] ), 4 );
  64. expect( range ).to.be.instanceof( LiveRange );
  65. range.detach();
  66. } );
  67. it( 'createFromRange should return LiveRange', () => {
  68. const range = LiveRange.createFromRange( new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ) );
  69. expect( range ).to.be.instanceof( LiveRange );
  70. range.detach();
  71. } );
  72. it( 'should fire change:range event with proper data when its boundaries are changed', () => {
  73. const live = new LiveRange( new Position( root, [ 0, 1, 4 ] ), new Position( root, [ 0, 2, 2 ] ) );
  74. const copy = Range.createFromRange( live );
  75. const spy = sinon.spy();
  76. live.on( 'change:range', spy );
  77. const sourcePosition = new Position( root, [ 2 ] );
  78. const targetPosition = new Position( root, [ 0 ] );
  79. const batch = new Batch();
  80. let op = null;
  81. model.enqueueChange( batch, writer => {
  82. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 1 );
  83. writer.move( sourceRange, targetPosition );
  84. op = batch.operations[ 0 ];
  85. } );
  86. expect( spy.calledOnce ).to.be.true;
  87. // First parameter available in event should be a range that is equal to the live range before the live range changed.
  88. expect( spy.args[ 0 ][ 1 ].isEqual( copy ) ).to.be.true;
  89. // Second parameter is the operation that changed the range.
  90. expect( spy.args[ 0 ][ 2 ] ).to.equal( op );
  91. } );
  92. it( 'should fire change:content event with proper data when content inside the range has changed', () => {
  93. const live = new LiveRange( new Position( root, [ 0, 1 ] ), new Position( root, [ 0, 3 ] ) );
  94. const spy = sinon.spy();
  95. live.on( 'change:content', spy );
  96. const sourcePosition = new Position( root, [ 0, 2, 0 ] );
  97. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  98. const batch = new Batch();
  99. let op = null;
  100. model.enqueueChange( batch, writer => {
  101. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 2 );
  102. writer.move( sourceRange, targetPosition );
  103. op = batch.operations[ 0 ];
  104. } );
  105. expect( spy.calledOnce ).to.be.true;
  106. // First parameter available in event should be a range that is equal to the live range before the live range changed.
  107. expect( spy.args[ 0 ][ 1 ].isEqual( live ) ).to.be.true;
  108. // Second parameter is the operation that changed the range.
  109. expect( spy.args[ 0 ][ 2 ] ).to.equal( op );
  110. } );
  111. describe( 'should get transformed and fire change:range if', () => {
  112. let live, spy;
  113. beforeEach( () => {
  114. live = new LiveRange( new Position( root, [ 0, 1, 4 ] ), new Position( root, [ 0, 2, 2 ] ) );
  115. spy = sinon.spy();
  116. live.on( 'change:range', spy );
  117. } );
  118. afterEach( () => {
  119. live.detach();
  120. } );
  121. describe( 'insertion', () => {
  122. it( 'is in the same parent as range start and before it', () => {
  123. model.change( writer => {
  124. writer.insertText( 'xxx', new Position( root, [ 0, 1, 0 ] ) );
  125. } );
  126. expect( live.start.path ).to.deep.equal( [ 0, 1, 7 ] );
  127. expect( live.end.path ).to.deep.equal( [ 0, 2, 2 ] );
  128. expect( spy.calledOnce ).to.be.true;
  129. } );
  130. it( 'is in the same parent as range end and before it', () => {
  131. model.change( writer => {
  132. writer.insertText( 'xxx', new Position( root, [ 0, 2, 0 ] ) );
  133. } );
  134. expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
  135. expect( live.end.path ).to.deep.equal( [ 0, 2, 5 ] );
  136. expect( spy.calledOnce ).to.be.true;
  137. } );
  138. it( 'is at a position before a node from range start path', () => {
  139. model.change( writer => {
  140. writer.insert( new Element( 'li' ), new Position( root, [ 0, 0 ] ) );
  141. } );
  142. expect( live.start.path ).to.deep.equal( [ 0, 2, 4 ] );
  143. expect( live.end.path ).to.deep.equal( [ 0, 3, 2 ] );
  144. expect( spy.calledOnce ).to.be.true;
  145. } );
  146. it( 'is at a position before a node from range end path', () => {
  147. model.change( writer => {
  148. writer.insert( new Element( 'li' ), new Position( root, [ 0, 2 ] ) );
  149. } );
  150. expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
  151. expect( live.end.path ).to.deep.equal( [ 0, 3, 2 ] );
  152. expect( spy.calledOnce ).to.be.true;
  153. } );
  154. it( 'is at the live range start position and live range is collapsed', () => {
  155. live.end.path = [ 0, 1, 4 ];
  156. model.change( writer => {
  157. writer.insertText( 'xxx', new Position( root, [ 0, 1, 4 ] ) );
  158. } );
  159. expect( live.start.path ).to.deep.equal( [ 0, 1, 7 ] );
  160. expect( live.end.path ).to.deep.equal( [ 0, 1, 7 ] );
  161. expect( spy.calledOnce ).to.be.true;
  162. } );
  163. } );
  164. describe( 'range move', () => {
  165. it( 'is to the same parent as range start and before it', () => {
  166. model.change( writer => {
  167. const sourcePosition = new Position( root, [ 0, 4, 0 ] );
  168. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 4 );
  169. const targetPosition = new Position( root, [ 0, 1, 0 ] );
  170. writer.move( sourceRange, targetPosition );
  171. } );
  172. expect( live.start.path ).to.deep.equal( [ 0, 1, 8 ] );
  173. expect( live.end.path ).to.deep.equal( [ 0, 2, 2 ] );
  174. expect( spy.calledOnce ).to.be.true;
  175. } );
  176. it( 'is to the same parent as range end and before it', () => {
  177. model.change( writer => {
  178. const sourcePosition = new Position( root, [ 0, 4, 0 ] );
  179. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 4 );
  180. const targetPosition = new Position( root, [ 0, 2, 0 ] );
  181. writer.move( sourceRange, targetPosition );
  182. } );
  183. expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
  184. expect( live.end.path ).to.deep.equal( [ 0, 2, 6 ] );
  185. expect( spy.calledOnce ).to.be.true;
  186. } );
  187. it( 'is to a position before a node from range start path', () => {
  188. model.change( writer => {
  189. const sourcePosition = new Position( root, [ 0, 4 ] );
  190. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 2 );
  191. const targetPosition = new Position( root, [ 0, 0 ] );
  192. writer.move( sourceRange, targetPosition );
  193. } );
  194. expect( live.start.path ).to.deep.equal( [ 0, 3, 4 ] );
  195. expect( live.end.path ).to.deep.equal( [ 0, 4, 2 ] );
  196. expect( spy.calledOnce ).to.be.true;
  197. } );
  198. it( 'is to a position before a node from range end path', () => {
  199. model.change( writer => {
  200. const sourcePosition = new Position( root, [ 0, 4 ] );
  201. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 1 );
  202. const targetPosition = new Position( root, [ 0, 2 ] );
  203. writer.move( sourceRange, targetPosition );
  204. } );
  205. expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
  206. expect( live.end.path ).to.deep.equal( [ 0, 3, 2 ] );
  207. expect( spy.calledOnce ).to.be.true;
  208. } );
  209. it( 'is from the same parent as range start and before it', () => {
  210. model.change( writer => {
  211. const sourcePosition = new Position( root, [ 0, 1, 0 ] );
  212. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 3 );
  213. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  214. writer.move( sourceRange, targetPosition );
  215. } );
  216. expect( live.start.path ).to.deep.equal( [ 0, 1, 1 ] );
  217. expect( live.end.path ).to.deep.equal( [ 0, 2, 2 ] );
  218. expect( spy.calledOnce ).to.be.true;
  219. } );
  220. it( 'is from the same parent as range end and before it - #1', () => {
  221. model.change( writer => {
  222. const sourcePosition = new Position( root, [ 0, 2, 0 ] );
  223. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 1 );
  224. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  225. writer.move( sourceRange, targetPosition );
  226. } );
  227. expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
  228. expect( live.end.path ).to.deep.equal( [ 0, 2, 1 ] );
  229. expect( spy.calledOnce ).to.be.true;
  230. } );
  231. it( 'is from the same parent as range end and before it - #2', () => {
  232. model.change( writer => {
  233. const sourcePosition = new Position( root, [ 0, 2, 0 ] );
  234. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 2 );
  235. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  236. writer.move( sourceRange, targetPosition );
  237. } );
  238. expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
  239. expect( live.end.path ).to.deep.equal( [ 0, 2, 0 ] );
  240. expect( spy.calledOnce ).to.be.true;
  241. } );
  242. it( 'is from a position before a node from range start path', () => {
  243. model.change( writer => {
  244. const sourcePosition = new Position( root, [ 0, 0 ] );
  245. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 1 );
  246. const targetPosition = new Position( root, [ 0, 4 ] );
  247. writer.move( sourceRange, targetPosition );
  248. } );
  249. expect( live.start.path ).to.deep.equal( [ 0, 0, 4 ] );
  250. expect( live.end.path ).to.deep.equal( [ 0, 1, 2 ] );
  251. expect( spy.calledOnce ).to.be.true;
  252. } );
  253. it( 'intersects on live range left side', () => {
  254. model.change( writer => {
  255. const sourcePosition = new Position( root, [ 0, 1, 2 ] );
  256. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 4 );
  257. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  258. writer.move( sourceRange, targetPosition );
  259. } );
  260. expect( live.start.path ).to.deep.equal( [ 0, 1, 2 ] );
  261. expect( live.end.path ).to.deep.equal( [ 0, 2, 2 ] );
  262. expect( spy.calledOnce ).to.be.true;
  263. } );
  264. it( 'intersects on live range right side', () => {
  265. model.change( writer => {
  266. const sourcePosition = new Position( root, [ 0, 2, 1 ] );
  267. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 4 );
  268. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  269. writer.move( sourceRange, targetPosition );
  270. } );
  271. expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
  272. expect( live.end.path ).to.deep.equal( [ 0, 2, 1 ] );
  273. expect( spy.calledOnce ).to.be.true;
  274. } );
  275. it( 'is equal to live range', () => {
  276. live.end.path = [ 0, 1, 7 ];
  277. model.change( writer => {
  278. const sourcePosition = new Position( root, [ 0, 1, 4 ] );
  279. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 3 );
  280. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  281. writer.move( sourceRange, targetPosition );
  282. } );
  283. expect( live.start.path ).to.deep.equal( [ 0, 4, 0 ] );
  284. expect( live.end.path ).to.deep.equal( [ 0, 4, 3 ] );
  285. expect( spy.calledOnce ).to.be.true;
  286. } );
  287. it( 'contains live range', () => {
  288. live.end.path = [ 0, 1, 6 ];
  289. model.change( writer => {
  290. const sourcePosition = new Position( root, [ 0, 1, 3 ] );
  291. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 5 );
  292. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  293. writer.move( sourceRange, targetPosition );
  294. } );
  295. expect( live.start.path ).to.deep.equal( [ 0, 4, 1 ] );
  296. expect( live.end.path ).to.deep.equal( [ 0, 4, 3 ] );
  297. expect( spy.calledOnce ).to.be.true;
  298. } );
  299. it( 'is intersecting with live range on left and points to live range', () => {
  300. live.end.path = [ 0, 1, 7 ];
  301. model.change( writer => {
  302. const sourcePosition = new Position( root, [ 0, 1, 2 ] );
  303. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 3 );
  304. const targetPosition = new Position( root, [ 0, 1, 8 ] );
  305. writer.move( sourceRange, targetPosition );
  306. } );
  307. expect( live.start.path ).to.deep.equal( [ 0, 1, 2 ] );
  308. expect( live.end.path ).to.deep.equal( [ 0, 1, 4 ] );
  309. expect( spy.calledOnce ).to.be.true;
  310. } );
  311. it( 'is intersecting with live range on right and is moved into live range', () => {
  312. model.change( writer => {
  313. const sourcePosition = new Position( root, [ 0, 2, 1 ] );
  314. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 5 );
  315. const targetPosition = new Position( root, [ 0, 2, 0 ] );
  316. writer.move( sourceRange, targetPosition );
  317. } );
  318. expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
  319. expect( live.end.path ).to.deep.equal( [ 0, 2, 1 ] );
  320. expect( spy.calledOnce ).to.be.true;
  321. } );
  322. } );
  323. describe( 'wrap', () => {
  324. // NOTE: it overrides the variable defined globally in these tests.
  325. // These tests need to be rewritten to use the batch API anyway and then this variable can be removed.
  326. let live;
  327. beforeEach( () => {
  328. model.schema.register( 'p', { inheritAllFrom: '$block' } );
  329. model.schema.register( 'w' );
  330. model.schema.extend( 'p', { allowIn: 'w' } );
  331. model.schema.extend( 'w', { allowIn: '$root' } );
  332. } );
  333. afterEach( () => {
  334. live.detach();
  335. } );
  336. it( 'is inside the wrapped range', () => {
  337. setData( model, '<p>x</p><p>[a]</p><p>x</p>' );
  338. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  339. model.change( writer => {
  340. // [<p>a</p>]
  341. writer.wrap( new Range( new Position( root, [ 1 ] ), new Position( root, [ 2 ] ) ), 'w' );
  342. } );
  343. expect( stringify( root, live ) ).to.equal( '<p>x</p><w><p>[a]</p></w><p>x</p>' );
  344. } );
  345. it( 'its start is intersecting with the wrapped range', () => {
  346. setData( model, '<p>a[b</p><p>x</p><p>c]d</p>' );
  347. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  348. model.change( writer => {
  349. // [<p>ab</p>]
  350. writer.wrap( new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ), 'w' );
  351. } );
  352. expect( stringify( root, live ) ).to.equal( '<w><p>a[b</p></w><p>x</p><p>c]d</p>' );
  353. } );
  354. it( 'its end is intersecting with the wrapped range', () => {
  355. setData( model, '<p>a[b</p><p>x</p><p>c]d</p>' );
  356. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  357. model.change( writer => {
  358. // [<p>cd</p>]
  359. writer.wrap( new Range( new Position( root, [ 2 ] ), new Position( root, [ 3 ] ) ), 'w' );
  360. } );
  361. expect( stringify( root, live ) ).to.equal( '<p>a[b</p><p>x</p><w><p>c]d</p></w>' );
  362. } );
  363. it( 'its start is intersecting with the wrapped range (multilpe elements)', () => {
  364. setData( model, '<p>a[b</p><p>x</p><p>c]d</p>' );
  365. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  366. model.change( writer => {
  367. // [<p>ab</p><p>x</p>]
  368. writer.wrap( new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ), 'w' );
  369. } );
  370. expect( stringify( root, live ) ).to.equal( '<w><p>a[b</p><p>x</p></w><p>c]d</p>' );
  371. } );
  372. it( 'its end is intersecting with the wrapped range (multiple elements)', () => {
  373. setData( model, '<p>a[b</p><p>x</p><p>c]d</p>' );
  374. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  375. model.change( writer => {
  376. // [<p>x</p><p>cd</p>]
  377. writer.wrap( new Range( new Position( root, [ 1 ] ), new Position( root, [ 3 ] ) ), 'w' );
  378. } );
  379. expect( stringify( root, live ) ).to.equal( '<p>a[b</p><w><p>x</p><p>c]d</p></w>' );
  380. } );
  381. it( 'contains element to wrap', () => {
  382. setData( model, '<p>a[b</p><p>x</p><p>c]d</p>' );
  383. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  384. model.change( writer => {
  385. // [<p>x</p>]
  386. writer.wrap( new Range( new Position( root, [ 1 ] ), new Position( root, [ 2 ] ) ), 'w' );
  387. } );
  388. expect( stringify( root, live ) ).to.equal( '<p>a[b</p><w><p>x</p></w><p>c]d</p>' );
  389. } );
  390. } );
  391. describe( 'unwrap', () => {
  392. // NOTE: it overrides the variable defined globally in these tests.
  393. // These tests need to be rewritten to use the batch API anyway and then this variable can be removed.
  394. let live;
  395. beforeEach( () => {
  396. model.schema.register( 'p', { inheritAllFrom: '$block' } );
  397. model.schema.register( 'w' );
  398. model.schema.extend( 'p', { allowIn: 'w' } );
  399. model.schema.extend( 'w', { allowIn: '$root' } );
  400. } );
  401. afterEach( () => {
  402. live.detach();
  403. } );
  404. it( 'is inside the wrapper to remove', () => {
  405. setData( model, '<p>x</p><w><p>[a]</p></w><p>x</p>' );
  406. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  407. model.change( writer => {
  408. writer.unwrap( root.getChild( 1 ) );
  409. } );
  410. expect( stringify( root, live ) ).to.equal( '<p>x</p><p>[a]</p><p>x</p>' );
  411. } );
  412. it( 'its start is intersecting with the wrapper to remove', () => {
  413. setData( model, '<w><p>a[b</p></w><p>c]d</p>' );
  414. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  415. model.change( writer => {
  416. writer.unwrap( root.getChild( 0 ) );
  417. } );
  418. expect( stringify( root, live ) ).to.equal( '<p>a[b</p><p>c]d</p>' );
  419. } );
  420. it( 'its end is intersecting with the wrapper to remove', () => {
  421. setData( model, '<p>a[b</p><w><p>c]d</p></w>' );
  422. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  423. model.change( writer => {
  424. writer.unwrap( root.getChild( 1 ) );
  425. } );
  426. expect( stringify( root, live ) ).to.equal( '<p>a[b</p><p>c]d</p>' );
  427. } );
  428. it( 'its start is intersecting with the wrapper to remove (multiple elements)', () => {
  429. setData( model, '<w><p>a[b</p><p>x</p></w><p>c]d</p>' );
  430. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  431. model.change( writer => {
  432. writer.unwrap( root.getChild( 0 ) );
  433. } );
  434. expect( stringify( root, live ) ).to.equal( '<p>a[b</p><p>x</p><p>c]d</p>' );
  435. } );
  436. it( 'its end is intersecting with the wrapper to remove (multiple elements)', () => {
  437. setData( model, '<p>a[b</p><w><p>x</p><p>c]d</p></w>' );
  438. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  439. model.change( writer => {
  440. writer.unwrap( root.getChild( 1 ) );
  441. } );
  442. expect( stringify( root, live ) ).to.equal( '<p>a[b</p><p>x</p><p>c]d</p>' );
  443. } );
  444. it( 'contains wrapped element', () => {
  445. setData( model, '<p>a[b</p><w><p>x</p></w><p>c]d</p>' );
  446. live = new LiveRange( doc.selection.getFirstPosition(), doc.selection.getLastPosition() );
  447. model.change( writer => {
  448. writer.unwrap( root.getChild( 1 ) );
  449. } );
  450. expect( stringify( root, live ) ).to.equal( '<p>a[b</p><p>x</p><p>c]d</p>' );
  451. } );
  452. } );
  453. } );
  454. describe( 'should not get transformed but fire change:content', () => {
  455. let spy, live, clone;
  456. beforeEach( () => {
  457. live = new LiveRange( new Position( root, [ 0, 1, 4 ] ), new Position( root, [ 0, 2, 2 ] ) );
  458. clone = Range.createFromRange( live );
  459. spy = sinon.spy();
  460. live.on( 'change:content', spy );
  461. } );
  462. afterEach( () => {
  463. live.detach();
  464. } );
  465. describe( 'insertion', () => {
  466. it( 'inside the range', () => {
  467. model.change( writer => {
  468. writer.insertText( 'xxx', new Position( root, [ 0, 1, 7 ] ) );
  469. } );
  470. expect( live.isEqual( clone ) ).to.be.true;
  471. expect( spy.calledOnce ).to.be.true;
  472. } );
  473. } );
  474. describe( 'range move', () => {
  475. it( 'inside the range', () => {
  476. model.change( writer => {
  477. const sourcePosition = new Position( root, [ 0, 4, 0 ] );
  478. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 3 );
  479. const targetPosition = new Position( root, [ 0, 1, 5 ] );
  480. writer.move( sourceRange, targetPosition );
  481. } );
  482. expect( live.isEqual( clone ) ).to.be.true;
  483. expect( spy.calledOnce ).to.be.true;
  484. } );
  485. it( 'from the range', () => {
  486. model.change( writer => {
  487. const sourcePosition = new Position( root, [ 0, 1, 5 ] );
  488. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 2 );
  489. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  490. writer.move( sourceRange, targetPosition );
  491. } );
  492. expect( live.isEqual( clone ) ).to.be.true;
  493. expect( spy.calledOnce ).to.be.true;
  494. } );
  495. it( 'from the beginning of range', () => {
  496. model.change( writer => {
  497. const sourcePosition = new Position( root, [ 0, 1, 4 ] );
  498. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 2 );
  499. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  500. writer.move( sourceRange, targetPosition );
  501. } );
  502. expect( live.isEqual( clone ) ).to.be.true;
  503. expect( spy.calledOnce ).to.be.true;
  504. } );
  505. it( 'from the range to the range', () => {
  506. live.end.path = [ 0, 1, 8 ];
  507. model.change( writer => {
  508. const sourcePosition = new Position( root, [ 0, 1, 5 ] );
  509. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 1 );
  510. const targetPosition = new Position( root, [ 0, 1, 7 ] );
  511. writer.move( sourceRange, targetPosition );
  512. } );
  513. expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
  514. expect( live.end.path ).to.deep.equal( [ 0, 1, 8 ] );
  515. expect( spy.calledOnce ).to.be.true;
  516. } );
  517. } );
  518. } );
  519. describe( 'should not get transformed and not fire change event if', () => {
  520. let otherRoot, spy, live, clone;
  521. beforeEach( () => {
  522. otherRoot = doc.createRoot( '$root', 'otherRoot' );
  523. live = new LiveRange( new Position( root, [ 0, 1, 4 ] ), new Position( root, [ 0, 2, 2 ] ) );
  524. clone = Range.createFromRange( live );
  525. spy = sinon.spy();
  526. live.on( 'change', spy );
  527. } );
  528. afterEach( () => {
  529. live.detach();
  530. } );
  531. describe( 'insertion', () => {
  532. it( 'is in the same parent as range end and after it', () => {
  533. model.change( writer => {
  534. writer.insertText( 'foo', new Position( root, [ 0, 2, 7 ] ) );
  535. } );
  536. expect( live.isEqual( clone ) ).to.be.true;
  537. expect( spy.called ).to.be.false;
  538. } );
  539. it( 'is to a position after a node from range end path', () => {
  540. model.change( writer => {
  541. writer.insert( new Element( 'li' ), new Position( root, [ 3 ] ) );
  542. } );
  543. expect( live.isEqual( clone ) ).to.be.true;
  544. expect( spy.called ).to.be.false;
  545. } );
  546. it( 'is in different root', () => {
  547. model.change( writer => {
  548. writer.insert( new Element( 'li' ), new Position( otherRoot, [ 0 ] ) );
  549. } );
  550. expect( live.isEqual( clone ) ).to.be.true;
  551. expect( spy.called ).to.be.false;
  552. } );
  553. } );
  554. describe( 'range move', () => {
  555. it( 'is to the same parent as range end and after it', () => {
  556. model.change( writer => {
  557. const sourcePosition = new Position( root, [ 0, 4, 0 ] );
  558. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 3 );
  559. const targetPosition = new Position( root, [ 0, 2, 4 ] );
  560. writer.move( sourceRange, targetPosition );
  561. } );
  562. expect( live.isEqual( clone ) ).to.be.true;
  563. expect( spy.called ).to.be.false;
  564. } );
  565. it( 'is to a position after a node from range end path', () => {
  566. model.change( writer => {
  567. const sourcePosition = new Position( root, [ 0, 5 ] );
  568. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 1 );
  569. const targetPosition = new Position( root, [ 0, 4 ] );
  570. writer.move( sourceRange, targetPosition );
  571. } );
  572. expect( live.isEqual( clone ) ).to.be.true;
  573. expect( spy.called ).to.be.false;
  574. } );
  575. it( 'is from the same parent as range end and after it', () => {
  576. model.change( writer => {
  577. const sourcePosition = new Position( root, [ 0, 2, 4 ] );
  578. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 3 );
  579. const targetPosition = new Position( root, [ 0, 4, 0 ] );
  580. writer.move( sourceRange, targetPosition );
  581. } );
  582. expect( live.isEqual( clone ) ).to.be.true;
  583. expect( spy.called ).to.be.false;
  584. } );
  585. it( 'is from a position after a node from range end path', () => {
  586. model.change( writer => {
  587. const sourcePosition = new Position( root, [ 0, 4 ] );
  588. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 1 );
  589. const targetPosition = new Position( root, [ 0, 5 ] );
  590. writer.move( sourceRange, targetPosition );
  591. } );
  592. expect( live.isEqual( clone ) ).to.be.true;
  593. expect( spy.called ).to.be.false;
  594. } );
  595. it( 'is to different root', () => {
  596. model.change( writer => {
  597. const sourcePosition = new Position( root, [ 0, 4 ] );
  598. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 1 );
  599. const targetPosition = new Position( otherRoot, [ 0 ] );
  600. writer.move( sourceRange, targetPosition );
  601. } );
  602. expect( live.isEqual( clone ) ).to.be.true;
  603. expect( spy.called ).to.be.false;
  604. } );
  605. it( 'is from different root', () => {
  606. model.change( writer => {
  607. writer.insertText( 'foo', new Position( otherRoot, [ 0 ] ) );
  608. const sourcePosition = new Position( otherRoot, [ 0 ] );
  609. const sourceRange = Range.createFromPositionAndShift( sourcePosition, 1 );
  610. const targetPosition = new Position( root, [ 0, 4 ] );
  611. writer.move( sourceRange, targetPosition );
  612. } );
  613. expect( live.isEqual( clone ) ).to.be.true;
  614. expect( spy.called ).to.be.false;
  615. } );
  616. } );
  617. } );
  618. } );