edit_operation.test.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. const { expect } = require('chai')
  2. const EditOperationBuilder = require('../lib/operation/edit_operation_builder')
  3. const TextOperation = require('../lib/operation/text_operation')
  4. const EditOperationTransformer = require('../lib/operation/edit_operation_transformer')
  5. const EditOperation = require('../lib/operation/edit_operation')
  6. const randomTextOperation = require('./support/random_text_operation')
  7. const random = require('./support/random')
  8. const AddCommentOperation = require('../lib/operation/add_comment_operation')
  9. const DeleteCommentOperation = require('../lib/operation/delete_comment_operation')
  10. const SetCommentStateOperation = require('../lib/operation/set_comment_state_operation')
  11. const Range = require('../lib/range')
  12. const EditNoOperation = require('../lib/operation/edit_no_operation')
  13. describe('EditOperation', function () {
  14. it('Cannot be instantiated', function () {
  15. expect(() => new EditOperation()).to.throw(
  16. 'Cannot instantiate abstract class'
  17. )
  18. })
  19. })
  20. describe('EditOperationTransformer', function () {
  21. it('Transforms two TextOperations', function () {
  22. const a = new TextOperation().insert('foo')
  23. const b = new TextOperation().insert('bar')
  24. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  25. expect(aPrime).to.be.an.instanceof(TextOperation)
  26. expect(bPrime).to.be.an.instanceof(TextOperation)
  27. })
  28. it('Transforms TextOperation and EditNoOperation', function () {
  29. const a = new TextOperation().insert('foo')
  30. const b = new EditNoOperation()
  31. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  32. expect(aPrime).to.be.an.instanceof(TextOperation)
  33. expect(bPrime).to.be.an.instanceof(EditNoOperation)
  34. })
  35. it('Transforms two AddCommentOperations with same commentId', function () {
  36. const a = new AddCommentOperation('comm1', [new Range(0, 1)])
  37. const b = new AddCommentOperation('comm1', [new Range(2, 3)])
  38. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  39. expect(aPrime).to.be.an.instanceof(EditNoOperation)
  40. expect(bPrime).to.be.an.instanceof(AddCommentOperation)
  41. })
  42. it('Transforms two AddCommentOperations with different commentId', function () {
  43. const a = new AddCommentOperation('comm1', [new Range(0, 1)])
  44. const b = new AddCommentOperation('comm2', [new Range(2, 3)])
  45. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  46. expect(aPrime).to.be.an.instanceof(AddCommentOperation)
  47. expect(aPrime.toJSON()).to.eql(a.toJSON())
  48. expect(bPrime).to.be.an.instanceof(AddCommentOperation)
  49. expect(bPrime.toJSON()).to.eql(b.toJSON())
  50. })
  51. it('Transforms two DeleteCommentOperations with same commentId', function () {
  52. const a = new DeleteCommentOperation('comm1')
  53. const b = new DeleteCommentOperation('comm1')
  54. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  55. expect(aPrime).to.be.an.instanceof(EditNoOperation)
  56. expect(bPrime).to.be.an.instanceof(EditNoOperation)
  57. })
  58. it('Transforms two DeleteCommentOperations with different commentId', function () {
  59. const a = new DeleteCommentOperation('comm1')
  60. const b = new DeleteCommentOperation('comm2')
  61. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  62. expect(aPrime).to.be.an.instanceof(DeleteCommentOperation)
  63. expect(aPrime.toJSON()).to.eql(a.toJSON())
  64. expect(bPrime).to.be.an.instanceof(DeleteCommentOperation)
  65. expect(bPrime.toJSON()).to.eql(b.toJSON())
  66. })
  67. it('Transforms AddCommentOperation and DeleteCommentOperation with same commentId', function () {
  68. const a = new AddCommentOperation('comm1', [new Range(0, 1)])
  69. const b = new DeleteCommentOperation('comm1')
  70. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  71. expect(aPrime).to.be.an.instanceof(EditNoOperation)
  72. expect(bPrime).to.be.an.instanceof(DeleteCommentOperation)
  73. expect(bPrime.toJSON()).to.eql(b.toJSON())
  74. })
  75. it('Transforms DeleteCommentOperation and AddCommentOperation with same commentId', function () {
  76. const a = new DeleteCommentOperation('comm1')
  77. const b = new AddCommentOperation('comm1', [new Range(0, 1)])
  78. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  79. expect(aPrime).to.be.an.instanceof(DeleteCommentOperation)
  80. expect(aPrime.toJSON()).to.eql(a.toJSON())
  81. expect(bPrime).to.be.an.instanceof(EditNoOperation)
  82. })
  83. it('Transforms AddCommentOperation and TextOperation', function () {
  84. // abc hello[ world] xyz - insert(9, " world")
  85. // abc hello |xyz| - addComment(10, 3, "comment_id")
  86. const a = new TextOperation().retain(9).insert(' world')
  87. const b = new AddCommentOperation('comm1', [new Range(10, 3)])
  88. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  89. expect(aPrime).to.be.an.instanceof(TextOperation)
  90. expect(aPrime.toJSON()).to.eql(a.toJSON())
  91. expect(bPrime).to.be.an.instanceof(AddCommentOperation)
  92. expect(bPrime.toJSON()).to.eql({
  93. commentId: 'comm1',
  94. ranges: [{ pos: 16, length: 3 }],
  95. resolved: false,
  96. })
  97. })
  98. it('Transforms TextOperation and AddCommentOperation', function () {
  99. // abc hello |xyz| - addComment(10, 3, "comment_id")
  100. // abc hello[ world] xyz - insert(9, " world")
  101. const a = new AddCommentOperation('comm1', [new Range(10, 3)])
  102. const b = new TextOperation().retain(9).insert(' world')
  103. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  104. expect(bPrime).to.be.an.instanceof(TextOperation)
  105. expect(bPrime.toJSON()).to.eql(b.toJSON())
  106. expect(aPrime).to.be.an.instanceof(AddCommentOperation)
  107. expect(aPrime.toJSON()).to.eql({
  108. commentId: 'comm1',
  109. ranges: [{ pos: 16, length: 3 }],
  110. resolved: false,
  111. })
  112. })
  113. it('Transforms AddCommentOperation and TextOperation that makes a detached comment', function () {
  114. // [abc hello xyz] - delete(0, 13)
  115. // abc |hello| xyz - addComment(5, 5, "comment_id")
  116. const a = new TextOperation().remove(13)
  117. const b = new AddCommentOperation('comm1', [new Range(5, 5)])
  118. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  119. expect(aPrime).to.be.an.instanceof(TextOperation)
  120. expect(aPrime.toJSON()).to.eql(a.toJSON())
  121. expect(bPrime).to.be.an.instanceof(AddCommentOperation)
  122. expect(bPrime.toJSON()).to.eql({
  123. commentId: 'comm1',
  124. ranges: [],
  125. resolved: false,
  126. })
  127. })
  128. it('Transforms AddCommentOperation and deletion TextOperation', function () {
  129. // abc hell{o xy}z - retain(8).delete(4)
  130. // abc hello |xyz| - addComment(10, 3, "comment_id")
  131. // abc hell|z|
  132. const a = new TextOperation().retain(8).remove(4)
  133. const b = new AddCommentOperation('comm1', [new Range(10, 3)])
  134. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  135. expect(aPrime).to.be.an.instanceof(TextOperation)
  136. expect(aPrime.toJSON()).to.eql(a.toJSON())
  137. expect(bPrime).to.be.an.instanceof(AddCommentOperation)
  138. expect(bPrime.toJSON()).to.eql({
  139. commentId: 'comm1',
  140. ranges: [{ pos: 8, length: 1 }],
  141. resolved: false,
  142. })
  143. })
  144. it('Transforms AddCommentOperation and complex TextOperation', function () {
  145. // [foo ]abc hell{o xy}z - insert(0, "foo ").retain(8).delete(4)
  146. // abc hello |xyz| - addComment(10, 3, "comment_id")
  147. // foo abc hell|z|
  148. const a = new TextOperation().insert('foo ').retain(8).remove(4)
  149. const b = new AddCommentOperation('comm1', [new Range(10, 3)])
  150. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  151. expect(aPrime).to.be.an.instanceof(TextOperation)
  152. expect(aPrime.toJSON()).to.eql(a.toJSON())
  153. expect(bPrime).to.be.an.instanceof(AddCommentOperation)
  154. expect(bPrime.toJSON()).to.eql({
  155. commentId: 'comm1',
  156. ranges: [{ pos: 12, length: 1 }],
  157. resolved: false,
  158. })
  159. })
  160. it('Transforms DeleteCommentOperation and TextOperation', function () {
  161. const a = new TextOperation().retain(9).insert(' world')
  162. const b = new DeleteCommentOperation('comm1')
  163. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  164. expect(aPrime).to.be.an.instanceof(TextOperation)
  165. expect(aPrime.toJSON()).to.eql(a.toJSON())
  166. expect(bPrime).to.be.an.instanceof(DeleteCommentOperation)
  167. expect(bPrime.toJSON()).to.eql(b.toJSON())
  168. })
  169. it('Transforms SetCommentStateOperation and TextOperation', function () {
  170. const a = new TextOperation().retain(9).insert(' world')
  171. const b = new SetCommentStateOperation('comm1', true)
  172. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  173. expect(aPrime).to.be.an.instanceof(TextOperation)
  174. expect(aPrime.toJSON()).to.eql(a.toJSON())
  175. expect(bPrime).to.be.an.instanceof(SetCommentStateOperation)
  176. expect(bPrime.toJSON()).to.eql(b.toJSON())
  177. })
  178. it('Transforms SetCommentStateOperation and AddCommentOperation', function () {
  179. const a = new AddCommentOperation('comm1', [new Range(0, 1)])
  180. const b = new SetCommentStateOperation('comm1', true)
  181. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  182. expect(aPrime).to.be.an.instanceof(AddCommentOperation)
  183. expect(aPrime.toJSON()).to.deep.eql({
  184. commentId: 'comm1',
  185. ranges: [{ pos: 0, length: 1 }],
  186. resolved: true,
  187. })
  188. expect(bPrime).to.be.an.instanceof(SetCommentStateOperation)
  189. expect(bPrime.toJSON()).to.deep.eql(b.toJSON())
  190. })
  191. it('Transforms SetCommentStateOperation and DeleteCommentOperation', function () {
  192. const a = new DeleteCommentOperation('comm1')
  193. const b = new SetCommentStateOperation('comm1', true)
  194. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  195. expect(aPrime).to.be.an.instanceof(DeleteCommentOperation)
  196. expect(aPrime.toJSON()).to.deep.eql(a.toJSON())
  197. expect(bPrime).to.be.an.instanceof(EditNoOperation)
  198. })
  199. it('Transforms SetCommentStateOperation and SetCommentStateOperation', function () {
  200. const a = new SetCommentStateOperation('comm1', false)
  201. const b = new SetCommentStateOperation('comm1', true)
  202. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  203. expect(aPrime.toJSON()).to.deep.eql({
  204. commentId: 'comm1',
  205. resolved: false,
  206. })
  207. expect(bPrime).to.be.an.instanceof(EditNoOperation)
  208. })
  209. it('Transforms two SetCommentStateOperation with different commentId', function () {
  210. const a = new SetCommentStateOperation('comm1', false)
  211. const b = new SetCommentStateOperation('comm2', true)
  212. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  213. expect(aPrime).to.be.an.instanceof(SetCommentStateOperation)
  214. expect(aPrime.toJSON()).to.deep.eql(a.toJSON())
  215. expect(bPrime).to.be.an.instanceof(SetCommentStateOperation)
  216. expect(bPrime.toJSON()).to.deep.eql(b.toJSON())
  217. })
  218. })
  219. describe('EditOperationBuilder', function () {
  220. it('Constructs TextOperation from JSON', function () {
  221. const raw = {
  222. textOperation: [1, 'foo', 3],
  223. }
  224. const op = EditOperationBuilder.fromJSON(raw)
  225. expect(op).to.be.an.instanceof(TextOperation)
  226. expect(op.toJSON()).to.deep.equal(raw)
  227. })
  228. it('Constructs AddCommentOperation from JSON', function () {
  229. const raw = {
  230. commentId: 'comm1',
  231. ranges: [{ pos: 0, length: 1 }],
  232. resolved: false,
  233. }
  234. const op = EditOperationBuilder.fromJSON(raw)
  235. expect(op).to.be.an.instanceof(AddCommentOperation)
  236. expect(op.toJSON()).to.deep.equal(raw)
  237. })
  238. it('Constructs DeleteCommentOperation from JSON', function () {
  239. const raw = {
  240. deleteComment: 'comm1',
  241. }
  242. const op = EditOperationBuilder.fromJSON(raw)
  243. expect(op).to.be.an.instanceof(DeleteCommentOperation)
  244. expect(op.toJSON()).to.deep.equal(raw)
  245. })
  246. it('Constructs SetCommentStateOperation from JSON', function () {
  247. const raw = {
  248. commentId: 'comm1',
  249. resolved: true,
  250. }
  251. const op = EditOperationBuilder.fromJSON(raw)
  252. expect(op).to.be.an.instanceof(SetCommentStateOperation)
  253. expect(op.toJSON()).to.deep.equal(raw)
  254. })
  255. it('Constructs EditNoOperation from JSON', function () {
  256. const raw = { noOp: true }
  257. const op = EditOperationBuilder.fromJSON(raw)
  258. expect(op).to.be.an.instanceof(EditNoOperation)
  259. expect(op.toJSON()).to.deep.equal(raw)
  260. })
  261. it('Throws error for unsupported operation', function () {
  262. const raw = {
  263. unsupportedOperation: {
  264. op: 'foo',
  265. },
  266. }
  267. expect(() => EditOperationBuilder.fromJSON(raw)).to.throw(
  268. 'Unsupported operation in EditOperationBuilder.fromJSON'
  269. )
  270. })
  271. it('Constructs TextOperation from JSON (randomised)', function () {
  272. const str = random.string(50)
  273. const randomOperation = randomTextOperation(str)
  274. const op = EditOperationBuilder.fromJSON(randomOperation.toJSON())
  275. expect(op).to.be.an.instanceof(TextOperation)
  276. expect(op.equals(randomOperation)).to.be.true
  277. })
  278. })