'use strict';
const Changeset = require('../../../static/js/Changeset');
const AttributePool = require('../../../static/js/AttributePool');
const {poolOrArray} = require('../easysync-helper.js');
describe('easysync-mutations', function () {
const applyMutations = (mu, arrayOfArrays) => {
arrayOfArrays.forEach((a) => {
const result = mu[a[0]](...a.slice(1));
if (a[0] === 'remove' && a[3]) {
expect(result).to.equal(a[3]);
}
});
};
const mutationsToChangeset = (oldLen, arrayOfArrays) => {
const assem = Changeset.smartOpAssembler();
const op = new Changeset.Op();
const bank = Changeset.stringAssembler();
let oldPos = 0;
let newLen = 0;
arrayOfArrays.forEach((a) => {
if (a[0] === 'skip') {
op.opcode = '=';
op.chars = a[1];
op.lines = (a[2] || 0);
assem.append(op);
oldPos += op.chars;
newLen += op.chars;
} else if (a[0] === 'remove') {
op.opcode = '-';
op.chars = a[1];
op.lines = (a[2] || 0);
assem.append(op);
oldPos += op.chars;
} else if (a[0] === 'insert') {
op.opcode = '+';
bank.append(a[1]);
op.chars = a[1].length;
op.lines = (a[2] || 0);
assem.append(op);
newLen += op.chars;
}
});
newLen += oldLen - oldPos;
assem.endDocument();
return Changeset.pack(oldLen, newLen, assem.toString(), bank.toString());
};
const runMutationTest = (testId, origLines, muts, correct) => {
it(`runMutationTest#${testId}`, async function () {
let lines = origLines.slice();
const mu = new Changeset.exportedForTestingOnly.TextLinesMutator(lines);
applyMutations(mu, muts);
mu.close();
expect(lines).to.eql(correct);
const inText = origLines.join('');
const cs = mutationsToChangeset(inText.length, muts);
lines = origLines.slice();
Changeset.mutateTextLines(cs, lines);
expect(lines).to.eql(correct);
const correctText = correct.join('');
const outText = Changeset.applyToText(cs, inText);
expect(outText).to.equal(correctText);
});
};
runMutationTest(1, ['apple\n', 'banana\n', 'cabbage\n', 'duffle\n', 'eggplant\n'], [
['remove', 1, 0, 'a'],
['insert', 'tu'],
['remove', 1, 0, 'p'],
['skip', 4, 1],
['skip', 7, 1],
['insert', 'cream\npie\n', 2],
['skip', 2],
['insert', 'bot'],
['insert', '\n', 1],
['insert', 'bu'],
['skip', 3],
['remove', 3, 1, 'ge\n'],
['remove', 6, 0, 'duffle'],
], ['tuple\n', 'banana\n', 'cream\n', 'pie\n', 'cabot\n', 'bubba\n', 'eggplant\n']);
runMutationTest(2, ['apple\n', 'banana\n', 'cabbage\n', 'duffle\n', 'eggplant\n'], [
['remove', 1, 0, 'a'],
['remove', 1, 0, 'p'],
['insert', 'tu'],
['skip', 11, 2],
['insert', 'cream\npie\n', 2],
['skip', 2],
['insert', 'bot'],
['insert', '\n', 1],
['insert', 'bu'],
['skip', 3],
['remove', 3, 1, 'ge\n'],
['remove', 6, 0, 'duffle'],
], ['tuple\n', 'banana\n', 'cream\n', 'pie\n', 'cabot\n', 'bubba\n', 'eggplant\n']);
runMutationTest(3, ['apple\n', 'banana\n', 'cabbage\n', 'duffle\n', 'eggplant\n'], [
['remove', 6, 1, 'apple\n'],
['skip', 15, 2],
['skip', 6],
['remove', 1, 1, '\n'],
['remove', 8, 0, 'eggplant'],
['skip', 1, 1],
], ['banana\n', 'cabbage\n', 'duffle\n']);
runMutationTest(4, ['15\n'], [
['skip', 1],
['insert', '\n2\n3\n4\n', 4],
['skip', 2, 1],
], ['1\n', '2\n', '3\n', '4\n', '5\n']);
runMutationTest(5, ['1\n', '2\n', '3\n', '4\n', '5\n'], [
['skip', 1],
['remove', 7, 4, '\n2\n3\n4\n'],
['skip', 2, 1],
], ['15\n']);
runMutationTest(6, ['123\n', 'abc\n', 'def\n', 'ghi\n', 'xyz\n'], [
['insert', '0'],
['skip', 4, 1],
['skip', 4, 1],
['remove', 8, 2, 'def\nghi\n'],
['skip', 4, 1],
], ['0123\n', 'abc\n', 'xyz\n']);
runMutationTest(7, ['apple\n', 'banana\n', 'cabbage\n', 'duffle\n', 'eggplant\n'], [
['remove', 6, 1, 'apple\n'],
['skip', 15, 2, true],
['skip', 6, 0, true],
['remove', 1, 1, '\n'],
['remove', 8, 0, 'eggplant'],
['skip', 1, 1, true],
], ['banana\n', 'cabbage\n', 'duffle\n']);
it('mutatorHasMore', async function () {
const lines = ['1\n', '2\n', '3\n', '4\n'];
let mu;
mu = new Changeset.exportedForTestingOnly.TextLinesMutator(lines);
expect(mu.hasMore()).to.be(true);
mu.skip(8, 4);
expect(mu.hasMore()).to.be(false);
mu.close();
expect(mu.hasMore()).to.be(false);
// still 1,2,3,4
mu = new Changeset.exportedForTestingOnly.TextLinesMutator(lines);
expect(mu.hasMore()).to.be(true);
mu.remove(2, 1);
expect(mu.hasMore()).to.be(true);
mu.skip(2, 1);
expect(mu.hasMore()).to.be(true);
mu.skip(2, 1);
expect(mu.hasMore()).to.be(true);
mu.skip(2, 1);
expect(mu.hasMore()).to.be(false);
mu.insert('5\n', 1);
expect(mu.hasMore()).to.be(false);
mu.close();
expect(mu.hasMore()).to.be(false);
// 2,3,4,5 now
mu = new Changeset.exportedForTestingOnly.TextLinesMutator(lines);
expect(mu.hasMore()).to.be(true);
mu.remove(6, 3);
expect(mu.hasMore()).to.be(true);
mu.remove(2, 1);
expect(mu.hasMore()).to.be(false);
mu.insert('hello\n', 1);
expect(mu.hasMore()).to.be(false);
mu.close();
expect(mu.hasMore()).to.be(false);
});
describe('mutateTextLines', function () {
const testMutateTextLines = (testId, cs, lines, correctLines) => {
it(`testMutateTextLines#${testId}`, async function () {
const a = lines.slice();
Changeset.mutateTextLines(cs, a);
expect(a).to.eql(correctLines);
});
};
testMutateTextLines(1, 'Z:4<1|1-2-1|1+1+1$\nc', ['a\n', 'b\n'], ['\n', 'c\n']);
testMutateTextLines(2, 'Z:4>0|1-2-1|2+3$\nc\n', ['a\n', 'b\n'], ['\n', 'c\n', '\n']);
it('mutate keep only lines', async function () {
const lines = ['1\n', '2\n', '3\n', '4\n'];
const result = lines.slice();
const cs = 'Z:8>0*0|1=2|2=2';
Changeset.mutateTextLines(cs, lines);
expect(result).to.eql(lines);
});
});
describe('mutate attributions', function () {
const testPoolWithChars = (() => {
const p = new AttributePool();
p.putAttrib(['char', 'newline']);
for (let i = 1; i < 36; i++) {
p.putAttrib(['char', Changeset.numToString(i)]);
}
p.putAttrib(['char', '']);
return p;
})();
const runMutateAttributionTest = (testId, attribs, cs, alines, outCorrect) => {
it(`runMutateAttributionTest#${testId}`, async function () {
const p = poolOrArray(attribs);
const alines2 = Array.prototype.slice.call(alines);
Changeset.mutateAttributionLines(Changeset.checkRep(cs), alines2, p);
expect(alines2).to.eql(outCorrect);
const removeQuestionMarks = (a) => a.replace(/\?/g, '');
const inMerged = Changeset.joinAttributionLines(alines.map(removeQuestionMarks));
const correctMerged = Changeset.joinAttributionLines(outCorrect.map(removeQuestionMarks));
const mergedResult = Changeset.applyToAttribution(cs, inMerged, p);
expect(mergedResult).to.equal(correctMerged);
});
};
// turn 123\n 456\n 789\n into 123\n 456\n 789\n
runMutateAttributionTest(1,
['bold,true'], 'Z:c>0|1=4=1*0=1$', ['|1+4', '|1+4', '|1+4'], ['|1+4', '+1*0+1|1+2', '|1+4']
);
// make a document bold
runMutateAttributionTest(2,
['bold,true'], 'Z:c>0*0|3=c$', ['|1+4', '|1+4', '|1+4'], ['*0|1+4', '*0|1+4', '*0|1+4']);
// clear bold on document
runMutateAttributionTest(3,
['bold,', 'bold,true'], 'Z:c>0*0|3=c$',
['*1+1+1*1+1|1+1', '+1*1+1|1+2', '*1+1+1*1+1|1+1'], ['|1+4', '|1+4', '|1+4']);
// add a character on line 3 of a document with 5 blank lines, and make sure
// the optimization that skips purely-kept lines is working; if any attribution string
// with a '?' is parsed it will cause an error.
runMutateAttributionTest(4,
['foo,bar', 'line,1', 'line,2', 'line,3', 'line,4', 'line,5'],
'Z:5>1|2=2+1$x', ['?*1|1+1', '?*2|1+1', '*3|1+1', '?*4|1+1', '?*5|1+1'],
['?*1|1+1', '?*2|1+1', '+1*3|1+1', '?*4|1+1', '?*5|1+1']);
// based on runMutationTest#1
runMutateAttributionTest(5, testPoolWithChars,
'Z:11>7-2*t+1*u+1|2=b|2+a=2*b+1*o+1*t+1*0|1+1*b+1*u+1=3|1-3-6$tucream\npie\nbot\nbu',
[
'*a+1*p+2*l+1*e+1*0|1+1',
'*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1',
'*c+1*a+1*b+2*a+1*g+1*e+1*0|1+1',
'*d+1*u+1*f+2*l+1*e+1*0|1+1',
'*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1',
],
[
'*t+1*u+1*p+1*l+1*e+1*0|1+1',
'*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1',
'|1+6',
'|1+4',
'*c+1*a+1*b+1*o+1*t+1*0|1+1',
'*b+1*u+1*b+2*a+1*0|1+1',
'*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1',
]);
// based on runMutationTest#3
runMutateAttributionTest(6, testPoolWithChars,
'Z:117=1|4+7$\n2\n3\n4\n',
['*1+1*5|1+2'], ['*1+1|1+1', '|1+2', '|1+2', '|1+2', '*5|1+2']);
// based on runMutationTest#5
runMutateAttributionTest(8, testPoolWithChars, 'Z:a<7=1|4-7$',
['*1|1+2', '*2|1+2', '*3|1+2', '*4|1+2', '*5|1+2'], ['*1+1*5|1+2']);
// based on runMutationTest#6
runMutateAttributionTest(9, testPoolWithChars, 'Z:k<7*0+1*10|2=8|2-8$0',
[
'*1+1*2+1*3+1|1+1',
'*a+1*b+1*c+1|1+1',
'*d+1*e+1*f+1|1+1',
'*g+1*h+1*i+1|1+1',
'?*x+1*y+1*z+1|1+1',
],
['*0+1|1+4', '|1+4', '?*x+1*y+1*z+1|1+1']);
runMutateAttributionTest(10, testPoolWithChars, 'Z:6>4=1+1=1+1|1=1+1=1*0+1$abcd',
['|1+3', '|1+3'], ['|1+5', '+2*0+1|1+2']);
runMutateAttributionTest(11, testPoolWithChars, 'Z:s>1|1=4=6|1+1$\n',
['*0|1+4', '*0|1+8', '*0+5|1+1', '*0|1+1', '*0|1+5', '*0|1+1', '*0|1+1', '*0|1+1', '|1+1'],
[
'*0|1+4',
'*0+6|1+1',
'*0|1+2',
'*0+5|1+1',
'*0|1+1',
'*0|1+5',
'*0|1+1',
'*0|1+1',
'*0|1+1',
'|1+1',
]);
});
});