DiffGeneratorTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /* eslint-disable
  2. no-return-assign,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const sinon = require('sinon');
  14. const chai = require('chai');
  15. const should = chai.should();
  16. const { expect } = chai;
  17. const modulePath = "../../../../app/js/DiffGenerator.js";
  18. const SandboxedModule = require('sandboxed-module');
  19. describe("DiffGenerator", function() {
  20. beforeEach(function() {
  21. this.DiffGenerator = SandboxedModule.require(modulePath, { requires: {
  22. "logger-sharelatex": { warn: sinon.stub() }
  23. }
  24. });
  25. this.ts = Date.now();
  26. this.user_id = "mock-user-id";
  27. this.user_id_2 = "mock-user-id-2";
  28. return this.meta = {
  29. start_ts: this.ts, end_ts: this.ts, user_id: this.user_id
  30. };});
  31. describe("rewindOp", function() {
  32. describe("rewinding an insert", function() { return it("should undo the insert", function() {
  33. const content = "hello world";
  34. const rewoundContent = this.DiffGenerator.rewindOp(content, { p: 6, i: "wo" });
  35. return rewoundContent.should.equal("hello rld");
  36. }); }
  37. );
  38. describe("rewinding a delete", function() { return it("should undo the delete", function() {
  39. const content = "hello rld";
  40. const rewoundContent = this.DiffGenerator.rewindOp(content, { p: 6, d: "wo" });
  41. return rewoundContent.should.equal("hello world");
  42. }); }
  43. );
  44. describe("with an inconsistent update", function() { return it("should throw an error", function() {
  45. const content = "hello world";
  46. return expect( () => {
  47. return this.DiffGenerator.rewindOp(content, { p: 6, i: "foo" });
  48. }).to.throw(this.DiffGenerator.ConsistencyError);
  49. }); }
  50. );
  51. return describe("with an update which is beyond the length of the content", function() { return it("should undo the insert as if it were at the end of the content", function() {
  52. const content = "foobar";
  53. const rewoundContent = this.DiffGenerator.rewindOp(content, { p: 4, i: "bar" });
  54. return rewoundContent.should.equal("foo");
  55. }); }
  56. );
  57. });
  58. describe("rewindUpdate", function() { return it("should rewind ops in reverse", function() {
  59. const content = "aaabbbccc";
  60. const update =
  61. {op: [{ p: 3, i: "bbb" }, { p: 6, i: "ccc" }]};
  62. const rewoundContent = this.DiffGenerator.rewindUpdate(content, update);
  63. return rewoundContent.should.equal("aaa");
  64. }); }
  65. );
  66. describe("rewindUpdates", function() { return it("should rewind updates in reverse", function() {
  67. const content = "aaabbbccc";
  68. const updates = [
  69. { op: [{ p: 3, i: "bbb" }] },
  70. { op: [{ p: 6, i: "ccc" }] }
  71. ];
  72. const rewoundContent = this.DiffGenerator.rewindUpdates(content, updates);
  73. return rewoundContent.should.equal("aaa");
  74. }); }
  75. );
  76. describe("buildDiff", function() {
  77. beforeEach(function() {
  78. this.diff = [ {u: "mock-diff"} ];
  79. this.content = "Hello world";
  80. this.updates = [
  81. { i: "mock-update-1" },
  82. { i: "mock-update-2" },
  83. { i: "mock-update-3" }
  84. ];
  85. this.DiffGenerator.applyUpdateToDiff = sinon.stub().returns(this.diff);
  86. this.DiffGenerator.compressDiff = sinon.stub().returns(this.diff);
  87. return this.result = this.DiffGenerator.buildDiff(this.content, this.updates);
  88. });
  89. it("should return the diff", function() {
  90. return this.result.should.deep.equal(this.diff);
  91. });
  92. it("should build the content into an initial diff", function() {
  93. return this.DiffGenerator.applyUpdateToDiff
  94. .calledWith([{
  95. u: this.content
  96. }], this.updates[0])
  97. .should.equal(true);
  98. });
  99. it("should apply each update", function() {
  100. return Array.from(this.updates).map((update) =>
  101. this.DiffGenerator.applyUpdateToDiff
  102. .calledWith(sinon.match.any, update)
  103. .should.equal(true));
  104. });
  105. return it("should compress the diff", function() {
  106. return this.DiffGenerator.compressDiff
  107. .calledWith(this.diff)
  108. .should.equal(true);
  109. });
  110. });
  111. describe("compressDiff", function() {
  112. describe("with adjacent inserts with the same user_id", function() { return it("should create one update with combined meta data and min/max timestamps", function() {
  113. const diff = this.DiffGenerator.compressDiff([
  114. { i: "foo", meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }},
  115. { i: "bar", meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id } }}
  116. ]);
  117. return expect(diff).to.deep.equal([
  118. { i: "foobar", meta: { start_ts: 5, end_ts: 20, user: { id: this.user_id } }}
  119. ]);
  120. }); }
  121. );
  122. describe("with adjacent inserts with different user_ids", function() { return it("should leave the inserts unchanged", function() {
  123. const input = [
  124. { i: "foo", meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }},
  125. { i: "bar", meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id_2 } }}
  126. ];
  127. const output = this.DiffGenerator.compressDiff(input);
  128. return expect(output).to.deep.equal(input);
  129. }); }
  130. );
  131. describe("with adjacent deletes with the same user_id", function() { return it("should create one update with combined meta data and min/max timestamps", function() {
  132. const diff = this.DiffGenerator.compressDiff([
  133. { d: "foo", meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }},
  134. { d: "bar", meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id } }}
  135. ]);
  136. return expect(diff).to.deep.equal([
  137. { d: "foobar", meta: { start_ts: 5, end_ts: 20, user: { id: this.user_id } }}
  138. ]);
  139. }); }
  140. );
  141. return describe("with adjacent deletes with different user_ids", function() { return it("should leave the deletes unchanged", function() {
  142. const input = [
  143. { d: "foo", meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }},
  144. { d: "bar", meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id_2 } }}
  145. ];
  146. const output = this.DiffGenerator.compressDiff(input);
  147. return expect(output).to.deep.equal(input);
  148. }); }
  149. );
  150. });
  151. return describe("applyUpdateToDiff", function() {
  152. describe("an insert", function() {
  153. it("should insert into the middle of (u)nchanged text", function() {
  154. const diff = this.DiffGenerator.applyUpdateToDiff(
  155. [ { u: "foobar" } ],
  156. { op: [{ p: 3, i: "baz" }], meta: this.meta }
  157. );
  158. return expect(diff).to.deep.equal([
  159. { u: "foo" },
  160. { i: "baz", meta: this.meta },
  161. { u: "bar" }
  162. ]);
  163. });
  164. it("should insert into the start of (u)changed text", function() {
  165. const diff = this.DiffGenerator.applyUpdateToDiff(
  166. [ { u: "foobar" } ],
  167. { op: [{ p: 0, i: "baz" }], meta: this.meta }
  168. );
  169. return expect(diff).to.deep.equal([
  170. { i: "baz", meta: this.meta },
  171. { u: "foobar" }
  172. ]);
  173. });
  174. it("should insert into the end of (u)changed text", function() {
  175. const diff = this.DiffGenerator.applyUpdateToDiff(
  176. [ { u: "foobar" } ],
  177. { op: [{ p: 6, i: "baz" }], meta: this.meta }
  178. );
  179. return expect(diff).to.deep.equal([
  180. { u: "foobar" },
  181. { i: "baz", meta: this.meta }
  182. ]);
  183. });
  184. it("should insert into the middle of (i)inserted text", function() {
  185. const diff = this.DiffGenerator.applyUpdateToDiff(
  186. [ { i: "foobar", meta: this.meta } ],
  187. { op: [{ p: 3, i: "baz" }], meta: this.meta }
  188. );
  189. return expect(diff).to.deep.equal([
  190. { i: "foo", meta: this.meta },
  191. { i: "baz", meta: this.meta },
  192. { i: "bar", meta: this.meta }
  193. ]);
  194. });
  195. return it("should not count deletes in the running length total", function() {
  196. const diff = this.DiffGenerator.applyUpdateToDiff(
  197. [
  198. { d: "deleted", meta: this.meta },
  199. { u: "foobar" }
  200. ],
  201. { op: [{ p: 3, i: "baz" }], meta: this.meta }
  202. );
  203. return expect(diff).to.deep.equal([
  204. { d: "deleted", meta: this.meta },
  205. { u: "foo" },
  206. { i: "baz", meta: this.meta },
  207. { u: "bar" }
  208. ]);
  209. });
  210. });
  211. return describe("a delete", function() {
  212. describe("deleting unchanged text", function() {
  213. it("should delete from the middle of (u)nchanged text", function() {
  214. const diff = this.DiffGenerator.applyUpdateToDiff(
  215. [ { u: "foobazbar" } ],
  216. { op: [{ p: 3, d: "baz" }], meta: this.meta }
  217. );
  218. return expect(diff).to.deep.equal([
  219. { u: "foo" },
  220. { d: "baz", meta: this.meta },
  221. { u: "bar" }
  222. ]);
  223. });
  224. it("should delete from the start of (u)nchanged text", function() {
  225. const diff = this.DiffGenerator.applyUpdateToDiff(
  226. [ { u: "foobazbar" } ],
  227. { op: [{ p: 0, d: "foo" }], meta: this.meta }
  228. );
  229. return expect(diff).to.deep.equal([
  230. { d: "foo", meta: this.meta },
  231. { u: "bazbar" }
  232. ]);
  233. });
  234. it("should delete from the end of (u)nchanged text", function() {
  235. const diff = this.DiffGenerator.applyUpdateToDiff(
  236. [ { u: "foobazbar" } ],
  237. { op: [{ p: 6, d: "bar" }], meta: this.meta }
  238. );
  239. return expect(diff).to.deep.equal([
  240. { u: "foobaz" },
  241. { d: "bar", meta: this.meta }
  242. ]);
  243. });
  244. return it("should delete across multiple (u)changed text parts", function() {
  245. const diff = this.DiffGenerator.applyUpdateToDiff(
  246. [ { u: "foo" }, { u: "baz" }, { u: "bar" } ],
  247. { op: [{ p: 2, d: "obazb" }], meta: this.meta }
  248. );
  249. return expect(diff).to.deep.equal([
  250. { u: "fo" },
  251. { d: "o", meta: this.meta },
  252. { d: "baz", meta: this.meta },
  253. { d: "b", meta: this.meta },
  254. { u: "ar" }
  255. ]);
  256. });
  257. });
  258. describe("deleting inserts", function() {
  259. it("should delete from the middle of (i)nserted text", function() {
  260. const diff = this.DiffGenerator.applyUpdateToDiff(
  261. [ { i: "foobazbar", meta: this.meta } ],
  262. { op: [{ p: 3, d: "baz" }], meta: this.meta }
  263. );
  264. return expect(diff).to.deep.equal([
  265. { i: "foo", meta: this.meta },
  266. { i: "bar", meta: this.meta }
  267. ]);
  268. });
  269. it("should delete from the start of (u)nchanged text", function() {
  270. const diff = this.DiffGenerator.applyUpdateToDiff(
  271. [ { i: "foobazbar", meta: this.meta } ],
  272. { op: [{ p: 0, d: "foo" }], meta: this.meta }
  273. );
  274. return expect(diff).to.deep.equal([
  275. { i: "bazbar", meta: this.meta }
  276. ]);
  277. });
  278. it("should delete from the end of (u)nchanged text", function() {
  279. const diff = this.DiffGenerator.applyUpdateToDiff(
  280. [ { i: "foobazbar", meta: this.meta } ],
  281. { op: [{ p: 6, d: "bar" }], meta: this.meta }
  282. );
  283. return expect(diff).to.deep.equal([
  284. { i: "foobaz", meta: this.meta }
  285. ]);
  286. });
  287. return it("should delete across multiple (u)changed and (i)nserted text parts", function() {
  288. const diff = this.DiffGenerator.applyUpdateToDiff(
  289. [ { u: "foo" }, { i: "baz", meta: this.meta }, { u: "bar" } ],
  290. { op: [{ p: 2, d: "obazb" }], meta: this.meta }
  291. );
  292. return expect(diff).to.deep.equal([
  293. { u: "fo" },
  294. { d: "o", meta: this.meta },
  295. { d: "b", meta: this.meta },
  296. { u: "ar" }
  297. ]);
  298. });
  299. });
  300. describe("deleting over existing deletes", function() { return it("should delete across multiple (u)changed and (d)deleted text parts", function() {
  301. const diff = this.DiffGenerator.applyUpdateToDiff(
  302. [ { u: "foo" }, { d: "baz", meta: this.meta }, { u: "bar" } ],
  303. { op: [{ p: 2, d: "ob" }], meta: this.meta }
  304. );
  305. return expect(diff).to.deep.equal([
  306. { u: "fo" },
  307. { d: "o", meta: this.meta },
  308. { d: "baz", meta: this.meta },
  309. { d: "b", meta: this.meta },
  310. { u: "ar" }
  311. ]);
  312. }); }
  313. );
  314. describe("deleting when the text doesn't match", function() {
  315. it("should throw an error when deleting from the middle of (u)nchanged text", function() {
  316. return expect(
  317. () => this.DiffGenerator.applyUpdateToDiff(
  318. [ { u: "foobazbar" } ],
  319. { op: [{ p: 3, d: "xxx" }], meta: this.meta }
  320. )
  321. ).to.throw(this.DiffGenerator.ConsistencyError);
  322. });
  323. it("should throw an error when deleting from the start of (u)nchanged text", function() {
  324. return expect(
  325. () => this.DiffGenerator.applyUpdateToDiff(
  326. [ { u: "foobazbar" } ],
  327. { op: [{ p: 0, d: "xxx" }], meta: this.meta }
  328. )
  329. ).to.throw(this.DiffGenerator.ConsistencyError);
  330. });
  331. return it("should throw an error when deleting from the end of (u)nchanged text", function() {
  332. return expect(
  333. () => this.DiffGenerator.applyUpdateToDiff(
  334. [ { u: "foobazbar" } ],
  335. { op: [{ p: 6, d: "xxx" }] , meta: this.meta }
  336. )
  337. ).to.throw(this.DiffGenerator.ConsistencyError);
  338. });
  339. });
  340. describe("when the last update in the existing diff is a delete", function() { return it("should insert the new update before the delete", function() {
  341. const diff = this.DiffGenerator.applyUpdateToDiff(
  342. [ { u: "foo" }, { d: "bar", meta: this.meta } ],
  343. { op: [{ p: 3, i: "baz" }], meta: this.meta }
  344. );
  345. return expect(diff).to.deep.equal([
  346. { u: "foo" },
  347. { i: "baz", meta: this.meta },
  348. { d: "bar", meta: this.meta }
  349. ]);
  350. }); }
  351. );
  352. return describe("when the only update in the existing diff is a delete", function() { return it("should insert the new update after the delete", function() {
  353. const diff = this.DiffGenerator.applyUpdateToDiff(
  354. [ { d: "bar", meta: this.meta } ],
  355. { op: [{ p: 0, i: "baz" }], meta: this.meta }
  356. );
  357. return expect(diff).to.deep.equal([
  358. { d: "bar", meta: this.meta },
  359. { i: "baz", meta: this.meta }
  360. ]);
  361. }); }
  362. );
  363. });
  364. });
  365. });