history.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import History from '/ckeditor5/engine/model/history.js';
  7. import Delta from '/ckeditor5/engine/model/delta/delta.js';
  8. import NoOperation from '/ckeditor5/engine/model/operation/nooperation.js';
  9. import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
  10. describe( 'History', () => {
  11. let history;
  12. beforeEach( () => {
  13. history = new History();
  14. } );
  15. describe( 'constructor', () => {
  16. it( 'should create an empty History instance', () => {
  17. expect( history._deltas.length ).to.equal( 0 );
  18. expect( history._historyPoints.size ).to.equal( 0 );
  19. } );
  20. } );
  21. describe( 'addOperation', () => {
  22. it( 'should save delta containing passed operation in the history', () => {
  23. let delta = new Delta();
  24. let operation = new NoOperation( 0 );
  25. delta.addOperation( operation );
  26. history.addOperation( operation );
  27. expect( history._deltas.length ).to.equal( 1 );
  28. expect( history._deltas[ 0 ] ).to.equal( delta );
  29. } );
  30. it( 'should save each delta only once', () => {
  31. let delta = new Delta();
  32. delta.addOperation( new NoOperation( 0 ) );
  33. delta.addOperation( new NoOperation( 1 ) );
  34. delta.addOperation( new NoOperation( 2 ) );
  35. for ( let operation of delta.operations ) {
  36. history.addOperation( operation );
  37. }
  38. expect( history._deltas.length ).to.equal( 1 );
  39. expect( history._deltas[ 0 ] ).to.equal( delta );
  40. } );
  41. it( 'should save multiple deltas and keep their order', () => {
  42. let deltaA = new Delta();
  43. let deltaB = new Delta();
  44. let deltaC = new Delta();
  45. let deltas = [ deltaA, deltaB, deltaC ];
  46. let i = 0;
  47. for ( let delta of deltas ) {
  48. delta.addOperation( new NoOperation( i++ ) );
  49. delta.addOperation( new NoOperation( i++ ) );
  50. }
  51. for ( let delta of deltas ) {
  52. for ( let operation of delta.operations ) {
  53. history.addOperation( operation );
  54. }
  55. }
  56. expect( history._deltas.length ).to.equal( 3 );
  57. expect( history._deltas[ 0 ] ).to.equal( deltaA );
  58. expect( history._deltas[ 1 ] ).to.equal( deltaB );
  59. expect( history._deltas[ 2 ] ).to.equal( deltaC );
  60. } );
  61. } );
  62. describe( 'getTransformedDelta', () => {
  63. it( 'should transform given delta by deltas from history which were applied since the baseVersion of given delta', () => {
  64. sinon.spy( History, '_transform' );
  65. let deltaA = new Delta();
  66. deltaA.addOperation( new NoOperation( 0 ) );
  67. let deltaB = new Delta();
  68. deltaB.addOperation( new NoOperation( 1 ) );
  69. let deltaC = new Delta();
  70. deltaC.addOperation( new NoOperation( 2 ) );
  71. let deltaD = new Delta();
  72. deltaD.addOperation( new NoOperation( 3 ) );
  73. let deltaX = new Delta();
  74. deltaX.addOperation( new NoOperation( 1 ) );
  75. history.addOperation( deltaA.operations[ 0 ] );
  76. history.addOperation( deltaB.operations[ 0 ] );
  77. history.addOperation( deltaC.operations[ 0 ] );
  78. history.addOperation( deltaD.operations[ 0 ] );
  79. // `deltaX` bases on the same history point as `deltaB` -- so it already acknowledges `deltaA` existence.
  80. // It should be transformed by `deltaB` and all following deltas (`deltaC` and `deltaD`).
  81. history.getTransformedDelta( deltaX );
  82. // `deltaX` was not transformed by `deltaA`.
  83. expect( History._transform.calledWithExactly( deltaX, deltaA ) ).to.be.false;
  84. expect( History._transform.calledWithExactly( deltaX, deltaB ) ).to.be.true;
  85. // We can't do exact call matching because after first transformation, what we are further transforming
  86. // is no longer `deltaX` but a result of transforming `deltaX` and `deltaB`.
  87. expect( History._transform.calledWithExactly( sinon.match.instanceOf( Delta ), deltaC ) ).to.be.true;
  88. expect( History._transform.calledWithExactly( sinon.match.instanceOf( Delta ), deltaD ) ).to.be.true;
  89. } );
  90. it( 'should correctly set base versions if multiple deltas are result of transformation', () => {
  91. // Let's stub History._transform so it will always return two deltas with two operations each.
  92. History._transform = function() {
  93. let resultA = new Delta();
  94. resultA.addOperation( new NoOperation( 1 ) );
  95. resultA.addOperation( new NoOperation( 1 ) );
  96. let resultB = new Delta();
  97. resultB.addOperation( new NoOperation( 1 ) );
  98. resultB.addOperation( new NoOperation( 1 ) );
  99. return [ resultA, resultB ];
  100. };
  101. let deltaA = new Delta();
  102. deltaA.addOperation( new NoOperation( 0 ) );
  103. let deltaX = new Delta();
  104. deltaX.addOperation( new NoOperation( 0 ) );
  105. history.addOperation( deltaA.operations[ 0 ] );
  106. let result = history.getTransformedDelta( deltaX );
  107. expect( result[ 0 ].operations[ 0 ].baseVersion ).to.equal( 1 );
  108. expect( result[ 0 ].operations[ 1 ].baseVersion ).to.equal( 2 );
  109. expect( result[ 1 ].operations[ 0 ].baseVersion ).to.equal( 3 );
  110. expect( result[ 1 ].operations[ 1 ].baseVersion ).to.equal( 4 );
  111. } );
  112. it( 'should not transform given delta if it bases on current version of history', () => {
  113. let deltaA = new Delta();
  114. deltaA.addOperation( new NoOperation( 0 ) );
  115. let deltaB = new Delta();
  116. let opB = new NoOperation( 1 );
  117. deltaB.addOperation( opB );
  118. history.addOperation( deltaA.operations[ 0 ] );
  119. let result = history.getTransformedDelta( deltaB );
  120. expect( result.length ).to.equal( 1 );
  121. expect( result[ 0 ] ).to.equal( deltaB );
  122. expect( result[ 0 ].operations[ 0 ] ).to.equal( opB );
  123. } );
  124. it( 'should throw if given delta bases on an incorrect version of history', () => {
  125. let deltaA = new Delta();
  126. deltaA.addOperation( new NoOperation( 0 ) );
  127. deltaA.addOperation( new NoOperation( 1 ) );
  128. history.addOperation( deltaA.operations[ 0 ] );
  129. history.addOperation( deltaA.operations[ 1 ] );
  130. let deltaB = new Delta();
  131. // Wrong base version - should be either 0 or 2, operation can't be based on an operation that is
  132. // in the middle of other delta, because deltas are atomic, not dividable structures.
  133. deltaB.addOperation( new NoOperation( 1 ) );
  134. expect( () => {
  135. history.getTransformedDelta( deltaB );
  136. } ).to.throw( CKEditorError, /history-wrong-version/ );
  137. } );
  138. } );
  139. } );