edit_operation.test.js 13 KB

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