8
0

diff.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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 diff from '../src/diff';
  6. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  7. import getLongText from './_utils/longtext';
  8. describe( 'diff', () => {
  9. let fastDiffSpy;
  10. testUtils.createSinonSandbox();
  11. beforeEach( () => {
  12. fastDiffSpy = testUtils.sinon.spy( diff, 'fastDiff' );
  13. } );
  14. it( 'should diff strings', () => {
  15. expect( diff( 'aba', 'acca' ) ).to.deep.equal( [ 'equal', 'insert', 'insert', 'delete', 'equal' ] );
  16. testUtils.sinon.assert.notCalled( fastDiffSpy );
  17. } );
  18. it( 'should diff arrays', () => {
  19. expect( diff( Array.from( 'aba' ), Array.from( 'acca' ) ) ).to.deep.equal( [ 'equal', 'insert', 'insert', 'delete', 'equal' ] );
  20. testUtils.sinon.assert.notCalled( fastDiffSpy );
  21. } );
  22. it( 'should reverse result if the second string is shorter', () => {
  23. expect( diff( 'acca', 'aba' ) ).to.deep.equal( [ 'equal', 'delete', 'delete', 'insert', 'equal' ] );
  24. testUtils.sinon.assert.notCalled( fastDiffSpy );
  25. } );
  26. it( 'should diff if strings are same', () => {
  27. expect( diff( 'abc', 'abc' ) ).to.deep.equal( [ 'equal', 'equal', 'equal' ] );
  28. testUtils.sinon.assert.notCalled( fastDiffSpy );
  29. } );
  30. it( 'should diff if one string is empty', () => {
  31. expect( diff( '', 'abc' ) ).to.deep.equal( [ 'insert', 'insert', 'insert' ] );
  32. testUtils.sinon.assert.notCalled( fastDiffSpy );
  33. } );
  34. it( 'should use custom comparator', () => {
  35. expect( diff( 'aBc', 'abc' ) ).to.deep.equal( [ 'equal', 'insert', 'delete', 'equal' ] );
  36. expect( diff( 'aBc', 'abc', ( a, b ) => a.toLowerCase() == b.toLowerCase() ) ).to.deep.equal( [ 'equal', 'equal', 'equal' ] );
  37. testUtils.sinon.assert.notCalled( fastDiffSpy );
  38. } );
  39. it( 'should use fastDiff() internally for strings with 400+ length and length sum of 1400+', () => {
  40. diff( getLongText( 400 ), getLongText( 1000 ) );
  41. testUtils.sinon.assert.called( fastDiffSpy );
  42. } );
  43. it( 'should use fastDiff() internally for arrays with 400+ length and length sum of 1400+', () => {
  44. diff( getLongText( 500 ).split( '' ), getLongText( 950 ).split( '' ) );
  45. testUtils.sinon.assert.called( fastDiffSpy );
  46. } );
  47. it( 'should use fastDiff() internally for strings with length sum of 2000+', () => {
  48. diff( getLongText( 100 ), getLongText( 2000 ) );
  49. testUtils.sinon.assert.called( fastDiffSpy );
  50. } );
  51. it( 'should use fastDiff() internally for strings with length sum of exaclty 2000', () => {
  52. diff( getLongText( 10 ), getLongText( 1990 ) );
  53. testUtils.sinon.assert.called( fastDiffSpy );
  54. } );
  55. describe( 'with multi-byte unicode', () => {
  56. describe( 'simple emoji - single unicode code point', () => {
  57. // 🙂 = '\ud83d\ude42' = 2 chars
  58. const emojiLength = '🙂'.length;
  59. const emojiDiffInsert = new Array( emojiLength ).fill( 'insert' );
  60. const emojiDiffEqual = new Array( emojiLength ).fill( 'equal' );
  61. const emojiDiffDelete = new Array( emojiLength ).fill( 'delete' );
  62. it( 'should properly handle emoji insertion', () => {
  63. expect( diff( 'abc', 'ab🙂c' ) ).to.deep.equal( [ 'equal', 'equal', ...emojiDiffInsert, 'equal' ] );
  64. } );
  65. it( 'should properly handle emoji insertion on the end', () => {
  66. expect( diff( 'abc', 'abc🙂' ) ).to.deep.equal( [ 'equal', 'equal', 'equal', ...emojiDiffInsert ] );
  67. } );
  68. it( 'should properly handle appending to string containing emoji', () => {
  69. expect( diff( 'abc🙂', 'abc🙂d' ) ).to.deep.equal( [ 'equal', 'equal', 'equal', ...emojiDiffEqual, 'insert' ] );
  70. } );
  71. it( 'should properly handle insertion to string containing emoji', () => {
  72. expect( diff( 'ab🙂cd', 'ab🙂cde' ) ).to.deep.equal( [ 'equal', 'equal', ...emojiDiffEqual, 'equal', 'equal', 'insert' ] );
  73. } );
  74. it( 'should properly remove emoji', () => {
  75. expect( diff( 'a🙂b', 'ab' ) ).to.deep.equal( [ 'equal', ...emojiDiffDelete, 'equal' ] );
  76. } );
  77. it( 'should properly replace emoji', () => {
  78. expect( diff( 'a🙂b', 'axb' ) ).to.deep.equal( [ 'equal', ...emojiDiffDelete, 'insert', 'equal' ] );
  79. } );
  80. it( 'should properly replace one emoji with another', () => {
  81. // 😄 = '\ud83d\ude04' = 2 chars
  82. // Note both emoji have same first code unit
  83. expect( diff( 'a🙂b', 'a😄b' ) ).to.deep.equal(
  84. [ 'equal', 'equal', ...emojiDiffInsert.slice( 1 ), ...emojiDiffDelete.slice( 1 ), 'equal' ]
  85. );
  86. } );
  87. } );
  88. describe( 'combined emoji - unicode ZWJ sequence', () => {
  89. // 👩‍🦰 = '\ud83d\udc69\u200d\ud83e\uddB0' = 5 chars
  90. const emojiLength = '👩‍🦰'.length;
  91. const emojiDiffInsert = new Array( emojiLength ).fill( 'insert' );
  92. const emojiDiffEqual = new Array( emojiLength ).fill( 'equal' );
  93. const emojiDiffDelete = new Array( emojiLength ).fill( 'delete' );
  94. it( 'should properly handle emoji insertion (with ZWJ)', () => {
  95. expect( diff( 'abc', 'ab👩‍🦰c' ) ).to.deep.equal( [ 'equal', 'equal', ...emojiDiffInsert, 'equal' ] );
  96. } );
  97. it( 'should properly handle emoji insertion on the end (with ZWJ)', () => {
  98. expect( diff( 'abc', 'abc👩‍🦰' ) ).to.deep.equal( [ 'equal', 'equal', 'equal', ...emojiDiffInsert ] );
  99. } );
  100. it( 'should properly handle appending to string containing emoji (with ZWJ)', () => {
  101. expect( diff( 'ab👩‍🦰', 'ab👩‍🦰c' ) ).to.deep.equal( [ 'equal', 'equal', ...emojiDiffEqual, 'insert' ] );
  102. } );
  103. it( 'should properly handle insertion to string containing emoji (with ZWJ)', () => {
  104. expect( diff( 'a👩‍🦰b', 'a👩‍🦰bc' ) ).to.deep.equal( [ 'equal', ...emojiDiffEqual, 'equal', 'insert' ] );
  105. } );
  106. it( 'should properly remove emoji (with ZWJ)', () => {
  107. expect( diff( 'a👩‍🦰b', 'ab' ) ).to.deep.equal( [ 'equal', ...emojiDiffDelete, 'equal' ] );
  108. } );
  109. it( 'should properly replace emoji (with ZWJ)', () => {
  110. expect( diff( 'a👩‍🦰b', 'axb' ) ).to.deep.equal( [ 'equal', ...emojiDiffDelete, 'insert', 'equal' ] );
  111. } );
  112. it( 'should properly replace ZWJ sequence with simple emoji', () => {
  113. const simpleEmojiDiffInsert = new Array( '🙂'.length ).fill( 'insert' );
  114. // Note that first char of both emoji is the same.
  115. expect( diff( 'a👩‍🦰b', 'a🙂b' ) ).to.deep.equal( [
  116. 'equal', 'equal', ...emojiDiffDelete.slice( 1 ), ...simpleEmojiDiffInsert.slice( 1 ), 'equal'
  117. ] );
  118. } );
  119. it( 'should properly replace simple emoji with ZWJ sequence', () => {
  120. const simpleEmojiDiffDelete = new Array( '🙂'.length ).fill( 'delete' );
  121. // Note that first char of both emoji is the same.
  122. expect( diff( 'a🙂b', 'a👩‍🦰b' ) ).to.deep.equal( [
  123. 'equal', 'equal', ...emojiDiffInsert.slice( 1 ), ...simpleEmojiDiffDelete.slice( 1 ), 'equal'
  124. ] );
  125. } );
  126. } );
  127. } );
  128. } );