feat(collaborative): function for getting changes from string (#2101)

* create function for getting changes from a string

* remove useless variables

* add more test cases

* refactor: return array of operations

* refactor: rename function, conditions, add docs

* refactor: use interfeces, move them to separate file, add comments

* refactor: rename test cases

* refactor: rename test cases

* refactor: change types schema

* refactor: rename function

* test: add the case for multiple changes
This commit is contained in:
Ilya Maroz 2022-09-06 11:32:38 +01:00 committed by GitHub
parent 0f77b7586d
commit a513e1079a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 396 additions and 0 deletions

View file

@ -0,0 +1,100 @@
import { InsertOperation, OperationType, RemoveOperation } from './types';
/**
* Function finds difference between two different strings
*
* @param str1 - original string
* @param str2 - new string that comes from the first
*
* @returns {object[]} array of operations
*/
export function createOperationsByStringsDiff(str1: string, str2: string): (InsertOperation<string> | RemoveOperation<string>)[] {
if (str1 === str2) {
return [];
}
const length1 = str1.length;
const length2 = str2.length;
/**
* Left border of changes
*/
let left = 0;
while (str1[left] === str2[left]) {
left++;
}
/**
* Right border of changes
*/
let right = 0;
while (str1[length1 - right - 1] === str2[length2 - right - 1]) {
right++;
}
/**
* Length of difference in the first string
*/
const diffLength1 = length1 - left - right;
/**
* Length of difference in the second string
*/
const diffLength2 = length2 - left - right;
/**
* There are differences in two strings
* User replaced values by new
*/
if (diffLength1 > 0 && diffLength2 > 0) {
const before = str1.slice(left, length1 - right);
const after = str2.slice(left, length2 - right);
return [
{
type: OperationType.Remove,
index: left,
data: before,
},
{
type: OperationType.Insert,
index: left,
data: after,
},
];
}
/**
* There is a difference only in the second string
* Used inserted new values
*/
if (diffLength2 > 0) {
const data = str2.slice(left, length2 - right + (left + right - length1));
return [
{
type: OperationType.Insert,
index: left,
data,
},
];
}
/**
* There is a difference only in the first string
* Used removed values from it
*/
if (diffLength1 > 0) {
const data = str1.slice(left, length1 - right + (left + right - length2));
return [
{
type: OperationType.Remove,
index: left,
data,
},
];
}
}

View file

@ -0,0 +1,18 @@
import { OperationType } from './OperationType';
export interface Operation<DATA_TYPE = string> {
/**
* Type of the operation
*/
type: OperationType;
/**
* Start index of changes
*/
index: number;
/**
* Changed data
*/
data: DATA_TYPE;
}

View file

@ -0,0 +1,14 @@
/**
* Possible operation types
*/
export enum OperationType {
/**
* User inserts symbols
*/
Insert,
/**
* User removes symbols
*/
Remove,
}

View file

@ -0,0 +1,3 @@
export * from './OperationType';
export * from './operations/InsertOperation';
export * from './operations/RemoveOperation';

View file

@ -0,0 +1,9 @@
import { Operation } from '../Operation';
import { OperationType } from '../OperationType';
/**
* Operation of inserting some data inside
*/
export interface InsertOperation<DATA_TYPE = string> extends Operation<DATA_TYPE> {
type: OperationType.Insert;
}

View file

@ -0,0 +1,9 @@
import { Operation } from '../Operation';
import { OperationType } from '../OperationType';
/**
* Operation of removing some data
*/
export interface RemoveOperation<DATA_TYPE = string> extends Operation<DATA_TYPE> {
type: OperationType.Remove;
}

View file

@ -0,0 +1,243 @@
import { createOperationsByStringsDiff } from '../../../../../src/components/collaborative-editing/diff/createOperationsByStringsDiff';
import { OperationType } from '../../../../../src/components/collaborative-editing/diff/types';
describe('createOperationsByStringsDiff function', () => {
describe('User inserts symbols', () => {
it('should create Insert operation when a user inserts symbols at the start of the string', () => {
const before = 'string';
const after = 'Changed string';
const expected = [
{
type: OperationType.Insert,
index: 0,
data: 'Changed ',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Insert operation when a user inserts symbols inside the string', () => {
const before = 'string';
const after = 'stri123ng';
const expected = [
{
type: OperationType.Insert,
index: 4,
data: '123',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Insert operation when a user inserts symbols at the end of the string', () => {
const before = 'string';
const after = 'string is changed';
const expected = [
{
type: OperationType.Insert,
index: 6,
data: ' is changed',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Insert operation when a user inserts symbols inside the empty string', () => {
const before = '';
const after = 'String';
const expected = [
{
type: OperationType.Insert,
index: 0,
data: 'String',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
});
describe('User removes symbols', () => {
it('should create Remove operation when a user removes symbols at the start of the string', () => {
const before = 'string';
const after = 'ing';
const expected = [
{
type: OperationType.Remove,
index: 0,
data: 'str',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Remove operation when a user removes symbols from the center of the string', () => {
const before = 'String is changed';
const after = 'String changed';
const expected = [
{
type: OperationType.Remove,
index: 7,
data: 'is ',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Remove operation when a user removes symbols at the end of the string', () => {
const before = 'String is changed';
const after = 'String is';
const expected = [
{
type: OperationType.Remove,
index: 9,
data: ' changed',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Remove operation when a user removes full string', () => {
const before = 'String';
const after = '';
const expected = [
{
type: OperationType.Remove,
index: 0,
data: 'String',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
});
describe('User replaces symbols', () => {
it('should create Remove and Insert operations when a user replaces symbols at the start of the string', () => {
const before = 'string';
const after = 'aaing';
const expected = [
{
type: OperationType.Remove,
index: 0,
data: 'str',
},
{
type: OperationType.Insert,
index: 0,
data: 'aa',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Remove and Insert operations when a user replaces symbols from the center of the string', () => {
const before = 'abcaafd';
const after = 'abceecfd';
const expected = [
{
type: OperationType.Remove,
index: 3,
data: 'aa',
},
{
type: OperationType.Insert,
index: 3,
data: 'eec',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Remove and Insert operations when a user replaces symbols at the end of the string', () => {
const before = 'abcdef';
const after = 'abcaa';
const expected = [
{
type: OperationType.Remove,
index: 3,
data: 'def',
},
{
type: OperationType.Insert,
index: 3,
data: 'aa',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Remove and Insert operations when a user replaces full string', () => {
const before = 'abcd';
const after = 'efg';
const expected = [
{
type: OperationType.Remove,
index: 0,
data: 'abcd',
},
{
type: OperationType.Insert,
index: 0,
data: 'efg',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
it('should create Remove and Insert operations when there are two changes at the start and the end of the string', () => {
const before = 'String is changed';
const after = 'Number is changed many times';
const expected = [
{
type: OperationType.Remove,
index: 0,
data: 'String is changed',
},
{
type: OperationType.Insert,
index: 0,
data: 'Number is changed many times',
},
];
const result = createOperationsByStringsDiff(before, after);
expect(result).deep.equal(expected);
});
});
});