TextTransformTests.coffee 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. text = require "../../../../app/js/sharejs/types/text"
  2. require("chai").should()
  3. RangesTracker = require "../../../../app/js/RangesTracker"
  4. describe "ShareJS text type", ->
  5. describe "transform", ->
  6. describe "insert / insert", ->
  7. it "with an insert before", ->
  8. dest = []
  9. text._tc(dest, { i: "foo", p: 9 }, { i: "bar", p: 3 })
  10. dest.should.deep.equal [{ i: "foo", p: 12 }]
  11. it "with an insert after", ->
  12. dest = []
  13. text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 9 })
  14. dest.should.deep.equal [{ i: "foo", p: 3 }]
  15. it "with an insert at the same place with side == 'right'", ->
  16. dest = []
  17. text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'right')
  18. dest.should.deep.equal [{ i: "foo", p: 6 }]
  19. it "with an insert at the same place with side == 'left'", ->
  20. dest = []
  21. text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'left')
  22. dest.should.deep.equal [{ i: "foo", p: 3 }]
  23. describe "insert / delete", ->
  24. it "with a delete before", ->
  25. dest = []
  26. text._tc(dest, { i: "foo", p: 9 }, { d: "bar", p: 3 })
  27. dest.should.deep.equal [{ i: "foo", p: 6 }]
  28. it "with a delete after", ->
  29. dest = []
  30. text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 9 })
  31. dest.should.deep.equal [{ i: "foo", p: 3 }]
  32. it "with a delete at the same place with side == 'right'", ->
  33. dest = []
  34. text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'right')
  35. dest.should.deep.equal [{ i: "foo", p: 3 }]
  36. it "with a delete at the same place with side == 'left'", ->
  37. dest = []
  38. text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'left')
  39. dest.should.deep.equal [{ i: "foo", p: 3 }]
  40. describe "delete / insert", ->
  41. it "with an insert before", ->
  42. dest = []
  43. text._tc(dest, { d: "foo", p: 9 }, { i: "bar", p: 3 })
  44. dest.should.deep.equal [{ d: "foo", p: 12 }]
  45. it "with an insert after", ->
  46. dest = []
  47. text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 9 })
  48. dest.should.deep.equal [{ d: "foo", p: 3 }]
  49. it "with an insert at the same place with side == 'right'", ->
  50. dest = []
  51. text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'right')
  52. dest.should.deep.equal [{ d: "foo", p: 6 }]
  53. it "with an insert at the same place with side == 'left'", ->
  54. dest = []
  55. text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'left')
  56. dest.should.deep.equal [{ d: "foo", p: 6 }]
  57. it "with a delete that overlaps the insert location", ->
  58. dest = []
  59. text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 4 })
  60. dest.should.deep.equal [{ d: "f", p: 3 }, { d: "oo", p: 6 }]
  61. describe "delete / delete", ->
  62. it "with a delete before", ->
  63. dest = []
  64. text._tc(dest, { d: "foo", p: 9 }, { d: "bar", p: 3 })
  65. dest.should.deep.equal [{ d: "foo", p: 6 }]
  66. it "with a delete after", ->
  67. dest = []
  68. text._tc(dest, { d: "foo", p: 3 }, { d: "bar", p: 9 })
  69. dest.should.deep.equal [{ d: "foo", p: 3 }]
  70. it "with deleting the same content", ->
  71. dest = []
  72. text._tc(dest, { d: "foo", p: 3 }, { d: "foo", p: 3 }, 'right')
  73. dest.should.deep.equal []
  74. it "with the delete overlapping before", ->
  75. dest = []
  76. text._tc(dest, { d: "foobar", p: 3 }, { d: "abcfoo", p: 0 }, 'right')
  77. dest.should.deep.equal [{ d: "bar", p: 0 }]
  78. it "with the delete overlapping after", ->
  79. dest = []
  80. text._tc(dest, { d: "abcfoo", p: 3 }, { d: "foobar", p: 6 })
  81. dest.should.deep.equal [{ d: "abc", p: 3 }]
  82. it "with the delete overlapping the whole delete", ->
  83. dest = []
  84. text._tc(dest, { d: "abcfoo123", p: 3 }, { d: "foo", p: 6 })
  85. dest.should.deep.equal [{ d: "abc123", p: 3 }]
  86. it "with the delete inside the whole delete", ->
  87. dest = []
  88. text._tc(dest, { d: "foo", p: 6 }, { d: "abcfoo123", p: 3 })
  89. dest.should.deep.equal []
  90. describe "comment / insert", ->
  91. it "with an insert before", ->
  92. dest = []
  93. text._tc(dest, { c: "foo", p: 9 }, { i: "bar", p: 3 })
  94. dest.should.deep.equal [{ c: "foo", p: 12 }]
  95. it "with an insert after", ->
  96. dest = []
  97. text._tc(dest, { c: "foo", p: 3 }, { i: "bar", p: 9 })
  98. dest.should.deep.equal [{ c: "foo", p: 3 }]
  99. it "with an insert at the left edge", ->
  100. dest = []
  101. text._tc(dest, { c: "foo", p: 3 }, { i: "bar", p: 3 })
  102. # RangesTracker doesn't inject inserts into comments on edges, so neither should we
  103. dest.should.deep.equal [{ c: "foo", p: 6 }]
  104. it "with an insert at the right edge", ->
  105. dest = []
  106. text._tc(dest, { c: "foo", p: 3 }, { i: "bar", p: 6 })
  107. # RangesTracker doesn't inject inserts into comments on edges, so neither should we
  108. dest.should.deep.equal [{ c: "foo", p: 3 }]
  109. it "with an insert in the middle", ->
  110. dest = []
  111. text._tc(dest, { c: "foo", p: 3 }, { i: "bar", p: 5 })
  112. dest.should.deep.equal [{ c: "fobaro", p: 3 }]
  113. describe "comment / delete", ->
  114. it "with a delete before", ->
  115. dest = []
  116. text._tc(dest, { c: "foo", p: 9 }, { d: "bar", p: 3 })
  117. dest.should.deep.equal [{ c: "foo", p: 6 }]
  118. it "with a delete after", ->
  119. dest = []
  120. text._tc(dest, { c: "foo", p: 3 }, { i: "bar", p: 9 })
  121. dest.should.deep.equal [{ c: "foo", p: 3 }]
  122. it "with a delete overlapping the comment content before", ->
  123. dest = []
  124. text._tc(dest, { c: "foobar", p: 6 }, { d: "123foo", p: 3 })
  125. dest.should.deep.equal [{ c: "bar", p: 3 }]
  126. it "with a delete overlapping the comment content after", ->
  127. dest = []
  128. text._tc(dest, { c: "foobar", p: 6 }, { d: "bar123", p: 9 })
  129. dest.should.deep.equal [{ c: "foo", p: 6 }]
  130. it "with a delete overlapping the comment content in the middle", ->
  131. dest = []
  132. text._tc(dest, { c: "foo123bar", p: 6 }, { d: "123", p: 9 })
  133. dest.should.deep.equal [{ c: "foobar", p: 6 }]
  134. it "with a delete overlapping the whole comment", ->
  135. dest = []
  136. text._tc(dest, { c: "foo", p: 6 }, { d: "123foo456", p: 3 })
  137. dest.should.deep.equal [{ c: "", p: 3 }]
  138. describe "comment / insert", ->
  139. it "should not do anything", ->
  140. dest = []
  141. text._tc(dest, { i: "foo", p: 6 }, { c: "bar", p: 3 })
  142. dest.should.deep.equal [{ i: "foo", p: 6 }]
  143. describe "comment / delete", ->
  144. it "should not do anything", ->
  145. dest = []
  146. text._tc(dest, { d: "foo", p: 6 }, { c: "bar", p: 3 })
  147. dest.should.deep.equal [{ d: "foo", p: 6 }]
  148. describe "comment / comment", ->
  149. it "should not do anything", ->
  150. dest = []
  151. text._tc(dest, { c: "foo", p: 6 }, { c: "bar", p: 3 })
  152. dest.should.deep.equal [{ c: "foo", p: 6 }]
  153. describe "apply", ->
  154. it "should apply an insert", ->
  155. text.apply("foo", [{ i: "bar", p: 2 }]).should.equal "fobaro"
  156. it "should apply a delete", ->
  157. text.apply("foo123bar", [{ d: "123", p: 3 }]).should.equal "foobar"
  158. it "should do nothing with a comment", ->
  159. text.apply("foo123bar", [{ c: "123", p: 3 }]).should.equal "foo123bar"
  160. it "should throw an error when deleted content does not match", ->
  161. (() ->
  162. text.apply("foo123bar", [{ d: "456", p: 3 }])
  163. ).should.throw(Error)
  164. it "should throw an error when comment content does not match", ->
  165. (() ->
  166. text.apply("foo123bar", [{ c: "456", p: 3 }])
  167. ).should.throw(Error)
  168. describe "applying ops and comments in different orders", ->
  169. it "should not matter which op or comment is applied first", ->
  170. transform = (op1, op2, side) ->
  171. d = []
  172. text._tc(d, op1, op2, side)
  173. return d
  174. applySnapshot = (snapshot, op) ->
  175. return text.apply(snapshot, op)
  176. applyRanges = (rangesTracker, ops) ->
  177. for op in ops
  178. if op.c?
  179. rangesTracker.addComment(op.p, op.c.length, {})
  180. else
  181. rangesTracker.applyOp(op, {})
  182. return rangesTracker
  183. commentsEqual = (comments1, comments2) ->
  184. return false if comments1.length != comments2.length
  185. comments1.sort (a,b) ->
  186. if a.offset - b.offset == 0
  187. return a.length - b.length
  188. else
  189. return a.offset - b.offset
  190. comments2.sort (a,b) ->
  191. if a.offset - b.offset == 0
  192. return a.length - b.length
  193. else
  194. return a.offset - b.offset
  195. for comment1, i in comments1
  196. comment2 = comments2[i]
  197. if comment1.offset != comment2.offset or comment1.length != comment2.length
  198. return false
  199. return true
  200. SNAPSHOT = "123"
  201. OPS = []
  202. # Insert ops
  203. for p in [0..SNAPSHOT.length]
  204. OPS.push {i: "a", p: p}
  205. OPS.push {i: "bc", p: p}
  206. for p in [0..(SNAPSHOT.length-1)]
  207. for length in [1..(SNAPSHOT.length - p)]
  208. OPS.push {d: SNAPSHOT.slice(p, p+length), p}
  209. for p in [0..(SNAPSHOT.length-1)]
  210. for length in [1..(SNAPSHOT.length - p)]
  211. OPS.push {c: SNAPSHOT.slice(p, p+length), p}
  212. for op1 in OPS
  213. for op2 in OPS
  214. op1_t = transform(op1, op2, "left")
  215. op2_t = transform(op2, op1, "right")
  216. rt12 = new RangesTracker()
  217. snapshot12 = applySnapshot(applySnapshot(SNAPSHOT, [op1]), op2_t)
  218. applyRanges(rt12, [op1])
  219. applyRanges(rt12, op2_t)
  220. rt21 = new RangesTracker()
  221. snapshot21 = applySnapshot(applySnapshot(SNAPSHOT, [op2]), op1_t)
  222. applyRanges(rt21, [op2])
  223. applyRanges(rt21, op1_t)
  224. if snapshot12 != snapshot21
  225. console.error {op1, op2, op1_t, op2_t, snapshot12, snapshot21}, "Ops are not consistent"
  226. throw new Error("OT is inconsistent")
  227. if !commentsEqual(rt12.comments, rt21.comments)
  228. console.log rt12.comments
  229. console.log rt21.comments
  230. console.error {op1, op2, op1_t, op2_t, rt12_comments: rt12.comments, rt21_comments: rt21.comments}, "Comments are not consistent"
  231. throw new Error("OT is inconsistent")