mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-02 10:10:47 -05:00
283 lines
9.4 KiB
CoffeeScript
283 lines
9.4 KiB
CoffeeScript
text = require "../../../../app/js/sharejs/types/text"
|
|
require("chai").should()
|
|
RangesTracker = require "../../../../app/js/RangesTracker"
|
|
|
|
describe "ShareJS text type", ->
|
|
beforeEach ->
|
|
@t = "mock-thread-id"
|
|
|
|
describe "transform", ->
|
|
describe "insert / insert", ->
|
|
it "with an insert before", ->
|
|
dest = []
|
|
text._tc(dest, { i: "foo", p: 9 }, { i: "bar", p: 3 })
|
|
dest.should.deep.equal [{ i: "foo", p: 12 }]
|
|
|
|
it "with an insert after", ->
|
|
dest = []
|
|
text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 9 })
|
|
dest.should.deep.equal [{ i: "foo", p: 3 }]
|
|
|
|
it "with an insert at the same place with side == 'right'", ->
|
|
dest = []
|
|
text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'right')
|
|
dest.should.deep.equal [{ i: "foo", p: 6 }]
|
|
|
|
it "with an insert at the same place with side == 'left'", ->
|
|
dest = []
|
|
text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'left')
|
|
dest.should.deep.equal [{ i: "foo", p: 3 }]
|
|
|
|
describe "insert / delete", ->
|
|
it "with a delete before", ->
|
|
dest = []
|
|
text._tc(dest, { i: "foo", p: 9 }, { d: "bar", p: 3 })
|
|
dest.should.deep.equal [{ i: "foo", p: 6 }]
|
|
|
|
it "with a delete after", ->
|
|
dest = []
|
|
text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 9 })
|
|
dest.should.deep.equal [{ i: "foo", p: 3 }]
|
|
|
|
it "with a delete at the same place with side == 'right'", ->
|
|
dest = []
|
|
text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'right')
|
|
dest.should.deep.equal [{ i: "foo", p: 3 }]
|
|
|
|
it "with a delete at the same place with side == 'left'", ->
|
|
dest = []
|
|
|
|
text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'left')
|
|
dest.should.deep.equal [{ i: "foo", p: 3 }]
|
|
|
|
describe "delete / insert", ->
|
|
it "with an insert before", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 9 }, { i: "bar", p: 3 })
|
|
dest.should.deep.equal [{ d: "foo", p: 12 }]
|
|
|
|
it "with an insert after", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 9 })
|
|
dest.should.deep.equal [{ d: "foo", p: 3 }]
|
|
|
|
it "with an insert at the same place with side == 'right'", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'right')
|
|
dest.should.deep.equal [{ d: "foo", p: 6 }]
|
|
|
|
it "with an insert at the same place with side == 'left'", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'left')
|
|
dest.should.deep.equal [{ d: "foo", p: 6 }]
|
|
|
|
it "with a delete that overlaps the insert location", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 4 })
|
|
dest.should.deep.equal [{ d: "f", p: 3 }, { d: "oo", p: 6 }]
|
|
|
|
|
|
describe "delete / delete", ->
|
|
it "with a delete before", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 9 }, { d: "bar", p: 3 })
|
|
dest.should.deep.equal [{ d: "foo", p: 6 }]
|
|
|
|
it "with a delete after", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 3 }, { d: "bar", p: 9 })
|
|
dest.should.deep.equal [{ d: "foo", p: 3 }]
|
|
|
|
it "with deleting the same content", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 3 }, { d: "foo", p: 3 }, 'right')
|
|
dest.should.deep.equal []
|
|
|
|
it "with the delete overlapping before", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foobar", p: 3 }, { d: "abcfoo", p: 0 }, 'right')
|
|
dest.should.deep.equal [{ d: "bar", p: 0 }]
|
|
|
|
it "with the delete overlapping after", ->
|
|
dest = []
|
|
text._tc(dest, { d: "abcfoo", p: 3 }, { d: "foobar", p: 6 })
|
|
dest.should.deep.equal [{ d: "abc", p: 3 }]
|
|
|
|
it "with the delete overlapping the whole delete", ->
|
|
dest = []
|
|
text._tc(dest, { d: "abcfoo123", p: 3 }, { d: "foo", p: 6 })
|
|
dest.should.deep.equal [{ d: "abc123", p: 3 }]
|
|
|
|
it "with the delete inside the whole delete", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 6 }, { d: "abcfoo123", p: 3 })
|
|
dest.should.deep.equal []
|
|
|
|
describe "comment / insert", ->
|
|
it "with an insert before", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo", p: 9, @t }, { i: "bar", p: 3 })
|
|
dest.should.deep.equal [{ c: "foo", p: 12, @t }]
|
|
|
|
it "with an insert after", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo", p: 3, @t }, { i: "bar", p: 9 })
|
|
dest.should.deep.equal [{ c: "foo", p: 3, @t }]
|
|
|
|
it "with an insert at the left edge", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo", p: 3, @t }, { i: "bar", p: 3 })
|
|
# RangesTracker doesn't inject inserts into comments on edges, so neither should we
|
|
dest.should.deep.equal [{ c: "foo", p: 6, @t }]
|
|
|
|
it "with an insert at the right edge", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo", p: 3, @t }, { i: "bar", p: 6 })
|
|
# RangesTracker doesn't inject inserts into comments on edges, so neither should we
|
|
dest.should.deep.equal [{ c: "foo", p: 3, @t }]
|
|
|
|
it "with an insert in the middle", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo", p: 3, @t }, { i: "bar", p: 5 })
|
|
dest.should.deep.equal [{ c: "fobaro", p: 3, @t }]
|
|
|
|
describe "comment / delete", ->
|
|
it "with a delete before", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo", p: 9, @t }, { d: "bar", p: 3 })
|
|
dest.should.deep.equal [{ c: "foo", p: 6, @t }]
|
|
|
|
it "with a delete after", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo", p: 3, @t }, { i: "bar", p: 9 })
|
|
dest.should.deep.equal [{ c: "foo", p: 3, @t }]
|
|
|
|
it "with a delete overlapping the comment content before", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foobar", p: 6, @t }, { d: "123foo", p: 3 })
|
|
dest.should.deep.equal [{ c: "bar", p: 3, @t }]
|
|
|
|
it "with a delete overlapping the comment content after", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foobar", p: 6, @t }, { d: "bar123", p: 9 })
|
|
dest.should.deep.equal [{ c: "foo", p: 6, @t }]
|
|
|
|
it "with a delete overlapping the comment content in the middle", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo123bar", p: 6, @t }, { d: "123", p: 9 })
|
|
dest.should.deep.equal [{ c: "foobar", p: 6, @t }]
|
|
|
|
it "with a delete overlapping the whole comment", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo", p: 6, @t }, { d: "123foo456", p: 3 })
|
|
dest.should.deep.equal [{ c: "", p: 3, @t }]
|
|
|
|
describe "comment / insert", ->
|
|
it "should not do anything", ->
|
|
dest = []
|
|
text._tc(dest, { i: "foo", p: 6 }, { c: "bar", p: 3 })
|
|
dest.should.deep.equal [{ i: "foo", p: 6 }]
|
|
|
|
describe "comment / delete", ->
|
|
it "should not do anything", ->
|
|
dest = []
|
|
text._tc(dest, { d: "foo", p: 6 }, { c: "bar", p: 3 })
|
|
dest.should.deep.equal [{ d: "foo", p: 6 }]
|
|
|
|
describe "comment / comment", ->
|
|
it "should not do anything", ->
|
|
dest = []
|
|
text._tc(dest, { c: "foo", p: 6 }, { c: "bar", p: 3 })
|
|
dest.should.deep.equal [{ c: "foo", p: 6 }]
|
|
|
|
describe "apply", ->
|
|
it "should apply an insert", ->
|
|
text.apply("foo", [{ i: "bar", p: 2 }]).should.equal "fobaro"
|
|
|
|
it "should apply a delete", ->
|
|
text.apply("foo123bar", [{ d: "123", p: 3 }]).should.equal "foobar"
|
|
|
|
it "should do nothing with a comment", ->
|
|
text.apply("foo123bar", [{ c: "123", p: 3 }]).should.equal "foo123bar"
|
|
|
|
it "should throw an error when deleted content does not match", ->
|
|
(() ->
|
|
text.apply("foo123bar", [{ d: "456", p: 3 }])
|
|
).should.throw(Error)
|
|
|
|
it "should throw an error when comment content does not match", ->
|
|
(() ->
|
|
text.apply("foo123bar", [{ c: "456", p: 3 }])
|
|
).should.throw(Error)
|
|
|
|
describe "applying ops and comments in different orders", ->
|
|
it "should not matter which op or comment is applied first", ->
|
|
transform = (op1, op2, side) ->
|
|
d = []
|
|
text._tc(d, op1, op2, side)
|
|
return d
|
|
|
|
applySnapshot = (snapshot, op) ->
|
|
return text.apply(snapshot, op)
|
|
|
|
applyRanges = (rangesTracker, ops) ->
|
|
for op in ops
|
|
rangesTracker.applyOp(op, {})
|
|
return rangesTracker
|
|
|
|
commentsEqual = (comments1, comments2) ->
|
|
return false if comments1.length != comments2.length
|
|
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 comment1, i in comments1
|
|
comment2 = comments2[i]
|
|
if comment1.offset != comment2.offset or comment1.length != comment2.length
|
|
return false
|
|
return true
|
|
|
|
SNAPSHOT = "123"
|
|
|
|
OPS = []
|
|
# Insert ops
|
|
for p in [0..SNAPSHOT.length]
|
|
OPS.push {i: "a", p: p}
|
|
OPS.push {i: "bc", p: p}
|
|
for p in [0..(SNAPSHOT.length-1)]
|
|
for length in [1..(SNAPSHOT.length - p)]
|
|
OPS.push {d: SNAPSHOT.slice(p, p+length), p}
|
|
for p in [0..(SNAPSHOT.length-1)]
|
|
for length in [1..(SNAPSHOT.length - p)]
|
|
OPS.push {c: SNAPSHOT.slice(p, p+length), p, @t}
|
|
|
|
for op1 in OPS
|
|
for op2 in OPS
|
|
op1_t = transform(op1, op2, "left")
|
|
op2_t = transform(op2, op1, "right")
|
|
|
|
rt12 = new RangesTracker()
|
|
snapshot12 = applySnapshot(applySnapshot(SNAPSHOT, [op1]), op2_t)
|
|
applyRanges(rt12, [op1])
|
|
applyRanges(rt12, op2_t)
|
|
|
|
rt21 = new RangesTracker()
|
|
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")
|