DiffGeneratorTests.coffee 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. expect = chai.expect
  5. modulePath = "../../../../app/js/DiffGenerator.js"
  6. SandboxedModule = require('sandboxed-module')
  7. describe "DiffGenerator", ->
  8. beforeEach ->
  9. @DiffGenerator = SandboxedModule.require modulePath
  10. @ts = Date.now()
  11. @user_id = "mock-user-id"
  12. @meta = {
  13. start_ts: @ts, end_ts: @ts, user_id: @user_id
  14. }
  15. describe "rewindOp", ->
  16. describe "rewinding an insert", ->
  17. it "should undo the insert", ->
  18. content = "hello world"
  19. rewoundContent = @DiffGenerator.rewindOp content, { p: 6, i: "wo" }
  20. rewoundContent.should.equal "hello rld"
  21. describe "rewinding a delete", ->
  22. it "should undo the delete", ->
  23. content = "hello rld"
  24. rewoundContent = @DiffGenerator.rewindOp content, { p: 6, d: "wo" }
  25. rewoundContent.should.equal "hello world"
  26. describe "with an inconsistent update", ->
  27. it "should throw an error", ->
  28. content = "hello world"
  29. expect( () =>
  30. @DiffGenerator.rewindOp content, { p: 6, i: "foo" }
  31. ).to.throw(@DiffGenerator.ConsistencyError)
  32. describe "rewindUpdate", ->
  33. it "should rewind ops in reverse", ->
  34. content = "aaabbbccc"
  35. update =
  36. op: [{ p: 3, i: "bbb" }, { p: 6, i: "ccc" }]
  37. rewoundContent = @DiffGenerator.rewindUpdate content, update
  38. rewoundContent.should.equal "aaa"
  39. describe "rewindUpdates", ->
  40. it "should rewind updates in reverse", ->
  41. content = "aaabbbccc"
  42. updates = [
  43. { op: [{ p: 3, i: "bbb" }] },
  44. { op: [{ p: 6, i: "ccc" }] }
  45. ]
  46. rewoundContent = @DiffGenerator.rewindUpdates content, updates
  47. rewoundContent.should.equal "aaa"
  48. describe "buildDiff", ->
  49. beforeEach ->
  50. @diff = [ u: "mock-diff" ]
  51. @content = "Hello world"
  52. @updates = [
  53. { i: "mock-update-1" }
  54. { i: "mock-update-2" }
  55. { i: "mock-update-3" }
  56. ]
  57. @DiffGenerator.applyUpdateToDiff = sinon.stub().returns(@diff)
  58. @result = @DiffGenerator.buildDiff(@content, @updates)
  59. it "should return the diff", ->
  60. @result.should.deep.equal @diff
  61. it "should build the content into an initial diff", ->
  62. @DiffGenerator.applyUpdateToDiff
  63. .calledWith([{
  64. u: @content
  65. }], @updates[0])
  66. .should.equal true
  67. it "should apply each update", ->
  68. for update in @updates
  69. @DiffGenerator.applyUpdateToDiff
  70. .calledWith(sinon.match.any, update)
  71. .should.equal true
  72. describe "applyUpdateToDiff", ->
  73. describe "an insert", ->
  74. it "should insert into the middle of (u)nchanged text", ->
  75. diff = @DiffGenerator.applyUpdateToDiff(
  76. [ { u: "foobar" } ],
  77. { op: [{ p: 3, i: "baz" }], meta: @meta }
  78. )
  79. expect(diff).to.deep.equal([
  80. { u: "foo" }
  81. { i: "baz", meta: @meta }
  82. { u: "bar" }
  83. ])
  84. it "should insert into the start of (u)changed text", ->
  85. diff = @DiffGenerator.applyUpdateToDiff(
  86. [ { u: "foobar" } ],
  87. { op: [{ p: 0, i: "baz" }], meta: @meta }
  88. )
  89. expect(diff).to.deep.equal([
  90. { i: "baz", meta: @meta }
  91. { u: "foobar" }
  92. ])
  93. it "should insert into the end of (u)changed text", ->
  94. diff = @DiffGenerator.applyUpdateToDiff(
  95. [ { u: "foobar" } ],
  96. { op: [{ p: 6, i: "baz" }], meta: @meta }
  97. )
  98. expect(diff).to.deep.equal([
  99. { u: "foobar" }
  100. { i: "baz", meta: @meta }
  101. ])
  102. it "should insert into the middle of (i)inserted text", ->
  103. diff = @DiffGenerator.applyUpdateToDiff(
  104. [ { i: "foobar", meta: @meta } ],
  105. { op: [{ p: 3, i: "baz" }], meta: @meta }
  106. )
  107. expect(diff).to.deep.equal([
  108. { i: "foo", meta: @meta }
  109. { i: "baz", meta: @meta }
  110. { i: "bar", meta: @meta }
  111. ])
  112. it "should not count deletes in the running length total", ->
  113. diff = @DiffGenerator.applyUpdateToDiff(
  114. [
  115. { d: "deleted", meta: @meta }
  116. { u: "foobar" }
  117. ],
  118. { op: [{ p: 3, i: "baz" }], meta: @meta }
  119. )
  120. expect(diff).to.deep.equal([
  121. { d: "deleted", meta: @meta }
  122. { u: "foo" }
  123. { i: "baz", meta: @meta }
  124. { u: "bar" }
  125. ])
  126. describe "a delete", ->
  127. describe "deleting unchanged text", ->
  128. it "should delete from the middle of (u)nchanged text", ->
  129. diff = @DiffGenerator.applyUpdateToDiff(
  130. [ { u: "foobazbar" } ],
  131. { op: [{ p: 3, d: "baz" }], meta: @meta }
  132. )
  133. expect(diff).to.deep.equal([
  134. { u: "foo" }
  135. { d: "baz", meta: @meta }
  136. { u: "bar" }
  137. ])
  138. it "should delete from the start of (u)nchanged text", ->
  139. diff = @DiffGenerator.applyUpdateToDiff(
  140. [ { u: "foobazbar" } ],
  141. { op: [{ p: 0, d: "foo" }], meta: @meta }
  142. )
  143. expect(diff).to.deep.equal([
  144. { d: "foo", meta: @meta }
  145. { u: "bazbar" }
  146. ])
  147. it "should delete from the end of (u)nchanged text", ->
  148. diff = @DiffGenerator.applyUpdateToDiff(
  149. [ { u: "foobazbar" } ],
  150. { op: [{ p: 6, d: "bar" }], meta: @meta }
  151. )
  152. expect(diff).to.deep.equal([
  153. { u: "foobaz" }
  154. { d: "bar", meta: @meta }
  155. ])
  156. it "should delete across multiple (u)changed text parts", ->
  157. diff = @DiffGenerator.applyUpdateToDiff(
  158. [ { u: "foo" }, { u: "baz" }, { u: "bar" } ],
  159. { op: [{ p: 2, d: "obazb" }], meta: @meta }
  160. )
  161. expect(diff).to.deep.equal([
  162. { u: "fo" }
  163. { d: "o", meta: @meta }
  164. { d: "baz", meta: @meta }
  165. { d: "b", meta: @meta }
  166. { u: "ar" }
  167. ])
  168. describe "deleting inserts", ->
  169. it "should delete from the middle of (i)nserted text", ->
  170. diff = @DiffGenerator.applyUpdateToDiff(
  171. [ { i: "foobazbar", meta: @meta } ],
  172. { op: [{ p: 3, d: "baz" }], meta: @meta }
  173. )
  174. expect(diff).to.deep.equal([
  175. { i: "foo", meta: @meta }
  176. { i: "bar", meta: @meta }
  177. ])
  178. it "should delete from the start of (u)nchanged text", ->
  179. diff = @DiffGenerator.applyUpdateToDiff(
  180. [ { i: "foobazbar", meta: @meta } ],
  181. { op: [{ p: 0, d: "foo" }], meta: @meta }
  182. )
  183. expect(diff).to.deep.equal([
  184. { i: "bazbar", meta: @meta }
  185. ])
  186. it "should delete from the end of (u)nchanged text", ->
  187. diff = @DiffGenerator.applyUpdateToDiff(
  188. [ { i: "foobazbar", meta: @meta } ],
  189. { op: [{ p: 6, d: "bar" }], meta: @meta }
  190. )
  191. expect(diff).to.deep.equal([
  192. { i: "foobaz", meta: @meta }
  193. ])
  194. it "should delete across multiple (u)changed and (i)nserted text parts", ->
  195. diff = @DiffGenerator.applyUpdateToDiff(
  196. [ { u: "foo" }, { i: "baz", meta: @meta }, { u: "bar" } ],
  197. { op: [{ p: 2, d: "obazb" }], meta: @meta }
  198. )
  199. expect(diff).to.deep.equal([
  200. { u: "fo" }
  201. { d: "o", meta: @meta }
  202. { d: "b", meta: @meta }
  203. { u: "ar" }
  204. ])
  205. describe "deleting over existing deletes", ->
  206. it "should delete across multiple (u)changed and (d)deleted text parts", ->
  207. diff = @DiffGenerator.applyUpdateToDiff(
  208. [ { u: "foo" }, { d: "baz", meta: @meta }, { u: "bar" } ],
  209. { op: [{ p: 2, d: "ob" }], meta: @meta }
  210. )
  211. expect(diff).to.deep.equal([
  212. { u: "fo" }
  213. { d: "o", meta: @meta }
  214. { d: "baz", meta: @meta }
  215. { d: "b", meta: @meta }
  216. { u: "ar" }
  217. ])
  218. describe "deleting when the text doesn't match", ->
  219. it "should throw an error when deleting from the middle of (u)nchanged text", ->
  220. expect(
  221. () => @DiffGenerator.applyUpdateToDiff(
  222. [ { u: "foobazbar" } ],
  223. { op: [{ p: 3, d: "xxx" }], meta: @meta }
  224. )
  225. ).to.throw(@DiffGenerator.ConsistencyError)
  226. it "should throw an error when deleting from the start of (u)nchanged text", ->
  227. expect(
  228. () => @DiffGenerator.applyUpdateToDiff(
  229. [ { u: "foobazbar" } ],
  230. { op: [{ p: 0, d: "xxx" }], meta: @meta }
  231. )
  232. ).to.throw(@DiffGenerator.ConsistencyError)
  233. it "should throw an error when deleting from the end of (u)nchanged text", ->
  234. expect(
  235. () => @DiffGenerator.applyUpdateToDiff(
  236. [ { u: "foobazbar" } ],
  237. { op: [{ p: 6, d: "xxx" }] , meta: @meta }
  238. )
  239. ).to.throw(@DiffGenerator.ConsistencyError)