'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', ]); }); });