mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
Changeset: Move changeset logic to a new Changeset
class
This commit is contained in:
parent
a470253779
commit
b718d88157
11 changed files with 193 additions and 127 deletions
|
@ -582,11 +582,10 @@ const handleUserChanges = async (socket, message) => {
|
|||
const wireApool = (new AttributePool()).fromJsonable(apool);
|
||||
const pad = await padManager.getPad(thisSession.padId);
|
||||
|
||||
// Verify that the changeset has valid syntax and is in canonical form
|
||||
Changeset.checkRep(changeset);
|
||||
const cs = Changeset.unpack(changeset).validate();
|
||||
|
||||
// Validate all added 'author' attribs to be the same value as the current user
|
||||
for (const op of Changeset.deserializeOps(Changeset.unpack(changeset).ops)) {
|
||||
for (const op of Changeset.deserializeOps(cs.ops)) {
|
||||
// + can add text with attribs
|
||||
// = can change or add attribs
|
||||
// - can have attribs, but they are discarded and don't show up in the attribs -
|
||||
|
|
|
@ -438,7 +438,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
|
|||
}
|
||||
}
|
||||
|
||||
return Changeset.checkRep(builder.toString());
|
||||
return builder.build().validate().toString();
|
||||
};
|
||||
|
||||
// export the constructor
|
||||
|
|
|
@ -155,14 +155,133 @@ exports.Op = Op;
|
|||
/**
|
||||
* Describes changes to apply to a document. Does not include the attribute pool or the original
|
||||
* document.
|
||||
*
|
||||
* @typedef {object} Changeset
|
||||
* @property {number} oldLen - The length of the base document.
|
||||
* @property {number} newLen - The length of the document after applying the changeset.
|
||||
* @property {string} ops - Serialized sequence of operations. Use `deserializeOps` to parse this
|
||||
* string.
|
||||
* @property {string} charBank - Characters inserted by insert operations.
|
||||
*/
|
||||
class Changeset {
|
||||
/**
|
||||
* Parses an encoded changeset.
|
||||
*
|
||||
* @param {string} cs - Encoded changeset.
|
||||
* @returns {Changeset}
|
||||
*/
|
||||
static unpack(cs) {
|
||||
const headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
|
||||
const headerMatch = headerRegex.exec(cs);
|
||||
if ((!headerMatch) || (!headerMatch[0])) error(`Not a changeset: ${cs}`);
|
||||
const oldLen = exports.parseNum(headerMatch[1]);
|
||||
const changeSign = (headerMatch[2] === '>') ? 1 : -1;
|
||||
const changeMag = exports.parseNum(headerMatch[3]);
|
||||
const newLen = oldLen + changeSign * changeMag;
|
||||
const opsStart = headerMatch[0].length;
|
||||
let opsEnd = cs.indexOf('$');
|
||||
if (opsEnd < 0) opsEnd = cs.length;
|
||||
return new Changeset(oldLen, newLen, cs.substring(opsStart, opsEnd), cs.substring(opsEnd + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} oldLen - Initial value of the `oldLen` property.
|
||||
* @param {number} newLen - Initial value of the `newLen` property.
|
||||
* @param {string} ops - Initial value of the `ops` property.
|
||||
* @param {string} charBank - Initial value of the `charBank` property.
|
||||
*/
|
||||
constructor(oldLen, newLen, ops, charBank) {
|
||||
/**
|
||||
* The length of the base document.
|
||||
*
|
||||
* @type {number}
|
||||
* @public
|
||||
*/
|
||||
this.oldLen = oldLen;
|
||||
|
||||
/**
|
||||
* The length of the document after applying the changeset.
|
||||
*
|
||||
* @type {number}
|
||||
* @public
|
||||
*/
|
||||
this.newLen = newLen;
|
||||
|
||||
/**
|
||||
* Serialized sequence of operations. Use `deserializeOps` to parse this string.
|
||||
*
|
||||
* @type {string}
|
||||
* @public
|
||||
*/
|
||||
this.ops = ops;
|
||||
|
||||
/**
|
||||
* Characters inserted by insert operations.
|
||||
*
|
||||
* @type {string}
|
||||
* @public
|
||||
*/
|
||||
this.charBank = charBank;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The encoded changeset.
|
||||
*/
|
||||
toString() {
|
||||
const lenDiff = this.newLen - this.oldLen;
|
||||
const lenDiffStr = lenDiff >= 0
|
||||
? `>${exports.numToString(lenDiff)}`
|
||||
: `<${exports.numToString(-lenDiff)}`;
|
||||
const a = [];
|
||||
a.push('Z:', exports.numToString(this.oldLen), lenDiffStr, this.ops, '$', this.charBank);
|
||||
return a.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that this Changeset is valid. This method does not check things that require access to
|
||||
* the attribute pool (e.g., attribute order) or original text (e.g., newline positions).
|
||||
*
|
||||
* @returns {Changeset} this (for chaining)
|
||||
*/
|
||||
validate() {
|
||||
let charBank = this.charBank;
|
||||
let oldPos = 0;
|
||||
let calcNewLen = 0;
|
||||
const cs = this.toString();
|
||||
const ops = (function* () {
|
||||
for (const o of exports.deserializeOps(this.ops)) {
|
||||
switch (o.opcode) {
|
||||
case '=':
|
||||
oldPos += o.chars;
|
||||
calcNewLen += o.chars;
|
||||
break;
|
||||
case '-':
|
||||
oldPos += o.chars;
|
||||
assert(oldPos <= this.oldLen, `${oldPos} > ${this.oldLen} in ${cs}`);
|
||||
break;
|
||||
case '+': {
|
||||
assert(charBank.length >= o.chars, 'Invalid changeset: not enough chars in charBank');
|
||||
const chars = charBank.slice(0, o.chars);
|
||||
const nlines = (chars.match(/\n/g) || []).length;
|
||||
assert(nlines === o.lines,
|
||||
'Invalid changeset: number of newlines in insert op does not match the charBank');
|
||||
assert(o.lines === 0 || chars.endsWith('\n'),
|
||||
'Invalid changeset: multiline insert op does not end with a newline');
|
||||
charBank = charBank.slice(o.chars);
|
||||
calcNewLen += o.chars;
|
||||
assert(calcNewLen <= this.newLen, `${calcNewLen} > ${this.newLen} in ${cs}`);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false, `Invalid changeset: Unknown opcode: ${JSON.stringify(o.opcode)}`);
|
||||
}
|
||||
yield o;
|
||||
}
|
||||
}).call(this);
|
||||
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
|
||||
calcNewLen += this.oldLen - oldPos;
|
||||
assert(calcNewLen === this.newLen,
|
||||
'Invalid changeset: claimed length does not match actual length');
|
||||
assert(charBank === '', 'Invalid changeset: excess characters in the charBank');
|
||||
const normalized =
|
||||
new Changeset(this.oldLen, calcNewLen, serializedOps, this.charBank).toString();
|
||||
assert(normalized === cs, 'Invalid changeset: not in canonical form');
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required length of the text before changeset can be applied.
|
||||
|
@ -170,7 +289,7 @@ exports.Op = Op;
|
|||
* @param {string} cs - String representation of the Changeset
|
||||
* @returns {number} oldLen property
|
||||
*/
|
||||
exports.oldLen = (cs) => exports.unpack(cs).oldLen;
|
||||
exports.oldLen = (cs) => Changeset.unpack(cs).oldLen;
|
||||
|
||||
/**
|
||||
* Returns the length of the text after changeset is applied.
|
||||
|
@ -178,7 +297,7 @@ exports.oldLen = (cs) => exports.unpack(cs).oldLen;
|
|||
* @param {string} cs - String representation of the Changeset
|
||||
* @returns {number} newLen property
|
||||
*/
|
||||
exports.newLen = (cs) => exports.unpack(cs).newLen;
|
||||
exports.newLen = (cs) => Changeset.unpack(cs).newLen;
|
||||
|
||||
/**
|
||||
* Parses a string of serialized changeset operations.
|
||||
|
@ -581,53 +700,14 @@ class SmartOpAssembler {
|
|||
* Used to check if a Changeset is valid. This function does not check things that require access to
|
||||
* the attribute pool (e.g., attribute order) or original text (e.g., newline positions).
|
||||
*
|
||||
* @deprecated Use `Changeset.unpack(cs).validate()` instead.
|
||||
* @param {string} cs - Changeset to check
|
||||
* @returns {string} the checked Changeset
|
||||
*/
|
||||
exports.checkRep = (cs) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const oldLen = unpacked.oldLen;
|
||||
const newLen = unpacked.newLen;
|
||||
let charBank = unpacked.charBank;
|
||||
|
||||
let oldPos = 0;
|
||||
let calcNewLen = 0;
|
||||
const ops = (function* () {
|
||||
for (const o of exports.deserializeOps(unpacked.ops)) {
|
||||
switch (o.opcode) {
|
||||
case '=':
|
||||
oldPos += o.chars;
|
||||
calcNewLen += o.chars;
|
||||
break;
|
||||
case '-':
|
||||
oldPos += o.chars;
|
||||
assert(oldPos <= oldLen, `${oldPos} > ${oldLen} in ${cs}`);
|
||||
break;
|
||||
case '+': {
|
||||
assert(charBank.length >= o.chars, 'Invalid changeset: not enough chars in charBank');
|
||||
const chars = charBank.slice(0, o.chars);
|
||||
const nlines = (chars.match(/\n/g) || []).length;
|
||||
assert(nlines === o.lines,
|
||||
'Invalid changeset: number of newlines in insert op does not match the charBank');
|
||||
assert(o.lines === 0 || chars.endsWith('\n'),
|
||||
'Invalid changeset: multiline insert op does not end with a newline');
|
||||
charBank = charBank.slice(o.chars);
|
||||
calcNewLen += o.chars;
|
||||
assert(calcNewLen <= newLen, `${calcNewLen} > ${newLen} in ${cs}`);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false, `Invalid changeset: Unknown opcode: ${JSON.stringify(o.opcode)}`);
|
||||
}
|
||||
yield o;
|
||||
}
|
||||
})();
|
||||
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
|
||||
calcNewLen += oldLen - oldPos;
|
||||
assert(calcNewLen === newLen, 'Invalid changeset: claimed length does not match actual length');
|
||||
assert(charBank === '', 'Invalid changeset: excess characters in the charBank');
|
||||
const normalized = exports.pack(oldLen, calcNewLen, serializedOps, unpacked.charBank);
|
||||
assert(normalized === cs, 'Invalid changeset: not in canonical form');
|
||||
padutils.warnDeprecated(
|
||||
'Changeset.checkRep(cs) is deprecated; use Changeset.unpack(cs).validate() instead.');
|
||||
Changeset.unpack(cs).validate();
|
||||
return cs;
|
||||
};
|
||||
|
||||
|
@ -1108,24 +1188,7 @@ const applyZip = (in1, in2, func) => {
|
|||
* @param {string} cs - The encoded changeset.
|
||||
* @returns {Changeset}
|
||||
*/
|
||||
exports.unpack = (cs) => {
|
||||
const headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
|
||||
const headerMatch = headerRegex.exec(cs);
|
||||
if ((!headerMatch) || (!headerMatch[0])) error(`Not a changeset: ${cs}`);
|
||||
const oldLen = exports.parseNum(headerMatch[1]);
|
||||
const changeSign = (headerMatch[2] === '>') ? 1 : -1;
|
||||
const changeMag = exports.parseNum(headerMatch[3]);
|
||||
const newLen = oldLen + changeSign * changeMag;
|
||||
const opsStart = headerMatch[0].length;
|
||||
let opsEnd = cs.indexOf('$');
|
||||
if (opsEnd < 0) opsEnd = cs.length;
|
||||
return {
|
||||
oldLen,
|
||||
newLen,
|
||||
ops: cs.substring(opsStart, opsEnd),
|
||||
charBank: cs.substring(opsEnd + 1),
|
||||
};
|
||||
};
|
||||
exports.unpack = (cs) => Changeset.unpack(cs);
|
||||
|
||||
/**
|
||||
* Creates an encoded changeset.
|
||||
|
@ -1136,14 +1199,8 @@ exports.unpack = (cs) => {
|
|||
* @param {string} bank - Characters for insert operations.
|
||||
* @returns {string} The encoded changeset.
|
||||
*/
|
||||
exports.pack = (oldLen, newLen, opsStr, bank) => {
|
||||
const lenDiff = newLen - oldLen;
|
||||
const lenDiffStr = (lenDiff >= 0 ? `>${exports.numToString(lenDiff)}`
|
||||
: `<${exports.numToString(-lenDiff)}`);
|
||||
const a = [];
|
||||
a.push('Z:', exports.numToString(oldLen), lenDiffStr, opsStr, '$', bank);
|
||||
return a.join('');
|
||||
};
|
||||
exports.pack =
|
||||
(oldLen, newLen, opsStr, bank) => new Changeset(oldLen, newLen, opsStr, bank).toString();
|
||||
|
||||
/**
|
||||
* Applies a Changeset to a string.
|
||||
|
@ -1153,7 +1210,7 @@ exports.pack = (oldLen, newLen, opsStr, bank) => {
|
|||
* @returns {string}
|
||||
*/
|
||||
exports.applyToText = (cs, str) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const unpacked = Changeset.unpack(cs);
|
||||
assert(str.length === unpacked.oldLen, `mismatched apply: ${str.length} / ${unpacked.oldLen}`);
|
||||
const bankIter = new StringIterator(unpacked.charBank);
|
||||
const strIter = new StringIterator(str);
|
||||
|
@ -1197,7 +1254,7 @@ exports.applyToText = (cs, str) => {
|
|||
* @param {string[]} lines - The lines to which the changeset needs to be applied
|
||||
*/
|
||||
exports.mutateTextLines = (cs, lines) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const unpacked = Changeset.unpack(cs);
|
||||
const bankIter = new StringIterator(unpacked.charBank);
|
||||
const mut = new TextLinesMutator(lines);
|
||||
for (const op of exports.deserializeOps(unpacked.ops)) {
|
||||
|
@ -1321,12 +1378,12 @@ const slicerZipperFunc = (attOp, csOp, pool) => {
|
|||
* @returns {string}
|
||||
*/
|
||||
exports.applyToAttribution = (cs, astr, pool) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const unpacked = Changeset.unpack(cs);
|
||||
return applyZip(astr, unpacked.ops, (op1, op2) => slicerZipperFunc(op1, op2, pool));
|
||||
};
|
||||
|
||||
exports.mutateAttributionLines = (cs, lines, pool) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const unpacked = Changeset.unpack(cs);
|
||||
const csOps = exports.deserializeOps(unpacked.ops);
|
||||
let csOpsNext = csOps.next();
|
||||
const csBank = unpacked.charBank;
|
||||
|
@ -1468,8 +1525,8 @@ exports.splitTextLines = (text) => text.match(/[^\n]*(?:\n|[^\n]$)/g);
|
|||
* @returns {string}
|
||||
*/
|
||||
exports.compose = (cs1, cs2, pool) => {
|
||||
const unpacked1 = exports.unpack(cs1);
|
||||
const unpacked2 = exports.unpack(cs2);
|
||||
const unpacked1 = Changeset.unpack(cs1);
|
||||
const unpacked2 = Changeset.unpack(cs2);
|
||||
const len1 = unpacked1.oldLen;
|
||||
const len2 = unpacked1.newLen;
|
||||
assert(len2 === unpacked2.oldLen, 'mismatched composition of two changesets');
|
||||
|
@ -1491,7 +1548,7 @@ exports.compose = (cs1, cs2, pool) => {
|
|||
return opOut;
|
||||
});
|
||||
|
||||
return exports.pack(len1, len3, newOps, bankAssem);
|
||||
return new Changeset(len1, len3, newOps, bankAssem).toString();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1517,7 +1574,7 @@ exports.attributeTester = (attribPair, pool) => {
|
|||
* @param {number} N - length of the identity changeset
|
||||
* @returns {string}
|
||||
*/
|
||||
exports.identity = (N) => exports.pack(N, N, '', '');
|
||||
exports.identity = (N) => new Changeset(N, N, '', '').toString();
|
||||
|
||||
/**
|
||||
* Creates a Changeset which works on oldFullText and removes text from spliceStart to
|
||||
|
@ -1544,7 +1601,7 @@ exports.makeSplice = (orig, start, ndel, ins, attribs, pool) => {
|
|||
yield* opsFromText('+', ins, attribs, pool);
|
||||
})();
|
||||
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
|
||||
return exports.pack(orig.length, orig.length + ins.length - ndel, serializedOps, ins);
|
||||
return new Changeset(orig.length, orig.length + ins.length - ndel, serializedOps, ins).toString();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1555,7 +1612,7 @@ exports.makeSplice = (orig, start, ndel, ins, attribs, pool) => {
|
|||
* @returns {[number, number, string][]}
|
||||
*/
|
||||
const toSplices = (cs) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const unpacked = Changeset.unpack(cs);
|
||||
/** @type {[number, number, string][]} */
|
||||
const splices = [];
|
||||
|
||||
|
@ -1860,7 +1917,7 @@ exports.prepareForWire = (cs, pool) => {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
exports.isIdentity = (cs) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const unpacked = Changeset.unpack(cs);
|
||||
return unpacked.ops === '' && unpacked.oldLen === unpacked.newLen;
|
||||
};
|
||||
|
||||
|
@ -1989,17 +2046,12 @@ class Builder {
|
|||
const serializedOps = exports.serializeOps((function* () {
|
||||
lengthChange = yield* exports.canonicalizeOps(this._ops, true);
|
||||
}).call(this));
|
||||
return {
|
||||
oldLen: this._oldLen,
|
||||
newLen: this._oldLen + lengthChange,
|
||||
ops: serializedOps,
|
||||
charBank: this._charBank,
|
||||
};
|
||||
const newLen = this._oldLen + lengthChange;
|
||||
return new Changeset(this._oldLen, newLen, serializedOps, this._charBank);
|
||||
}
|
||||
|
||||
toString() {
|
||||
const {oldLen, newLen, ops, charBank} = this.build();
|
||||
return exports.pack(oldLen, newLen, ops, charBank);
|
||||
return this.build().toString();
|
||||
}
|
||||
}
|
||||
exports.Builder = Builder;
|
||||
|
@ -2112,7 +2164,7 @@ exports.inverse = (cs, lines, alines, pool) => {
|
|||
let curLineOpsLine;
|
||||
let curLineNextOp = new Op('+');
|
||||
|
||||
const unpacked = exports.unpack(cs);
|
||||
const unpacked = Changeset.unpack(cs);
|
||||
const builder = new Builder(unpacked.newLen);
|
||||
|
||||
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
|
||||
|
@ -2236,13 +2288,13 @@ exports.inverse = (cs, lines, alines, pool) => {
|
|||
}
|
||||
}
|
||||
|
||||
return exports.checkRep(builder.toString());
|
||||
return builder.build().validate().toString();
|
||||
};
|
||||
|
||||
// %CLIENT FILE ENDS HERE%
|
||||
exports.follow = (cs1, cs2, reverseInsertOrder, pool) => {
|
||||
const unpacked1 = exports.unpack(cs1);
|
||||
const unpacked2 = exports.unpack(cs2);
|
||||
const unpacked1 = Changeset.unpack(cs1);
|
||||
const unpacked2 = Changeset.unpack(cs2);
|
||||
const len1 = unpacked1.oldLen;
|
||||
const len2 = unpacked2.oldLen;
|
||||
assert(len1 === len2, 'mismatched follow - cannot transform cs1 on top of cs2');
|
||||
|
@ -2378,7 +2430,7 @@ exports.follow = (cs1, cs2, reverseInsertOrder, pool) => {
|
|||
});
|
||||
newLen += oldLen - oldPos;
|
||||
|
||||
return exports.pack(oldLen, newLen, newOps, unpacked2.charBank);
|
||||
return new Changeset(oldLen, newLen, newOps, unpacked2.charBank).toString();
|
||||
};
|
||||
|
||||
const followAttributes = (att1, att2, pool) => {
|
||||
|
|
|
@ -538,8 +538,8 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
lengthChange = yield* Changeset.canonicalizeOps(ops, false);
|
||||
})());
|
||||
const newLen = oldLen + lengthChange;
|
||||
const changeset =
|
||||
Changeset.checkRep(Changeset.pack(oldLen, newLen, serializedOps, atext.text.slice(0, -1)));
|
||||
const changeset = Changeset.pack(oldLen, newLen, serializedOps, atext.text.slice(0, -1));
|
||||
Changeset.unpack(changeset).validate();
|
||||
performDocumentApplyChangeset(changeset);
|
||||
|
||||
performSelectionChange(
|
||||
|
@ -1447,7 +1447,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
};
|
||||
|
||||
const doRepApplyChangeset = (changes, insertsAfterSelection) => {
|
||||
Changeset.checkRep(changes);
|
||||
Changeset.unpack(changes).validate();
|
||||
|
||||
if (Changeset.oldLen(changes) !== rep.alltext.length) {
|
||||
const errMsg = `${Changeset.oldLen(changes)}/${rep.alltext.length}`;
|
||||
|
|
|
@ -158,7 +158,7 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => {
|
|||
})();
|
||||
const serializedOps = Changeset.serializeOps(Changeset.squashOps(ops, true));
|
||||
userChangeset = Changeset.pack(cs.oldLen, cs.newLen, serializedOps, cs.charBank);
|
||||
Changeset.checkRep(userChangeset);
|
||||
Changeset.unpack(userChangeset).validate();
|
||||
|
||||
if (Changeset.isIdentity(userChangeset)) toSubmit = null;
|
||||
else toSubmit = userChangeset;
|
||||
|
|
|
@ -202,7 +202,7 @@ const randomTestChangeset = (origText, withAttribs) => {
|
|||
const outText = `${outTextAssem}\n`;
|
||||
const serializedOps = Changeset.serializeOps(Changeset.canonicalizeOps(ops, true));
|
||||
const cs = Changeset.pack(oldLen, outText.length, serializedOps, charBank);
|
||||
Changeset.checkRep(cs);
|
||||
Changeset.unpack(cs).validate();
|
||||
return [cs, outText];
|
||||
};
|
||||
exports.randomTestChangeset = randomTestChangeset;
|
||||
|
|
|
@ -24,10 +24,14 @@ describe('easysync-compose', function () {
|
|||
const change3 = x3[0];
|
||||
const text3 = x3[1];
|
||||
|
||||
const change12 = Changeset.checkRep(Changeset.compose(change1, change2, p));
|
||||
const change23 = Changeset.checkRep(Changeset.compose(change2, change3, p));
|
||||
const change123 = Changeset.checkRep(Changeset.compose(change12, change3, p));
|
||||
const change123a = Changeset.checkRep(Changeset.compose(change1, change23, p));
|
||||
const change12 = Changeset.compose(change1, change2, p);
|
||||
Changeset.unpack(change12).validate();
|
||||
const change23 = Changeset.compose(change2, change3, p);
|
||||
Changeset.unpack(change23).validate();
|
||||
const change123 = Changeset.compose(change12, change3, p);
|
||||
Changeset.unpack(change123).validate();
|
||||
const change123a = Changeset.compose(change1, change23, p);
|
||||
Changeset.unpack(change123a).validate();
|
||||
expect(change123a).to.equal(change123);
|
||||
|
||||
expect(Changeset.applyToText(change12, startText)).to.equal(text2);
|
||||
|
@ -44,9 +48,12 @@ describe('easysync-compose', function () {
|
|||
const p = new AttributePool();
|
||||
p.putAttrib(['bold', '']);
|
||||
p.putAttrib(['bold', 'true']);
|
||||
const cs1 = Changeset.checkRep('Z:2>1*1+1*1=1$x');
|
||||
const cs2 = Changeset.checkRep('Z:3>0*0|1=3$');
|
||||
const cs12 = Changeset.checkRep(Changeset.compose(cs1, cs2, p));
|
||||
const cs1 = 'Z:2>1*1+1*1=1$x';
|
||||
Changeset.unpack(cs1).validate();
|
||||
const cs2 = 'Z:3>0*0|1=3$';
|
||||
Changeset.unpack(cs2).validate();
|
||||
const cs12 = Changeset.compose(cs1, cs2, p);
|
||||
Changeset.unpack(cs12).validate();
|
||||
expect(cs12).to.equal('Z:2>1+1*0|1=2$x');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,11 +15,15 @@ describe('easysync-follow', function () {
|
|||
const cs1 = randomTestChangeset(startText)[0];
|
||||
const cs2 = randomTestChangeset(startText)[0];
|
||||
|
||||
const afb = Changeset.checkRep(Changeset.follow(cs1, cs2, false, p));
|
||||
const bfa = Changeset.checkRep(Changeset.follow(cs2, cs1, true, p));
|
||||
const afb = Changeset.follow(cs1, cs2, false, p);
|
||||
Changeset.unpack(afb).validate();
|
||||
const bfa = Changeset.follow(cs2, cs1, true, p);
|
||||
Changeset.unpack(bfa).validate();
|
||||
|
||||
const merge1 = Changeset.checkRep(Changeset.compose(cs1, afb));
|
||||
const merge2 = Changeset.checkRep(Changeset.compose(cs2, bfa));
|
||||
const merge1 = Changeset.compose(cs1, afb);
|
||||
Changeset.unpack(merge1).validate();
|
||||
const merge2 = Changeset.compose(cs2, bfa);
|
||||
Changeset.unpack(merge2).validate();
|
||||
|
||||
expect(merge2).to.equal(merge1);
|
||||
});
|
||||
|
@ -60,7 +64,7 @@ describe('easysync-follow', function () {
|
|||
describe('chracterRangeFollow', function () {
|
||||
const testCharacterRangeFollow = (testId, cs, oldRange, insertionsAfter, correctNewRange) => {
|
||||
it(`testCharacterRangeFollow#${testId}`, async function () {
|
||||
cs = Changeset.checkRep(cs);
|
||||
Changeset.unpack(cs).validate();
|
||||
expect(Changeset.characterRangeFollow(cs, oldRange[0], oldRange[1], insertionsAfter))
|
||||
.to.eql(correctNewRange);
|
||||
});
|
||||
|
|
|
@ -41,7 +41,8 @@ describe('easysync-inverseRandom', function () {
|
|||
const testInverse = (testId, cs, lines, alines, pool, correctOutput) => {
|
||||
it(`testInverse#${testId}`, async function () {
|
||||
pool = poolOrArray(pool);
|
||||
const str = Changeset.inverse(Changeset.checkRep(cs), lines, alines, pool);
|
||||
Changeset.unpack(cs).validate();
|
||||
const str = Changeset.inverse(cs, lines, alines, pool);
|
||||
expect(str).to.equal(correctOutput);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -204,7 +204,8 @@ describe('easysync-mutations', function () {
|
|||
it(`runMutateAttributionTest#${testId}`, async function () {
|
||||
const p = poolOrArray(attribs);
|
||||
const alines2 = Array.prototype.slice.call(alines);
|
||||
Changeset.mutateAttributionLines(Changeset.checkRep(cs), alines2, p);
|
||||
Changeset.unpack(cs).validate();
|
||||
Changeset.mutateAttributionLines(cs, alines2, p);
|
||||
expect(alines2).to.eql(outCorrect);
|
||||
|
||||
const removeQuestionMarks = (a) => a.replace(/\?/g, '');
|
||||
|
|
|
@ -78,7 +78,8 @@ describe('easysync-other', function () {
|
|||
});
|
||||
|
||||
it('testToSplices', async function () {
|
||||
const cs = Changeset.checkRep('Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk');
|
||||
const cs = 'Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk';
|
||||
Changeset.unpack(cs).validate();
|
||||
const correctSplices = [
|
||||
[5, 8, '123456789'],
|
||||
[9, 17, 'abcdefghijk'],
|
||||
|
@ -112,7 +113,8 @@ describe('easysync-other', function () {
|
|||
const runApplyToAttributionTest = (testId, attribs, cs, inAttr, outCorrect) => {
|
||||
it(`applyToAttribution#${testId}`, async function () {
|
||||
const p = poolOrArray(attribs);
|
||||
const result = Changeset.applyToAttribution(Changeset.checkRep(cs), inAttr, p);
|
||||
Changeset.unpack(cs).validate();
|
||||
const result = Changeset.applyToAttribution(cs, inAttr, p);
|
||||
expect(result).to.equal(outCorrect);
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue