TextTransformTests.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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. const RangesTracker = require('../../../../app/js/RangesTracker')
  18. describe('ShareJS text type', function () {
  19. beforeEach(function () {
  20. return (this.t = 'mock-thread-id')
  21. })
  22. describe('transform', function () {
  23. describe('insert / insert', function () {
  24. it('with an insert before', function () {
  25. const dest = []
  26. text._tc(dest, { i: 'foo', p: 9 }, { i: 'bar', p: 3 })
  27. return dest.should.deep.equal([{ i: 'foo', p: 12 }])
  28. })
  29. it('with an insert after', function () {
  30. const dest = []
  31. text._tc(dest, { i: 'foo', p: 3 }, { i: 'bar', p: 9 })
  32. return dest.should.deep.equal([{ i: 'foo', p: 3 }])
  33. })
  34. it("with an insert at the same place with side == 'right'", function () {
  35. const dest = []
  36. text._tc(dest, { i: 'foo', p: 3 }, { i: 'bar', p: 3 }, 'right')
  37. return dest.should.deep.equal([{ i: 'foo', p: 6 }])
  38. })
  39. return it("with an insert at the same place with side == 'left'", function () {
  40. const dest = []
  41. text._tc(dest, { i: 'foo', p: 3 }, { i: 'bar', p: 3 }, 'left')
  42. return dest.should.deep.equal([{ i: 'foo', p: 3 }])
  43. })
  44. })
  45. describe('insert / delete', function () {
  46. it('with a delete before', function () {
  47. const dest = []
  48. text._tc(dest, { i: 'foo', p: 9 }, { d: 'bar', p: 3 })
  49. return dest.should.deep.equal([{ i: 'foo', p: 6 }])
  50. })
  51. it('with a delete after', function () {
  52. const dest = []
  53. text._tc(dest, { i: 'foo', p: 3 }, { d: 'bar', p: 9 })
  54. return dest.should.deep.equal([{ i: 'foo', p: 3 }])
  55. })
  56. it("with a delete at the same place with side == 'right'", function () {
  57. const dest = []
  58. text._tc(dest, { i: 'foo', p: 3 }, { d: 'bar', p: 3 }, 'right')
  59. return dest.should.deep.equal([{ i: 'foo', p: 3 }])
  60. })
  61. return it("with a delete at the same place with side == 'left'", function () {
  62. const dest = []
  63. text._tc(dest, { i: 'foo', p: 3 }, { d: 'bar', p: 3 }, 'left')
  64. return dest.should.deep.equal([{ i: 'foo', p: 3 }])
  65. })
  66. })
  67. describe('delete / insert', function () {
  68. it('with an insert before', function () {
  69. const dest = []
  70. text._tc(dest, { d: 'foo', p: 9 }, { i: 'bar', p: 3 })
  71. return dest.should.deep.equal([{ d: 'foo', p: 12 }])
  72. })
  73. it('with an insert after', function () {
  74. const dest = []
  75. text._tc(dest, { d: 'foo', p: 3 }, { i: 'bar', p: 9 })
  76. return dest.should.deep.equal([{ d: 'foo', p: 3 }])
  77. })
  78. it("with an insert at the same place with side == 'right'", function () {
  79. const dest = []
  80. text._tc(dest, { d: 'foo', p: 3 }, { i: 'bar', p: 3 }, 'right')
  81. return dest.should.deep.equal([{ d: 'foo', p: 6 }])
  82. })
  83. it("with an insert at the same place with side == 'left'", function () {
  84. const dest = []
  85. text._tc(dest, { d: 'foo', p: 3 }, { i: 'bar', p: 3 }, 'left')
  86. return dest.should.deep.equal([{ d: 'foo', p: 6 }])
  87. })
  88. return it('with a delete that overlaps the insert location', function () {
  89. const dest = []
  90. text._tc(dest, { d: 'foo', p: 3 }, { i: 'bar', p: 4 })
  91. return dest.should.deep.equal([
  92. { d: 'f', p: 3 },
  93. { d: 'oo', p: 6 },
  94. ])
  95. })
  96. })
  97. describe('delete / delete', function () {
  98. it('with a delete before', function () {
  99. const dest = []
  100. text._tc(dest, { d: 'foo', p: 9 }, { d: 'bar', p: 3 })
  101. return dest.should.deep.equal([{ d: 'foo', p: 6 }])
  102. })
  103. it('with a delete after', function () {
  104. const dest = []
  105. text._tc(dest, { d: 'foo', p: 3 }, { d: 'bar', p: 9 })
  106. return dest.should.deep.equal([{ d: 'foo', p: 3 }])
  107. })
  108. it('with deleting the same content', function () {
  109. const dest = []
  110. text._tc(dest, { d: 'foo', p: 3 }, { d: 'foo', p: 3 }, 'right')
  111. return dest.should.deep.equal([])
  112. })
  113. it('with the delete overlapping before', function () {
  114. const dest = []
  115. text._tc(dest, { d: 'foobar', p: 3 }, { d: 'abcfoo', p: 0 }, 'right')
  116. return dest.should.deep.equal([{ d: 'bar', p: 0 }])
  117. })
  118. it('with the delete overlapping after', function () {
  119. const dest = []
  120. text._tc(dest, { d: 'abcfoo', p: 3 }, { d: 'foobar', p: 6 })
  121. return dest.should.deep.equal([{ d: 'abc', p: 3 }])
  122. })
  123. it('with the delete overlapping the whole delete', function () {
  124. const dest = []
  125. text._tc(dest, { d: 'abcfoo123', p: 3 }, { d: 'foo', p: 6 })
  126. return dest.should.deep.equal([{ d: 'abc123', p: 3 }])
  127. })
  128. return it('with the delete inside the whole delete', function () {
  129. const dest = []
  130. text._tc(dest, { d: 'foo', p: 6 }, { d: 'abcfoo123', p: 3 })
  131. return dest.should.deep.equal([])
  132. })
  133. })
  134. describe('comment / insert', function () {
  135. it('with an insert before', function () {
  136. const dest = []
  137. text._tc(dest, { c: 'foo', p: 9, t: this.t }, { i: 'bar', p: 3 })
  138. return dest.should.deep.equal([{ c: 'foo', p: 12, t: this.t }])
  139. })
  140. it('with an insert after', function () {
  141. const dest = []
  142. text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 9 })
  143. return dest.should.deep.equal([{ c: 'foo', p: 3, t: this.t }])
  144. })
  145. it('with an insert at the left edge', function () {
  146. const dest = []
  147. text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 3 })
  148. // RangesTracker doesn't inject inserts into comments on edges, so neither should we
  149. return dest.should.deep.equal([{ c: 'foo', p: 6, t: this.t }])
  150. })
  151. it('with an insert at the right edge', function () {
  152. const dest = []
  153. text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 6 })
  154. // RangesTracker doesn't inject inserts into comments on edges, so neither should we
  155. return dest.should.deep.equal([{ c: 'foo', p: 3, t: this.t }])
  156. })
  157. return it('with an insert in the middle', function () {
  158. const dest = []
  159. text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 5 })
  160. return dest.should.deep.equal([{ c: 'fobaro', p: 3, t: this.t }])
  161. })
  162. })
  163. describe('comment / delete', function () {
  164. it('with a delete before', function () {
  165. const dest = []
  166. text._tc(dest, { c: 'foo', p: 9, t: this.t }, { d: 'bar', p: 3 })
  167. return dest.should.deep.equal([{ c: 'foo', p: 6, t: this.t }])
  168. })
  169. it('with a delete after', function () {
  170. const dest = []
  171. text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 9 })
  172. return dest.should.deep.equal([{ c: 'foo', p: 3, t: this.t }])
  173. })
  174. it('with a delete overlapping the comment content before', function () {
  175. const dest = []
  176. text._tc(dest, { c: 'foobar', p: 6, t: this.t }, { d: '123foo', p: 3 })
  177. return dest.should.deep.equal([{ c: 'bar', p: 3, t: this.t }])
  178. })
  179. it('with a delete overlapping the comment content after', function () {
  180. const dest = []
  181. text._tc(dest, { c: 'foobar', p: 6, t: this.t }, { d: 'bar123', p: 9 })
  182. return dest.should.deep.equal([{ c: 'foo', p: 6, t: this.t }])
  183. })
  184. it('with a delete overlapping the comment content in the middle', function () {
  185. const dest = []
  186. text._tc(dest, { c: 'foo123bar', p: 6, t: this.t }, { d: '123', p: 9 })
  187. return dest.should.deep.equal([{ c: 'foobar', p: 6, t: this.t }])
  188. })
  189. return it('with a delete overlapping the whole comment', function () {
  190. const dest = []
  191. text._tc(dest, { c: 'foo', p: 6, t: this.t }, { d: '123foo456', p: 3 })
  192. return dest.should.deep.equal([{ c: '', p: 3, t: this.t }])
  193. })
  194. })
  195. describe('comment / insert', function () {
  196. return it('should not do anything', function () {
  197. const dest = []
  198. text._tc(dest, { i: 'foo', p: 6 }, { c: 'bar', p: 3 })
  199. return dest.should.deep.equal([{ i: 'foo', p: 6 }])
  200. })
  201. })
  202. describe('comment / delete', function () {
  203. return it('should not do anything', function () {
  204. const dest = []
  205. text._tc(dest, { d: 'foo', p: 6 }, { c: 'bar', p: 3 })
  206. return dest.should.deep.equal([{ d: 'foo', p: 6 }])
  207. })
  208. })
  209. return describe('comment / comment', function () {
  210. return it('should not do anything', function () {
  211. const dest = []
  212. text._tc(dest, { c: 'foo', p: 6 }, { c: 'bar', p: 3 })
  213. return dest.should.deep.equal([{ c: 'foo', p: 6 }])
  214. })
  215. })
  216. })
  217. describe('apply', function () {
  218. it('should apply an insert', function () {
  219. return text.apply('foo', [{ i: 'bar', p: 2 }]).should.equal('fobaro')
  220. })
  221. it('should apply a delete', function () {
  222. return text
  223. .apply('foo123bar', [{ d: '123', p: 3 }])
  224. .should.equal('foobar')
  225. })
  226. it('should do nothing with a comment', function () {
  227. return text
  228. .apply('foo123bar', [{ c: '123', p: 3 }])
  229. .should.equal('foo123bar')
  230. })
  231. it('should throw an error when deleted content does not match', function () {
  232. return (() => text.apply('foo123bar', [{ d: '456', p: 3 }])).should.throw(
  233. Error
  234. )
  235. })
  236. return it('should throw an error when comment content does not match', function () {
  237. return (() => text.apply('foo123bar', [{ c: '456', p: 3 }])).should.throw(
  238. Error
  239. )
  240. })
  241. })
  242. return describe('applying ops and comments in different orders', function () {
  243. return it('should not matter which op or comment is applied first', function () {
  244. let length, p
  245. let asc, end
  246. let asc1, end1
  247. let asc3, end3
  248. const transform = function (op1, op2, side) {
  249. const d = []
  250. text._tc(d, op1, op2, side)
  251. return d
  252. }
  253. const applySnapshot = (snapshot, op) => text.apply(snapshot, op)
  254. const applyRanges = function (rangesTracker, ops) {
  255. for (const op of Array.from(ops)) {
  256. rangesTracker.applyOp(op, {})
  257. }
  258. return rangesTracker
  259. }
  260. const commentsEqual = function (comments1, comments2) {
  261. if (comments1.length !== comments2.length) {
  262. return false
  263. }
  264. comments1.sort((a, b) => {
  265. if (a.offset - b.offset === 0) {
  266. return a.length - b.length
  267. } else {
  268. return a.offset - b.offset
  269. }
  270. })
  271. comments2.sort((a, b) => {
  272. if (a.offset - b.offset === 0) {
  273. return a.length - b.length
  274. } else {
  275. return a.offset - b.offset
  276. }
  277. })
  278. for (let i = 0; i < comments1.length; i++) {
  279. const comment1 = comments1[i]
  280. const comment2 = comments2[i]
  281. if (
  282. comment1.offset !== comment2.offset ||
  283. comment1.length !== comment2.length
  284. ) {
  285. return false
  286. }
  287. }
  288. return true
  289. }
  290. const SNAPSHOT = '123'
  291. const OPS = []
  292. // Insert ops
  293. for (
  294. p = 0, end = SNAPSHOT.length, asc = end >= 0;
  295. asc ? p <= end : p >= end;
  296. asc ? p++ : p--
  297. ) {
  298. OPS.push({ i: 'a', p })
  299. OPS.push({ i: 'bc', p })
  300. }
  301. for (
  302. p = 0, end1 = SNAPSHOT.length - 1, asc1 = end1 >= 0;
  303. asc1 ? p <= end1 : p >= end1;
  304. asc1 ? p++ : p--
  305. ) {
  306. let asc2, end2
  307. for (
  308. length = 1, end2 = SNAPSHOT.length - p, asc2 = end2 >= 1;
  309. asc2 ? length <= end2 : length >= end2;
  310. asc2 ? length++ : length--
  311. ) {
  312. OPS.push({ d: SNAPSHOT.slice(p, p + length), p })
  313. }
  314. }
  315. for (
  316. p = 0, end3 = SNAPSHOT.length - 1, asc3 = end3 >= 0;
  317. asc3 ? p <= end3 : p >= end3;
  318. asc3 ? p++ : p--
  319. ) {
  320. let asc4, end4
  321. for (
  322. length = 1, end4 = SNAPSHOT.length - p, asc4 = end4 >= 1;
  323. asc4 ? length <= end4 : length >= end4;
  324. asc4 ? length++ : length--
  325. ) {
  326. OPS.push({ c: SNAPSHOT.slice(p, p + length), p, t: this.t })
  327. }
  328. }
  329. return (() => {
  330. const result = []
  331. for (const op1 of Array.from(OPS)) {
  332. result.push(
  333. (() => {
  334. const result1 = []
  335. for (const op2 of Array.from(OPS)) {
  336. const op1_t = transform(op1, op2, 'left')
  337. const op2_t = transform(op2, op1, 'right')
  338. const rt12 = new RangesTracker()
  339. const snapshot12 = applySnapshot(
  340. applySnapshot(SNAPSHOT, [op1]),
  341. op2_t
  342. )
  343. applyRanges(rt12, [op1])
  344. applyRanges(rt12, op2_t)
  345. const rt21 = new RangesTracker()
  346. const snapshot21 = applySnapshot(
  347. applySnapshot(SNAPSHOT, [op2]),
  348. op1_t
  349. )
  350. applyRanges(rt21, [op2])
  351. applyRanges(rt21, op1_t)
  352. if (snapshot12 !== snapshot21) {
  353. console.error(
  354. { op1, op2, op1_t, op2_t, snapshot12, snapshot21 },
  355. 'Ops are not consistent'
  356. )
  357. throw new Error('OT is inconsistent')
  358. }
  359. if (!commentsEqual(rt12.comments, rt21.comments)) {
  360. console.log(rt12.comments)
  361. console.log(rt21.comments)
  362. console.error(
  363. {
  364. op1,
  365. op2,
  366. op1_t,
  367. op2_t,
  368. rt12_comments: rt12.comments,
  369. rt21_comments: rt21.comments,
  370. },
  371. 'Comments are not consistent'
  372. )
  373. throw new Error('OT is inconsistent')
  374. } else {
  375. result1.push(undefined)
  376. }
  377. }
  378. return result1
  379. })()
  380. )
  381. }
  382. return result
  383. })()
  384. })
  385. })
  386. })