edit_operation.test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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. })
  96. })
  97. it('Transforms TextOperation and AddCommentOperation', function () {
  98. // abc hello |xyz| - addComment(10, 3, "comment_id")
  99. // abc hello[ world] xyz - insert(9, " world")
  100. const a = new AddCommentOperation('comm1', [new Range(10, 3)])
  101. const b = new TextOperation().retain(9).insert(' world')
  102. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  103. expect(bPrime).to.be.an.instanceof(TextOperation)
  104. expect(bPrime.toJSON()).to.eql(b.toJSON())
  105. expect(aPrime).to.be.an.instanceof(AddCommentOperation)
  106. expect(aPrime.toJSON()).to.eql({
  107. commentId: 'comm1',
  108. ranges: [{ pos: 16, length: 3 }],
  109. })
  110. })
  111. it('Transforms AddCommentOperation and TextOperation that makes a detached comment', function () {
  112. // [abc hello xyz] - delete(0, 13)
  113. // abc |hello| xyz - addComment(5, 5, "comment_id")
  114. const a = new TextOperation().remove(13)
  115. const b = new AddCommentOperation('comm1', [new Range(5, 5)])
  116. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  117. expect(aPrime).to.be.an.instanceof(TextOperation)
  118. expect(aPrime.toJSON()).to.eql(a.toJSON())
  119. expect(bPrime).to.be.an.instanceof(AddCommentOperation)
  120. expect(bPrime.toJSON()).to.eql({
  121. commentId: 'comm1',
  122. ranges: [],
  123. })
  124. })
  125. it('Transforms AddCommentOperation and deletion TextOperation', function () {
  126. // abc hell{o xy}z - retain(8).delete(4)
  127. // abc hello |xyz| - addComment(10, 3, "comment_id")
  128. // abc hell|z|
  129. const a = new TextOperation().retain(8).remove(4)
  130. const b = new AddCommentOperation('comm1', [new Range(10, 3)])
  131. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  132. expect(aPrime).to.be.an.instanceof(TextOperation)
  133. expect(aPrime.toJSON()).to.eql(a.toJSON())
  134. expect(bPrime).to.be.an.instanceof(AddCommentOperation)
  135. expect(bPrime.toJSON()).to.eql({
  136. commentId: 'comm1',
  137. ranges: [{ pos: 8, length: 1 }],
  138. })
  139. })
  140. it('Transforms AddCommentOperation and complex TextOperation', function () {
  141. // [foo ]abc hell{o xy}z - insert(0, "foo ").retain(8).delete(4)
  142. // abc hello |xyz| - addComment(10, 3, "comment_id")
  143. // foo abc hell|z|
  144. const a = new TextOperation().insert('foo ').retain(8).remove(4)
  145. const b = new AddCommentOperation('comm1', [new Range(10, 3)])
  146. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  147. expect(aPrime).to.be.an.instanceof(TextOperation)
  148. expect(aPrime.toJSON()).to.eql(a.toJSON())
  149. expect(bPrime).to.be.an.instanceof(AddCommentOperation)
  150. expect(bPrime.toJSON()).to.eql({
  151. commentId: 'comm1',
  152. ranges: [{ pos: 12, length: 1 }],
  153. })
  154. })
  155. it('Transforms DeleteCommentOperation and TextOperation', function () {
  156. const a = new TextOperation().retain(9).insert(' world')
  157. const b = new DeleteCommentOperation('comm1')
  158. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  159. expect(aPrime).to.be.an.instanceof(TextOperation)
  160. expect(aPrime.toJSON()).to.eql(a.toJSON())
  161. expect(bPrime).to.be.an.instanceof(DeleteCommentOperation)
  162. expect(bPrime.toJSON()).to.eql(b.toJSON())
  163. })
  164. it('Transforms SetCommentStateOperation and TextOperation', function () {
  165. const a = new TextOperation().retain(9).insert(' world')
  166. const b = new SetCommentStateOperation('comm1', true)
  167. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  168. expect(aPrime).to.be.an.instanceof(TextOperation)
  169. expect(aPrime.toJSON()).to.eql(a.toJSON())
  170. expect(bPrime).to.be.an.instanceof(SetCommentStateOperation)
  171. expect(bPrime.toJSON()).to.eql(b.toJSON())
  172. })
  173. it('Transforms SetCommentStateOperation and AddCommentOperation', function () {
  174. const a = new AddCommentOperation('comm1', [new Range(0, 1)])
  175. const b = new SetCommentStateOperation('comm1', true)
  176. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  177. expect(aPrime).to.be.an.instanceof(AddCommentOperation)
  178. expect(aPrime.toJSON()).to.deep.eql({
  179. commentId: 'comm1',
  180. ranges: [{ pos: 0, length: 1 }],
  181. resolved: true,
  182. })
  183. expect(bPrime).to.be.an.instanceof(SetCommentStateOperation)
  184. expect(bPrime.toJSON()).to.deep.eql(b.toJSON())
  185. })
  186. it('Transforms SetCommentStateOperation and DeleteCommentOperation', function () {
  187. const a = new DeleteCommentOperation('comm1')
  188. const b = new SetCommentStateOperation('comm1', true)
  189. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  190. expect(aPrime).to.be.an.instanceof(DeleteCommentOperation)
  191. expect(aPrime.toJSON()).to.deep.eql(a.toJSON())
  192. expect(bPrime).to.be.an.instanceof(EditNoOperation)
  193. })
  194. it('Transforms SetCommentStateOperation and SetCommentStateOperation', function () {
  195. const a = new SetCommentStateOperation('comm1', false)
  196. const b = new SetCommentStateOperation('comm1', true)
  197. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  198. expect(aPrime.toJSON()).to.deep.eql({
  199. commentId: 'comm1',
  200. resolved: false,
  201. })
  202. expect(bPrime).to.be.an.instanceof(EditNoOperation)
  203. })
  204. it('Transforms two SetCommentStateOperation with different commentId', function () {
  205. const a = new SetCommentStateOperation('comm1', false)
  206. const b = new SetCommentStateOperation('comm2', true)
  207. const [aPrime, bPrime] = EditOperationTransformer.transform(a, b)
  208. expect(aPrime).to.be.an.instanceof(SetCommentStateOperation)
  209. expect(aPrime.toJSON()).to.deep.eql(a.toJSON())
  210. expect(bPrime).to.be.an.instanceof(SetCommentStateOperation)
  211. expect(bPrime.toJSON()).to.deep.eql(b.toJSON())
  212. })
  213. })
  214. describe('EditOperationBuilder', function () {
  215. it('Constructs TextOperation from JSON', function () {
  216. const raw = {
  217. textOperation: [1, 'foo', 3],
  218. }
  219. const op = EditOperationBuilder.fromJSON(raw)
  220. expect(op).to.be.an.instanceof(TextOperation)
  221. expect(op.toJSON()).to.deep.equal(raw)
  222. })
  223. it('Constructs AddCommentOperation from JSON', function () {
  224. const raw = {
  225. commentId: 'comm1',
  226. ranges: [{ pos: 0, length: 1 }],
  227. }
  228. const op = EditOperationBuilder.fromJSON(raw)
  229. expect(op).to.be.an.instanceof(AddCommentOperation)
  230. expect(op.toJSON()).to.deep.equal(raw)
  231. })
  232. it('Constructs DeleteCommentOperation from JSON', function () {
  233. const raw = {
  234. deleteComment: 'comm1',
  235. }
  236. const op = EditOperationBuilder.fromJSON(raw)
  237. expect(op).to.be.an.instanceof(DeleteCommentOperation)
  238. expect(op.toJSON()).to.deep.equal(raw)
  239. })
  240. it('Constructs SetCommentStateOperation from JSON', function () {
  241. const raw = {
  242. commentId: 'comm1',
  243. resolved: true,
  244. }
  245. const op = EditOperationBuilder.fromJSON(raw)
  246. expect(op).to.be.an.instanceof(SetCommentStateOperation)
  247. expect(op.toJSON()).to.deep.equal(raw)
  248. })
  249. it('Constructs EditNoOperation from JSON', function () {
  250. const raw = { noOp: true }
  251. const op = EditOperationBuilder.fromJSON(raw)
  252. expect(op).to.be.an.instanceof(EditNoOperation)
  253. expect(op.toJSON()).to.deep.equal(raw)
  254. })
  255. it('Throws error for unsupported operation', function () {
  256. const raw = {
  257. unsupportedOperation: {
  258. op: 'foo',
  259. },
  260. }
  261. expect(() => EditOperationBuilder.fromJSON(raw)).to.throw(
  262. 'Unsupported operation in EditOperationBuilder.fromJSON'
  263. )
  264. })
  265. it('Constructs TextOperation from JSON (randomised)', function () {
  266. const str = random.string(50)
  267. const randomOperation = randomTextOperation(str)
  268. const op = EditOperationBuilder.fromJSON(randomOperation.toJSON())
  269. expect(op).to.be.an.instanceof(TextOperation)
  270. expect(op.equals(randomOperation)).to.be.true
  271. })
  272. })