TextTransformTests.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /* eslint-disable
  2. camelcase,
  3. mocha/no-identical-title,
  4. no-return-assign,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS202: Simplify dynamic range loops
  13. * DS205: Consider reworking code to avoid use of IIFEs
  14. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  15. */
  16. const text = require("../../../../app/js/sharejs/types/text");
  17. require("chai").should();
  18. const RangesTracker = require("../../../../app/js/RangesTracker");
  19. describe("ShareJS text type", function() {
  20. beforeEach(function() {
  21. return this.t = "mock-thread-id";
  22. });
  23. describe("transform", function() {
  24. describe("insert / insert", function() {
  25. it("with an insert before", function() {
  26. const dest = [];
  27. text._tc(dest, { i: "foo", p: 9 }, { i: "bar", p: 3 });
  28. return dest.should.deep.equal([{ i: "foo", p: 12 }]);
  29. });
  30. it("with an insert after", function() {
  31. const dest = [];
  32. text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 9 });
  33. return dest.should.deep.equal([{ i: "foo", p: 3 }]);
  34. });
  35. it("with an insert at the same place with side == 'right'", function() {
  36. const dest = [];
  37. text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'right');
  38. return dest.should.deep.equal([{ i: "foo", p: 6 }]);
  39. });
  40. return it("with an insert at the same place with side == 'left'", function() {
  41. const dest = [];
  42. text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'left');
  43. return dest.should.deep.equal([{ i: "foo", p: 3 }]);
  44. });
  45. });
  46. describe("insert / delete", function() {
  47. it("with a delete before", function() {
  48. const dest = [];
  49. text._tc(dest, { i: "foo", p: 9 }, { d: "bar", p: 3 });
  50. return dest.should.deep.equal([{ i: "foo", p: 6 }]);
  51. });
  52. it("with a delete after", function() {
  53. const dest = [];
  54. text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 9 });
  55. return dest.should.deep.equal([{ i: "foo", p: 3 }]);
  56. });
  57. it("with a delete at the same place with side == 'right'", function() {
  58. const dest = [];
  59. text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'right');
  60. return dest.should.deep.equal([{ i: "foo", p: 3 }]);
  61. });
  62. return it("with a delete at the same place with side == 'left'", function() {
  63. const dest = [];
  64. text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'left');
  65. return dest.should.deep.equal([{ i: "foo", p: 3 }]);
  66. });
  67. });
  68. describe("delete / insert", function() {
  69. it("with an insert before", function() {
  70. const dest = [];
  71. text._tc(dest, { d: "foo", p: 9 }, { i: "bar", p: 3 });
  72. return dest.should.deep.equal([{ d: "foo", p: 12 }]);
  73. });
  74. it("with an insert after", function() {
  75. const dest = [];
  76. text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 9 });
  77. return dest.should.deep.equal([{ d: "foo", p: 3 }]);
  78. });
  79. it("with an insert at the same place with side == 'right'", function() {
  80. const dest = [];
  81. text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'right');
  82. return dest.should.deep.equal([{ d: "foo", p: 6 }]);
  83. });
  84. it("with an insert at the same place with side == 'left'", function() {
  85. const dest = [];
  86. text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'left');
  87. return dest.should.deep.equal([{ d: "foo", p: 6 }]);
  88. });
  89. return it("with a delete that overlaps the insert location", function() {
  90. const dest = [];
  91. text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 4 });
  92. return dest.should.deep.equal([{ d: "f", p: 3 }, { d: "oo", p: 6 }]);
  93. });
  94. });
  95. describe("delete / delete", function() {
  96. it("with a delete before", function() {
  97. const dest = [];
  98. text._tc(dest, { d: "foo", p: 9 }, { d: "bar", p: 3 });
  99. return dest.should.deep.equal([{ d: "foo", p: 6 }]);
  100. });
  101. it("with a delete after", function() {
  102. const dest = [];
  103. text._tc(dest, { d: "foo", p: 3 }, { d: "bar", p: 9 });
  104. return dest.should.deep.equal([{ d: "foo", p: 3 }]);
  105. });
  106. it("with deleting the same content", function() {
  107. const dest = [];
  108. text._tc(dest, { d: "foo", p: 3 }, { d: "foo", p: 3 }, 'right');
  109. return dest.should.deep.equal([]);
  110. });
  111. it("with the delete overlapping before", function() {
  112. const dest = [];
  113. text._tc(dest, { d: "foobar", p: 3 }, { d: "abcfoo", p: 0 }, 'right');
  114. return dest.should.deep.equal([{ d: "bar", p: 0 }]);
  115. });
  116. it("with the delete overlapping after", function() {
  117. const dest = [];
  118. text._tc(dest, { d: "abcfoo", p: 3 }, { d: "foobar", p: 6 });
  119. return dest.should.deep.equal([{ d: "abc", p: 3 }]);
  120. });
  121. it("with the delete overlapping the whole delete", function() {
  122. const dest = [];
  123. text._tc(dest, { d: "abcfoo123", p: 3 }, { d: "foo", p: 6 });
  124. return dest.should.deep.equal([{ d: "abc123", p: 3 }]);
  125. });
  126. return it("with the delete inside the whole delete", function() {
  127. const dest = [];
  128. text._tc(dest, { d: "foo", p: 6 }, { d: "abcfoo123", p: 3 });
  129. return dest.should.deep.equal([]);
  130. });
  131. });
  132. describe("comment / insert", function() {
  133. it("with an insert before", function() {
  134. const dest = [];
  135. text._tc(dest, { c: "foo", p: 9, t: this.t }, { i: "bar", p: 3 });
  136. return dest.should.deep.equal([{ c: "foo", p: 12, t: this.t }]);
  137. });
  138. it("with an insert after", function() {
  139. const dest = [];
  140. text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 9 });
  141. return dest.should.deep.equal([{ c: "foo", p: 3, t: this.t }]);
  142. });
  143. it("with an insert at the left edge", function() {
  144. const dest = [];
  145. text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 3 });
  146. // RangesTracker doesn't inject inserts into comments on edges, so neither should we
  147. return dest.should.deep.equal([{ c: "foo", p: 6, t: this.t }]);
  148. });
  149. it("with an insert at the right edge", function() {
  150. const dest = [];
  151. text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 6 });
  152. // RangesTracker doesn't inject inserts into comments on edges, so neither should we
  153. return dest.should.deep.equal([{ c: "foo", p: 3, t: this.t }]);
  154. });
  155. return it("with an insert in the middle", function() {
  156. const dest = [];
  157. text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 5 });
  158. return dest.should.deep.equal([{ c: "fobaro", p: 3, t: this.t }]);
  159. });
  160. });
  161. describe("comment / delete", function() {
  162. it("with a delete before", function() {
  163. const dest = [];
  164. text._tc(dest, { c: "foo", p: 9, t: this.t }, { d: "bar", p: 3 });
  165. return dest.should.deep.equal([{ c: "foo", p: 6, t: this.t }]);
  166. });
  167. it("with a delete after", function() {
  168. const dest = [];
  169. text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 9 });
  170. return dest.should.deep.equal([{ c: "foo", p: 3, t: this.t }]);
  171. });
  172. it("with a delete overlapping the comment content before", function() {
  173. const dest = [];
  174. text._tc(dest, { c: "foobar", p: 6, t: this.t }, { d: "123foo", p: 3 });
  175. return dest.should.deep.equal([{ c: "bar", p: 3, t: this.t }]);
  176. });
  177. it("with a delete overlapping the comment content after", function() {
  178. const dest = [];
  179. text._tc(dest, { c: "foobar", p: 6, t: this.t }, { d: "bar123", p: 9 });
  180. return dest.should.deep.equal([{ c: "foo", p: 6, t: this.t }]);
  181. });
  182. it("with a delete overlapping the comment content in the middle", function() {
  183. const dest = [];
  184. text._tc(dest, { c: "foo123bar", p: 6, t: this.t }, { d: "123", p: 9 });
  185. return dest.should.deep.equal([{ c: "foobar", p: 6, t: this.t }]);
  186. });
  187. return it("with a delete overlapping the whole comment", function() {
  188. const dest = [];
  189. text._tc(dest, { c: "foo", p: 6, t: this.t }, { d: "123foo456", p: 3 });
  190. return dest.should.deep.equal([{ c: "", p: 3, t: this.t }]);
  191. });
  192. });
  193. describe("comment / insert", function() { return it("should not do anything", function() {
  194. const dest = [];
  195. text._tc(dest, { i: "foo", p: 6 }, { c: "bar", p: 3 });
  196. return dest.should.deep.equal([{ i: "foo", p: 6 }]);
  197. }); });
  198. describe("comment / delete", function() { return it("should not do anything", function() {
  199. const dest = [];
  200. text._tc(dest, { d: "foo", p: 6 }, { c: "bar", p: 3 });
  201. return dest.should.deep.equal([{ d: "foo", p: 6 }]);
  202. }); });
  203. return describe("comment / comment", function() { return it("should not do anything", function() {
  204. const dest = [];
  205. text._tc(dest, { c: "foo", p: 6 }, { c: "bar", p: 3 });
  206. return dest.should.deep.equal([{ c: "foo", p: 6 }]);
  207. }); });
  208. });
  209. describe("apply", function() {
  210. it("should apply an insert", function() { return text.apply("foo", [{ i: "bar", p: 2 }]).should.equal("fobaro"); });
  211. it("should apply a delete", function() { return text.apply("foo123bar", [{ d: "123", p: 3 }]).should.equal("foobar"); });
  212. it("should do nothing with a comment", function() { return text.apply("foo123bar", [{ c: "123", p: 3 }]).should.equal("foo123bar"); });
  213. it("should throw an error when deleted content does not match", function() { return ((() => text.apply("foo123bar", [{ d: "456", p: 3 }]))).should.throw(Error); });
  214. return it("should throw an error when comment content does not match", function() { return ((() => text.apply("foo123bar", [{ c: "456", p: 3 }]))).should.throw(Error); });
  215. });
  216. return describe("applying ops and comments in different orders", function() { return it("should not matter which op or comment is applied first", function() {
  217. let length, p;
  218. let asc, end;
  219. let asc1, end1;
  220. let asc3, end3;
  221. const transform = function(op1, op2, side) {
  222. const d = [];
  223. text._tc(d, op1, op2, side);
  224. return d;
  225. };
  226. const applySnapshot = (snapshot, op) => text.apply(snapshot, op);
  227. const applyRanges = function(rangesTracker, ops) {
  228. for (const op of Array.from(ops)) {
  229. rangesTracker.applyOp(op, {});
  230. }
  231. return rangesTracker;
  232. };
  233. const commentsEqual = function(comments1, comments2) {
  234. if (comments1.length !== comments2.length) { return false; }
  235. comments1.sort((a,b) => {
  236. if ((a.offset - b.offset) === 0) {
  237. return a.length - b.length;
  238. } else {
  239. return a.offset - b.offset;
  240. }
  241. });
  242. comments2.sort((a,b) => {
  243. if ((a.offset - b.offset) === 0) {
  244. return a.length - b.length;
  245. } else {
  246. return a.offset - b.offset;
  247. }
  248. });
  249. for (let i = 0; i < comments1.length; i++) {
  250. const comment1 = comments1[i];
  251. const comment2 = comments2[i];
  252. if ((comment1.offset !== comment2.offset) || (comment1.length !== comment2.length)) {
  253. return false;
  254. }
  255. }
  256. return true;
  257. };
  258. const SNAPSHOT = "123";
  259. const OPS = [];
  260. // Insert ops
  261. for (p = 0, end = SNAPSHOT.length, asc = end >= 0; asc ? p <= end : p >= end; asc ? p++ : p--) {
  262. OPS.push({i: "a", p});
  263. OPS.push({i: "bc", p});
  264. }
  265. for (p = 0, end1 = SNAPSHOT.length-1, asc1 = end1 >= 0; asc1 ? p <= end1 : p >= end1; asc1 ? p++ : p--) {
  266. var asc2, end2;
  267. for (length = 1, end2 = SNAPSHOT.length - p, asc2 = end2 >= 1; asc2 ? length <= end2 : length >= end2; asc2 ? length++ : length--) {
  268. OPS.push({d: SNAPSHOT.slice(p, p+length), p});
  269. }
  270. }
  271. for (p = 0, end3 = SNAPSHOT.length-1, asc3 = end3 >= 0; asc3 ? p <= end3 : p >= end3; asc3 ? p++ : p--) {
  272. var asc4, end4;
  273. for (length = 1, end4 = SNAPSHOT.length - p, asc4 = end4 >= 1; asc4 ? length <= end4 : length >= end4; asc4 ? length++ : length--) {
  274. OPS.push({c: SNAPSHOT.slice(p, p+length), p, t: this.t});
  275. }
  276. }
  277. return (() => {
  278. const result = [];
  279. for (var op1 of Array.from(OPS)) {
  280. result.push((() => {
  281. const result1 = [];
  282. for (const op2 of Array.from(OPS)) {
  283. const op1_t = transform(op1, op2, "left");
  284. const op2_t = transform(op2, op1, "right");
  285. const rt12 = new RangesTracker();
  286. const snapshot12 = applySnapshot(applySnapshot(SNAPSHOT, [op1]), op2_t);
  287. applyRanges(rt12, [op1]);
  288. applyRanges(rt12, op2_t);
  289. const rt21 = new RangesTracker();
  290. const snapshot21 = applySnapshot(applySnapshot(SNAPSHOT, [op2]), op1_t);
  291. applyRanges(rt21, [op2]);
  292. applyRanges(rt21, op1_t);
  293. if (snapshot12 !== snapshot21) {
  294. console.error({op1, op2, op1_t, op2_t, snapshot12, snapshot21}, "Ops are not consistent");
  295. throw new Error("OT is inconsistent");
  296. }
  297. if (!commentsEqual(rt12.comments, rt21.comments)) {
  298. console.log(rt12.comments);
  299. console.log(rt21.comments);
  300. console.error({op1, op2, op1_t, op2_t, rt12_comments: rt12.comments, rt21_comments: rt21.comments}, "Comments are not consistent");
  301. throw new Error("OT is inconsistent");
  302. } else {
  303. result1.push(undefined);
  304. }
  305. }
  306. return result1;
  307. })());
  308. }
  309. return result;
  310. })();
  311. }); });
  312. });