瀏覽代碼

Additional tests for diff and fastDiff for multi-byte unicode sequences.

Kuba Niegowski 5 年之前
父節點
當前提交
9533ce3657
共有 2 個文件被更改,包括 215 次插入4 次删除
  1. 82 2
      packages/ckeditor5-utils/tests/diff.js
  2. 133 2
      packages/ckeditor5-utils/tests/fastdiff.js

+ 82 - 2
packages/ckeditor5-utils/tests/diff.js

@@ -68,7 +68,87 @@ describe( 'diff', () => {
 		testUtils.sinon.assert.called( fastDiffSpy );
 	} );
 
-	it( 'should diff insertion on the end handle multi-byte unicode properly', () => {
-		expect( diff( '123🙂', '123🙂x' ) ).to.deep.equals( [ 'equal', 'equal', 'equal', 'equal', 'equal', 'insert' ] );
+	describe( 'with multi-byte unicode', () => {
+		describe( 'simple emoji - single unicode code point', () => {
+			// 🙂 = '\ud83d\ude42' = 2 chars
+			const emojiLength = '🙂'.length;
+			const emojiDiffInsert = new Array( emojiLength ).fill( 'insert' );
+			const emojiDiffEqual = new Array( emojiLength ).fill( 'equal' );
+			const emojiDiffDelete = new Array( emojiLength ).fill( 'delete' );
+
+			it( 'should properly handle emoji insertion', () => {
+				expect( diff( 'abc', 'ab🙂c' ) ).to.deep.equals( [ 'equal', 'equal', ...emojiDiffInsert, 'equal' ] );
+			} );
+
+			it( 'should properly handle emoji insertion on the end', () => {
+				expect( diff( 'abc', 'abc🙂' ) ).to.deep.equals( [ 'equal', 'equal', 'equal', ...emojiDiffInsert ] );
+			} );
+
+			it( 'should properly handle appending to string containing emoji', () => {
+				expect( diff( 'abc🙂', 'abc🙂d' ) ).to.deep.equals( [ 'equal', 'equal', 'equal', ...emojiDiffEqual, 'insert' ] );
+			} );
+
+			it( 'should properly handle insertion to string containing emoji', () => {
+				expect( diff( 'ab🙂cd', 'ab🙂cde' ) ).to.deep.equals( [ 'equal', 'equal', ...emojiDiffEqual, 'equal', 'equal', 'insert' ] );
+			} );
+
+			it( 'should properly remove emoji', () => {
+				expect( diff( 'a🙂b', 'ab' ) ).to.deep.equals( [ 'equal', ...emojiDiffDelete, 'equal' ] );
+			} );
+
+			it( 'should properly replace emoji', () => {
+				expect( diff( 'a🙂b', 'axb' ) ).to.deep.equals( [ 'equal', ...emojiDiffDelete, 'insert', 'equal' ] );
+			} );
+		} );
+
+		describe( 'combined emoji - unicode ZWJ sequence', () => {
+			// 👩‍🦰 = '\ud83d\udc69\u200d\ud83e\uddB0' = 5 chars
+			const emojiLength = '👩‍🦰'.length;
+			const emojiDiffInsert = new Array( emojiLength ).fill( 'insert' );
+			const emojiDiffEqual = new Array( emojiLength ).fill( 'equal' );
+			const emojiDiffDelete = new Array( emojiLength ).fill( 'delete' );
+
+			it( 'should properly handle emoji insertion (with ZWJ)', () => {
+				expect( diff( 'abc', 'ab👩‍🦰c' ) ).to.deep.equals( [ 'equal', 'equal', ...emojiDiffInsert, 'equal' ] );
+			} );
+
+			it( 'should properly handle emoji insertion on the end (with ZWJ)', () => {
+				expect( diff( 'abc', 'abc👩‍🦰' ) ).to.deep.equals( [ 'equal', 'equal', 'equal', ...emojiDiffInsert ] );
+			} );
+
+			it( 'should properly handle appending to string containing emoji (with ZWJ)', () => {
+				expect( diff( 'ab👩‍🦰', 'ab👩‍🦰c' ) ).to.deep.equals( [ 'equal', 'equal', ...emojiDiffEqual, 'insert' ] );
+			} );
+
+			it( 'should properly handle insertion to string containing emoji (with ZWJ)', () => {
+				expect( diff( 'a👩‍🦰b', 'a👩‍🦰bc' ) ).to.deep.equals( [ 'equal', ...emojiDiffEqual, 'equal', 'insert' ] );
+			} );
+
+			it( 'should properly remove emoji (with ZWJ)', () => {
+				expect( diff( 'a👩‍🦰b', 'ab' ) ).to.deep.equals( [ 'equal', ...emojiDiffDelete, 'equal' ] );
+			} );
+
+			it( 'should properly replace emoji (with ZWJ)', () => {
+				expect( diff( 'a👩‍🦰b', 'axb' ) ).to.deep.equals( [ 'equal', ...emojiDiffDelete, 'insert', 'equal' ] );
+			} );
+
+			it( 'should properly replace ZWJ sequence with simple emoji', () => {
+				const simpleEmojiDiffInsert = new Array( '🙂'.length ).fill( 'insert' );
+
+				// Note that first char of both emoji is the same.
+				expect( diff( 'a👩‍🦰b', 'a🙂b' ) ).to.deep.equals( [
+					'equal', 'equal', ...emojiDiffDelete.slice( 1 ), ...simpleEmojiDiffInsert.slice( 1 ), 'equal'
+				] );
+			} );
+
+			it( 'should properly replace simple emoji with ZWJ sequence', () => {
+				const simpleEmojiDiffDelete = new Array( '🙂'.length ).fill( 'delete' );
+
+				// Note that first char of both emoji is the same.
+				expect( diff( 'a🙂b', 'a👩‍🦰b' ) ).to.deep.equals( [
+					'equal', 'equal', ...emojiDiffInsert.slice( 1 ), ...simpleEmojiDiffDelete.slice( 1 ), 'equal'
+				] );
+			} );
+		} );
 	} );
 } );

+ 133 - 2
packages/ckeditor5-utils/tests/fastdiff.js

@@ -121,8 +121,46 @@ describe( 'fastDiff', () => {
 				], true, ( a, b ) => a.text === b.text );
 			} );
 
-			it( 'should diff insertion on the end handle multi-byte unicode properly', () => {
-				expectDiff( '123🙂', '123🙂x', [ { index: 5, type: 'insert', values: [ 'x' ] } ] );
+			describe( 'with multi-byte unicode', () => {
+				describe( 'simple emoji - single unicode code point', () => {
+					// 🙂 = '\ud83d\ude42' = 2 chars
+
+					it( 'should properly handle emoji insertion', () => {
+						expectDiff( 'abc', 'ab🙂c', [ { index: 2, type: 'insert', values: '🙂'.split( '' ) } ] );
+					} );
+
+					it( 'should properly handle emoji insertion on the end', () => {
+						expectDiff( 'abc', 'abc🙂', [ { index: 3, type: 'insert', values: '🙂'.split( '' ) } ] );
+					} );
+
+					it( 'should properly handle appending to string containing emoji', () => {
+						expectDiff( 'abc🙂', 'abc🙂d', [ { index: 5, type: 'insert', values: [ 'd' ] } ] );
+					} );
+
+					it( 'should properly handle insertion to string containing emoji', () => {
+						expectDiff( 'ab🙂cd', 'ab🙂cde', [ { index: 6, type: 'insert', values: [ 'e' ] } ] );
+					} );
+				} );
+
+				describe( 'combined emoji - unicode ZWJ sequence', () => {
+					// 👩‍🦰 = '\ud83d\udc69\u200d\ud83e\uddB0' = 5 chars
+
+					it( 'should properly handle emoji with ZWJ insertion', () => {
+						expectDiff( 'abc', 'ab👩‍🦰c', [ { index: 2, type: 'insert', values: '👩‍🦰'.split( '' ) } ] );
+					} );
+
+					it( 'should properly handle emoji (with ZWJ) insertion on the end', () => {
+						expectDiff( 'abc', 'abc👩‍🦰', [ { index: 3, type: 'insert', values: '👩‍🦰'.split( '' ) } ] );
+					} );
+
+					it( 'should properly handle appending to string containing emoji (with ZWJ)', () => {
+						expectDiff( 'ab👩‍🦰', 'ab👩‍🦰c', [ { index: 7, type: 'insert', values: [ 'c' ] } ] );
+					} );
+
+					it( 'should properly handle insertion to string containing emoji (with ZWJ)', () => {
+						expectDiff( 'a👩‍🦰b', 'a👩‍🦰bc', [ { index: 7, type: 'insert', values: [ 'c' ] } ] );
+					} );
+				} );
 			} );
 		} );
 
@@ -200,6 +238,42 @@ describe( 'fastDiff', () => {
 					{ index: 0, type: 'delete', howMany: 1 }
 				], true, ( a, b ) => a.text === b.text );
 			} );
+
+			describe( 'with multi-byte unicode', () => {
+				describe( 'simple emoji - single unicode code point', () => {
+					// 🙂 = '\ud83d\ude42' = 2 chars
+					const emojiLength = '🙂'.split( '' ).length;
+
+					it( 'should properly handle emoji delete', () => {
+						expectDiff( 'ab🙂c', 'abc', [ { index: 2, type: 'delete', howMany: emojiLength } ] );
+					} );
+
+					it( 'should properly handle emoji delete at end', () => {
+						expectDiff( 'ab🙂', 'ab', [ { index: 2, type: 'delete', howMany: emojiLength } ] );
+					} );
+
+					it( 'should properly handle emoji delete at beginning', () => {
+						expectDiff( '🙂ab', 'ab', [ { index: 0, type: 'delete', howMany: emojiLength } ] );
+					} );
+				} );
+
+				describe( 'combined emoji - unicode ZWJ sequence', () => {
+					// 👩‍🦰 = '\ud83d\udc69\u200d\ud83e\uddB0' = 5 chars
+					const emojiLength = '👩‍🦰'.split( '' ).length;
+
+					it( 'should properly handle emoji delete (with ZWJ)', () => {
+						expectDiff( 'ab👩‍🦰c', 'abc', [ { index: 2, type: 'delete', howMany: emojiLength } ] );
+					} );
+
+					it( 'should properly handle emoji delete at end (with ZWJ)', () => {
+						expectDiff( 'ab👩‍🦰', 'ab', [ { index: 2, type: 'delete', howMany: emojiLength } ] );
+					} );
+
+					it( 'should properly handle emoji delete at beginning (with ZWJ)', () => {
+						expectDiff( '👩‍🦰ab', 'ab', [ { index: 0, type: 'delete', howMany: emojiLength } ] );
+					} );
+				} );
+			} );
 		} );
 
 		describe( 'replacement', () => {
@@ -297,6 +371,63 @@ describe( 'fastDiff', () => {
 					{ index: 2, type: 'delete', howMany: 1 }
 				], true, ( a, b ) => a.text === b.text );
 			} );
+
+			describe( 'with multi-byte unicode', () => {
+				// 🙂 = '\ud83d\ude42' = 2 chars
+				const smileEmoji = '🙂'.split( '' );
+
+				// 👩 = '\ud83d\udc69' = 2 chars
+				const womanEmoji = '👩'.split( '' );
+
+				// 👩‍🦰 = '\ud83d\udc69\u200d\ud83e\uddB0' = 5 chars
+				const womanRedHairEmoji = '👩‍🦰'.split( '' );
+
+				// Do not check compatibility with 'diffToChanges' as it generates:
+				// [ { index: 1, type: 'delete', howMany: 2 }, { index: 1, type: 'insert', values: [ 'x' ] } ]
+				it( 'should properly replace emoji with text', () => {
+					expectDiff( 'a🙂b', 'axb', [
+						{ index: 1, type: 'insert', values: [ 'x' ] },
+						{ index: 2, type: 'delete', howMany: smileEmoji.length }
+					], false );
+				} );
+
+				it( 'should properly replace text with emoji', () => {
+					expectDiff( 'abc', 'a👩c', [
+						{ index: 1, type: 'insert', values: womanEmoji },
+						{ index: 3, type: 'delete', howMany: 1 }
+					] );
+				} );
+
+				it( 'should properly replace emoji with emoji', () => {
+					// Note that first char of both emoji is the same.
+					expectDiff( 'a👩b', 'a🙂b', [
+						{ index: 2, type: 'insert', values: smileEmoji.slice( 1 ) },
+						{ index: 3, type: 'delete', howMany: 1 }
+					] );
+				} );
+
+				it( 'should properly replace simple emoji with ZWJ sequence of it', () => {
+					// Note that first 2 chars of both emoji are the same.
+					expectDiff( 'a👩b', 'a👩‍🦰b', [
+						{ index: 3, type: 'insert', values: womanRedHairEmoji.slice( 2 ) }
+					] );
+				} );
+
+				it( 'should properly replace ZWJ sequence with simple emoji (part of sequence)', () => {
+					// Note that first 2 chars of both emoji are the same.
+					expectDiff( 'a👩‍🦰b', 'a👩b', [
+						{ index: 3, type: 'delete', howMany: 3 }
+					] );
+				} );
+
+				it( 'should properly replace simple emoji with other ZWJ sequence', () => {
+					// Note that first char of both emoji is the same.
+					expectDiff( 'a🙂b', 'a👩‍🦰b', [
+						{ index: 2, type: 'insert', values: womanRedHairEmoji.slice( 1 ) },
+						{ index: 6, type: 'delete', howMany: 1 }
+					] );
+				} );
+			} );
 		} );
 	} );