mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
4d70bd664f
These changes were previously merged, not deployed, and reverted. This reverts the revert. This reverts commit a6b8c6c658b33b6eee78b8b99e43308f32211ae2, reversing changes made to 93c98921372eed4244d22fce800716cb27eca299.
438 lines
14 KiB
JavaScript
438 lines
14 KiB
JavaScript
/* 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')
|
|
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
|
|
})()
|
|
})
|
|
})
|
|
})
|