Skip to content
Permalink
ed9506bbaf
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
Robert Brignull replace jest with ava
Latest commit 0347b72 May 4, 2020 History
0 contributors

Users who have contributed to this file

334 lines (301 sloc) 10.2 KB
var _ = require('lodash');
var seedrandom = require('seedrandom');
var diff = require('./diff.js');
var ITERATIONS = 10000;
var ALPHABET = 'GATTACA';
var LENGTH = 100;
var EMOJI_MAX_LENGTH = 50;
var seed = Math.floor(Math.random() * 10000);
var random = seedrandom(seed);
console.log('Running regression tests...');
[
['GAATAAAAAAAGATTAACAT', 'AAAAACTTGTAATTAACAAC'],
['🔘🤘🔗🔗', '🔗🤗🤗__🤗🤘🤘🤗🔗🤘🔗'],
['🔗🤗🤗__🤗🤘🤘🤗🔗🤘🔗', '🤗🤘🔘'],
['🤘🤘🔘🔘_🔘🔗🤘🤗🤗__🔗🤘', '🤘🔘🤘🔗🤘🤘🔗🤗🤘🔘🔘'],
['🤗🤘🤗🔘🤘🔘🤗_🤗🔗🤘🤗_🤘🔗🤗🤘🔗🤘🤘🤘🔗🤗🔗🔗🔗🤗_🤘🔗🤗🤗🔘🤗🤗🤘🤗',
'_🤗🤘_🤘🤘🔘🤗🔘🤘_🔘🤗🔗🔘🔗🤘🔗🤘🤗🔗🔗🔗🤘🔘_🤗🤘🤘🤘__🤘_🔘🤘🤘_🔗🤘🔘'],
['🔗🤘🤗🔘🔘🤗', '🤘🤘🤘🤗🔘🔗🔗'],
['🔘_🔗🔗🔗🤗🔗', '🤘🤗🔗🤗_🤘🔘_'],
].forEach(function (data) {
var result = diff(data[0], data[1]);
applyDiff(result, data[0], data[1]);
});
console.log('Running computing ' + ITERATIONS + ' diffs with seed ' + seed + '...');
console.log('Generating strings...');
var strings = [];
for (var i = 0; i <= ITERATIONS; ++i) {
var chars = [];
for (var l = 0; l < LENGTH; ++l) {
var letter = ALPHABET.substr(Math.floor(random() * ALPHABET.length), 1);
chars.push(letter);
}
strings.push(chars.join(''));
}
console.log('Running fuzz tests *without* cursor information...');
for (var i = 0; i < ITERATIONS; ++i) {
var result = diff(strings[i], strings[i + 1]);
applyDiff(result, strings[i], strings[i + 1]);
}
console.log('Running fuzz tests *with* cursor information');
for (var i = 0; i < ITERATIONS; ++i) {
var cursor_pos = Math.floor(random() * strings[i].length + 1);
var diffs = diff(strings[i], strings[i + 1], cursor_pos);
applyDiff(diffs, strings[i], strings[i + 1]);
}
function parseDiff(str) {
if (!str) {
return [];
}
return str.split(/(?=[+\-=])/).map(function (piece) {
var symbol = piece.charAt(0);
var text = piece.slice(1);
return [
symbol === '+' ? diff.INSERT : symbol === '-' ? diff.DELETE : diff.EQUAL,
text
]
});
}
console.log('Running cursor tests');
[
['', 0, '', null, ''],
['', 0, 'a', null, '+a'],
['a', 0, 'aa', null, '+a=a'],
['a', 1, 'aa', null, '=a+a'],
['aa', 0, 'aaa', null, '+a=aa'],
['aa', 1, 'aaa', null, '=a+a=a'],
['aa', 2, 'aaa', null, '=aa+a'],
['aaa', 0, 'aaaa', null, '+a=aaa'],
['aaa', 1, 'aaaa', null, '=a+a=aa'],
['aaa', 2, 'aaaa', null, '=aa+a=a'],
['aaa', 3, 'aaaa', null, '=aaa+a'],
['a', 0, '', null, '-a'],
['a', 1, '', null, '-a'],
['aa', 0, 'a', null, '-a=a'],
['aa', 1, 'a', null, '-a=a'],
['aa', 2, 'a', null, '=a-a'],
['aaa', 0, 'aa', null, '-a=aa'],
['aaa', 1, 'aa', null, '-a=aa'],
['aaa', 2, 'aa', null, '=a-a=a'],
['aaa', 3, 'aa', null, '=aa-a'],
['', 0, '', 0, ''],
['', 0, 'a', 1, '+a'],
['a', 0, 'aa', 1, '+a=a'],
['a', 1, 'aa', 2, '=a+a'],
['aa', 0, 'aaa', 1, '+a=aa'],
['aa', 1, 'aaa', 2, '=a+a=a'],
['aa', 2, 'aaa', 3, '=aa+a'],
['aaa', 0, 'aaaa', 1, '+a=aaa'],
['aaa', 1, 'aaaa', 2, '=a+a=aa'],
['aaa', 2, 'aaaa', 3, '=aa+a=a'],
['aaa', 3, 'aaaa', 4, '=aaa+a'],
['a', 1, '', 0, '-a'],
['aa', 1, 'a', 0, '-a=a'],
['aa', 2, 'a', 1, '=a-a'],
['aaa', 1, 'aa', 0, '-a=aa'],
['aaa', 2, 'aa', 1, '=a-a=a'],
['aaa', 3, 'aa', 2, '=aa-a'],
['a', 1, '', 0, '-a'],
['aa', 1, 'a', 0, '-a=a'],
['aa', 2, 'a', 1, '=a-a'],
['aaa', 1, 'aa', 0, '-a=aa'],
['aaa', 2, 'aa', 1, '=a-a=a'],
['aaa', 3, 'aa', 2, '=aa-a'],
// forward-delete
['a', 0, '', 0, '-a'],
['aa', 0, 'a', 0, '-a=a'],
['aa', 1, 'a', 1, '=a-a'],
['aaa', 0, 'aa', 0, '-a=aa'],
['aaa', 1, 'aa', 1, '=a-a=a'],
['aaa', 2, 'aa', 2, '=aa-a'],
['bob', 0, 'bobob', null, '+bo=bob'],
['bob', 1, 'bobob', null, '=b+ob=ob'],
['bob', 2, 'bobob', null, '=bo+bo=b'],
['bob', 3, 'bobob', null, '=bob+ob'],
['bob', 0, 'bobob', 2, '+bo=bob'],
['bob', 1, 'bobob', 3, '=b+ob=ob'],
['bob', 2, 'bobob', 4, '=bo+bo=b'],
['bob', 3, 'bobob', 5, '=bob+ob'],
['bobob', 2, 'bob', null, '-bo=bob'],
['bobob', 3, 'bob', null, '=b-ob=ob'],
['bobob', 4, 'bob', null, '=bo-bo=b'],
['bobob', 5, 'bob', null, '=bob-ob'],
['bobob', 2, 'bob', 0, '-bo=bob'],
['bobob', 3, 'bob', 1, '=b-ob=ob'],
['bobob', 4, 'bob', 2, '=bo-bo=b'],
['bobob', 5, 'bob', 3, '=bob-ob'],
['bob', 1, 'b', null, '=b-ob'],
['hello', [0, 5], 'h', 1, '-hello+h'],
['yay', [0, 3], 'y', 1, '-yay+y'],
['bobob', [1, 4], 'bob', 2, '=b-obo+o=b'],
].forEach(function (data) {
var oldText = data[0];
var newText = data[2];
var oldRange = typeof data[1] === 'number' ?
{ index: data[1], length: 0 } :
{ index: data[1][0], length: data[1][1] - data[1][0] };
var newRange = typeof data[3] === 'number' ?
{ index: data[3], length: 0 } :
data[3] === null ? null : { index: data[3][0], length: data[3][1] - data[3][0] };
var expected = parseDiff(data[4]);
if (newRange === null && typeof data[1] !== 'number') {
throw new Error('invalid test case');
}
var cursorInfo = newRange === null ? data[1] : {
oldRange: oldRange,
newRange: newRange,
};
doCursorTest(oldText, newText, cursorInfo, expected);
doCursorTest('x' + oldText, 'x' + newText, shiftCursorInfo(cursorInfo, 1), diffPrepend(expected, 'x'));
doCursorTest(oldText + 'x', newText + 'x', cursorInfo, diffAppend(expected, 'x'));
});
function diffPrepend(tuples, text) {
if (tuples.length > 0 && tuples[0][0] === diff.EQUAL) {
return [[diff.EQUAL, text + tuples[0][1]]].concat(tuples.slice(1));
} else {
return [[diff.EQUAL, text]].concat(tuples);
}
}
function diffAppend(tuples, text) {
var lastTuple = tuples[tuples.length - 1];
if (lastTuple && lastTuple[0] === diff.EQUAL) {
return tuples.slice(0, -1).concat([[diff.EQUAL, lastTuple[1] + text]]);
} else {
return tuples.concat([[diff.EQUAL, text]]);
}
}
function shiftCursorInfo(cursorInfo, amount) {
if (typeof cursorInfo === 'number') {
return cursorInfo + amount;
} else {
return {
oldRange: {
index: cursorInfo.oldRange.index + amount,
length: cursorInfo.oldRange.length,
},
newRange: {
index: cursorInfo.newRange.index + amount,
length: cursorInfo.newRange.length,
},
}
}
}
function doCursorTest(oldText, newText, cursorInfo, expected) {
var result = diff(oldText, newText, cursorInfo);
if (!_.isEqual(result, expected)) {
console.log([oldText, newText, cursorInfo]);
console.log(result, '!==', expected);
throw new Error('cursor test failed');
}
}
console.log('Running emoji tests');
[
['🐶', '🐯', '-🐶+🐯'],
['👨🏽', '👩🏽', '-👨+👩=🏽'],
['👩🏼', '👩🏽', '=👩-🏼+🏽'],
['🍏🍎', '🍎', '-🍏=🍎'],
['🍎', '🍏🍎', '+🍏=🍎'],
].forEach(function (data) {
var oldText = data[0];
var newText = data[1];
var expected = parseDiff(data[2]);
doEmojiTest(oldText, newText, expected);
doEmojiTest('x' + oldText, 'x' + newText, diffPrepend(expected, 'x'));
doEmojiTest(oldText + 'x', newText + 'x', diffAppend(expected, 'x'));
});
function doEmojiTest(oldText, newText, expected) {
var result = diff(oldText, newText);
if (!_.isEqual(result, expected)) {
console.log(oldText, newText, expected);
console.log(result, '!==', expected);
throw new Error('Emoji simple test case failed');
}
}
// emojis chosen to share high and low surrogates!
var EMOJI_ALPHABET = ['_', '🤗', '🔗', '🤘', '🔘'];
console.log('Generating emoji strings...');
var emoji_strings = [];
for (var i = 0; i <= ITERATIONS; ++i) {
var letters = [];
var len = Math.floor(random() * EMOJI_MAX_LENGTH);
for (var l = 0; l < len; ++l) {
var letter = EMOJI_ALPHABET[Math.floor(random() * EMOJI_ALPHABET.length)];
letters.push(letter);
}
emoji_strings.push(letters.join(''));
}
console.log('Running emoji fuzz tests...');
for (var i = 0; i < ITERATIONS; ++i) {
var oldText = emoji_strings[i];
var newText = emoji_strings[i + 1];
var result = diff(oldText, newText);
applyDiff(result, oldText, newText);
}
// Applies a diff to text, throwing an error if diff is invalid or incorrect
function applyDiff(diffs, text, expectedResult) {
var pos = 0;
function throwError(message) {
console.log(diffs, text, expectedResult);
throw new Error(message);
}
function expect(expected) {
var found = text.substr(pos, expected.length);
if (found !== expected) {
throwError('Expected "' + expected + '", found "' + found + '"');
}
}
var result = '';
var inserts_since_last_equality = 0;
var deletes_since_last_equality = 0;
for (var i = 0; i < diffs.length; i++) {
var d = diffs[i];
if (!d[1]) {
throwError('Empty tuple in diff')
}
var firstCharCode = d[1].charCodeAt(0);
var lastCharCode = d[1].slice(-1).charCodeAt(0);
if (firstCharCode >= 0xDC00 && firstCharCode <= 0xDFFF ||
lastCharCode >= 0xD800 && lastCharCode <= 0xDBFF) {
throwError('Bad unicode diff tuple')
}
switch (d[0]) {
case diff.EQUAL:
if (i !== 0 && !inserts_since_last_equality && !deletes_since_last_equality) {
throwError('two consecutive equalities in diff');
}
inserts_since_last_equality = 0;
deletes_since_last_equality = 0;
expect(d[1]);
result += d[1];
pos += d[1].length;
break;
case diff.DELETE:
if (deletes_since_last_equality) {
throwError('multiple deletes between equalities')
}
if (inserts_since_last_equality) {
throwError('delete following insert in diff')
}
deletes_since_last_equality++;
expect(d[1]);
pos += d[1].length;
break
case diff.INSERT:
if (inserts_since_last_equality) {
throwError('multiple inserts between equalities')
}
inserts_since_last_equality++;
result += d[1];
break;
}
}
if (pos !== text.length) {
throwError('Diff did not consume entire input text');
}
if (result !== expectedResult) {
console.log(diffs, text, expectedResult, result);
throw new Error('Diff not correct')
}
return result;
}
console.log("Success!");