TextTransformTests.js 15 KB

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