/* eslint-disable camelcase, mocha/no-identical-title, no-return-assign, */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from * DS102: Remove unnecessary code created because of implicit returns * DS202: Simplify dynamic range loops * DS205: Consider reworking code to avoid use of IIFEs * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const text = require("../../../../app/js/sharejs/types/text"); require("chai").should(); const RangesTracker = require("../../../../app/js/RangesTracker"); describe("ShareJS text type", function() { beforeEach(function() { return this.t = "mock-thread-id"; }); describe("transform", function() { describe("insert / insert", function() { it("with an insert before", function() { const dest = []; text._tc(dest, { i: "foo", p: 9 }, { i: "bar", p: 3 }); return dest.should.deep.equal([{ i: "foo", p: 12 }]); }); it("with an insert after", function() { const dest = []; text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 9 }); return dest.should.deep.equal([{ i: "foo", p: 3 }]); }); it("with an insert at the same place with side == 'right'", function() { const dest = []; text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'right'); return dest.should.deep.equal([{ i: "foo", p: 6 }]); }); return it("with an insert at the same place with side == 'left'", function() { const dest = []; text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'left'); return dest.should.deep.equal([{ i: "foo", p: 3 }]); }); }); describe("insert / delete", function() { it("with a delete before", function() { const dest = []; text._tc(dest, { i: "foo", p: 9 }, { d: "bar", p: 3 }); return dest.should.deep.equal([{ i: "foo", p: 6 }]); }); it("with a delete after", function() { const dest = []; text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 9 }); return dest.should.deep.equal([{ i: "foo", p: 3 }]); }); it("with a delete at the same place with side == 'right'", function() { const dest = []; text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'right'); return dest.should.deep.equal([{ i: "foo", p: 3 }]); }); return it("with a delete at the same place with side == 'left'", function() { const dest = []; text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'left'); return dest.should.deep.equal([{ i: "foo", p: 3 }]); }); }); describe("delete / insert", function() { it("with an insert before", function() { const dest = []; text._tc(dest, { d: "foo", p: 9 }, { i: "bar", p: 3 }); return dest.should.deep.equal([{ d: "foo", p: 12 }]); }); it("with an insert after", function() { const dest = []; text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 9 }); return dest.should.deep.equal([{ d: "foo", p: 3 }]); }); it("with an insert at the same place with side == 'right'", function() { const dest = []; text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'right'); return dest.should.deep.equal([{ d: "foo", p: 6 }]); }); it("with an insert at the same place with side == 'left'", function() { const dest = []; text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'left'); return dest.should.deep.equal([{ d: "foo", p: 6 }]); }); return it("with a delete that overlaps the insert location", function() { const dest = []; text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 4 }); return dest.should.deep.equal([{ d: "f", p: 3 }, { d: "oo", p: 6 }]); }); }); describe("delete / delete", function() { it("with a delete before", function() { const dest = []; text._tc(dest, { d: "foo", p: 9 }, { d: "bar", p: 3 }); return dest.should.deep.equal([{ d: "foo", p: 6 }]); }); it("with a delete after", function() { const dest = []; text._tc(dest, { d: "foo", p: 3 }, { d: "bar", p: 9 }); return dest.should.deep.equal([{ d: "foo", p: 3 }]); }); it("with deleting the same content", function() { const dest = []; text._tc(dest, { d: "foo", p: 3 }, { d: "foo", p: 3 }, 'right'); return dest.should.deep.equal([]); }); it("with the delete overlapping before", function() { const dest = []; text._tc(dest, { d: "foobar", p: 3 }, { d: "abcfoo", p: 0 }, 'right'); return dest.should.deep.equal([{ d: "bar", p: 0 }]); }); it("with the delete overlapping after", function() { const dest = []; text._tc(dest, { d: "abcfoo", p: 3 }, { d: "foobar", p: 6 }); return dest.should.deep.equal([{ d: "abc", p: 3 }]); }); it("with the delete overlapping the whole delete", function() { const dest = []; text._tc(dest, { d: "abcfoo123", p: 3 }, { d: "foo", p: 6 }); return dest.should.deep.equal([{ d: "abc123", p: 3 }]); }); return it("with the delete inside the whole delete", function() { const dest = []; text._tc(dest, { d: "foo", p: 6 }, { d: "abcfoo123", p: 3 }); return dest.should.deep.equal([]); }); }); describe("comment / insert", function() { it("with an insert before", function() { const dest = []; text._tc(dest, { c: "foo", p: 9, t: this.t }, { i: "bar", p: 3 }); return dest.should.deep.equal([{ c: "foo", p: 12, t: this.t }]); }); it("with an insert after", function() { const dest = []; text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 9 }); return dest.should.deep.equal([{ c: "foo", p: 3, t: this.t }]); }); it("with an insert at the left edge", function() { const dest = []; text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 3 }); // RangesTracker doesn't inject inserts into comments on edges, so neither should we return dest.should.deep.equal([{ c: "foo", p: 6, t: this.t }]); }); it("with an insert at the right edge", function() { const dest = []; text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 6 }); // RangesTracker doesn't inject inserts into comments on edges, so neither should we return dest.should.deep.equal([{ c: "foo", p: 3, t: this.t }]); }); return it("with an insert in the middle", function() { const dest = []; text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 5 }); return dest.should.deep.equal([{ c: "fobaro", p: 3, t: this.t }]); }); }); describe("comment / delete", function() { it("with a delete before", function() { const dest = []; text._tc(dest, { c: "foo", p: 9, t: this.t }, { d: "bar", p: 3 }); return dest.should.deep.equal([{ c: "foo", p: 6, t: this.t }]); }); it("with a delete after", function() { const dest = []; text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 9 }); return dest.should.deep.equal([{ c: "foo", p: 3, t: this.t }]); }); it("with a delete overlapping the comment content before", function() { const dest = []; text._tc(dest, { c: "foobar", p: 6, t: this.t }, { d: "123foo", p: 3 }); return dest.should.deep.equal([{ c: "bar", p: 3, t: this.t }]); }); it("with a delete overlapping the comment content after", function() { const dest = []; text._tc(dest, { c: "foobar", p: 6, t: this.t }, { d: "bar123", p: 9 }); return dest.should.deep.equal([{ c: "foo", p: 6, t: this.t }]); }); it("with a delete overlapping the comment content in the middle", function() { const dest = []; text._tc(dest, { c: "foo123bar", p: 6, t: this.t }, { d: "123", p: 9 }); return dest.should.deep.equal([{ c: "foobar", p: 6, t: this.t }]); }); return it("with a delete overlapping the whole comment", function() { const dest = []; text._tc(dest, { c: "foo", p: 6, t: this.t }, { d: "123foo456", p: 3 }); return dest.should.deep.equal([{ c: "", p: 3, t: this.t }]); }); }); describe("comment / insert", function() { return it("should not do anything", function() { const dest = []; text._tc(dest, { i: "foo", p: 6 }, { c: "bar", p: 3 }); return dest.should.deep.equal([{ i: "foo", p: 6 }]); }); }); describe("comment / delete", function() { return it("should not do anything", function() { const dest = []; text._tc(dest, { d: "foo", p: 6 }, { c: "bar", p: 3 }); return dest.should.deep.equal([{ d: "foo", p: 6 }]); }); }); return describe("comment / comment", function() { return it("should not do anything", function() { const dest = []; text._tc(dest, { c: "foo", p: 6 }, { c: "bar", p: 3 }); return dest.should.deep.equal([{ c: "foo", p: 6 }]); }); }); }); describe("apply", function() { it("should apply an insert", function() { return text.apply("foo", [{ i: "bar", p: 2 }]).should.equal("fobaro"); }); it("should apply a delete", function() { return text.apply("foo123bar", [{ d: "123", p: 3 }]).should.equal("foobar"); }); it("should do nothing with a comment", function() { return text.apply("foo123bar", [{ c: "123", p: 3 }]).should.equal("foo123bar"); }); it("should throw an error when deleted content does not match", function() { return ((() => text.apply("foo123bar", [{ d: "456", p: 3 }]))).should.throw(Error); }); return it("should throw an error when comment content does not match", function() { return ((() => text.apply("foo123bar", [{ c: "456", p: 3 }]))).should.throw(Error); }); }); return describe("applying ops and comments in different orders", function() { return it("should not matter which op or comment is applied first", function() { let length, p; let asc, end; let asc1, end1; let asc3, end3; const transform = function(op1, op2, side) { const d = []; text._tc(d, op1, op2, side); return d; }; const applySnapshot = (snapshot, op) => text.apply(snapshot, op); const applyRanges = function(rangesTracker, ops) { for (const op of Array.from(ops)) { rangesTracker.applyOp(op, {}); } return rangesTracker; }; const commentsEqual = function(comments1, comments2) { if (comments1.length !== comments2.length) { return false; } comments1.sort((a,b) => { if ((a.offset - b.offset) === 0) { return a.length - b.length; } else { return a.offset - b.offset; } }); comments2.sort((a,b) => { if ((a.offset - b.offset) === 0) { return a.length - b.length; } else { return a.offset - b.offset; } }); for (let i = 0; i < comments1.length; i++) { const comment1 = comments1[i]; const comment2 = comments2[i]; if ((comment1.offset !== comment2.offset) || (comment1.length !== comment2.length)) { return false; } } return true; }; const SNAPSHOT = "123"; const OPS = []; // Insert ops for (p = 0, end = SNAPSHOT.length, asc = end >= 0; asc ? p <= end : p >= end; asc ? p++ : p--) { OPS.push({i: "a", p}); OPS.push({i: "bc", p}); } for (p = 0, end1 = SNAPSHOT.length-1, asc1 = end1 >= 0; asc1 ? p <= end1 : p >= end1; asc1 ? p++ : p--) { var asc2, end2; for (length = 1, end2 = SNAPSHOT.length - p, asc2 = end2 >= 1; asc2 ? length <= end2 : length >= end2; asc2 ? length++ : length--) { OPS.push({d: SNAPSHOT.slice(p, p+length), p}); } } for (p = 0, end3 = SNAPSHOT.length-1, asc3 = end3 >= 0; asc3 ? p <= end3 : p >= end3; asc3 ? p++ : p--) { var asc4, end4; for (length = 1, end4 = SNAPSHOT.length - p, asc4 = end4 >= 1; asc4 ? length <= end4 : length >= end4; asc4 ? length++ : length--) { OPS.push({c: SNAPSHOT.slice(p, p+length), p, t: this.t}); } } return (() => { const result = []; for (var op1 of Array.from(OPS)) { result.push((() => { const result1 = []; for (const op2 of Array.from(OPS)) { const op1_t = transform(op1, op2, "left"); const op2_t = transform(op2, op1, "right"); const rt12 = new RangesTracker(); const snapshot12 = applySnapshot(applySnapshot(SNAPSHOT, [op1]), op2_t); applyRanges(rt12, [op1]); applyRanges(rt12, op2_t); const rt21 = new RangesTracker(); const snapshot21 = applySnapshot(applySnapshot(SNAPSHOT, [op2]), op1_t); applyRanges(rt21, [op2]); applyRanges(rt21, op1_t); if (snapshot12 !== snapshot21) { console.error({op1, op2, op1_t, op2_t, snapshot12, snapshot21}, "Ops are not consistent"); throw new Error("OT is inconsistent"); } if (!commentsEqual(rt12.comments, rt21.comments)) { console.log(rt12.comments); console.log(rt21.comments); console.error({op1, op2, op1_t, op2_t, rt12_comments: rt12.comments, rt21_comments: rt21.comments}, "Comments are not consistent"); throw new Error("OT is inconsistent"); } else { result1.push(undefined); } } return result1; })()); } return result; })(); }); }); });