operation.test.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. 'use strict'
  2. const _ = require('lodash')
  3. const { expect } = require('chai')
  4. const ot = require('..')
  5. const File = ot.File
  6. const AddFileOperation = ot.AddFileOperation
  7. const MoveFileOperation = ot.MoveFileOperation
  8. const EditFileOperation = ot.EditFileOperation
  9. const NoOperation = ot.NoOperation
  10. const Operation = ot.Operation
  11. const TextOperation = ot.TextOperation
  12. const Snapshot = ot.Snapshot
  13. describe('Operation', function () {
  14. function makeEmptySnapshot() {
  15. return new Snapshot()
  16. }
  17. function makeOneFileSnapshot() {
  18. const snapshot = makeEmptySnapshot()
  19. snapshot.addFile('foo', File.fromString(''))
  20. return snapshot
  21. }
  22. function makeTwoFileSnapshot() {
  23. const snapshot = makeOneFileSnapshot()
  24. snapshot.addFile('bar', File.fromString('a'))
  25. return snapshot
  26. }
  27. function addFile(pathname, content) {
  28. return new AddFileOperation(pathname, File.fromString(content))
  29. }
  30. function roundTripOperation(operation) {
  31. return Operation.fromRaw(operation.toRaw())
  32. }
  33. function deepCopySnapshot(snapshot) {
  34. return Snapshot.fromRaw(snapshot.toRaw())
  35. }
  36. function runConcurrently(operation0, operation1, snapshot) {
  37. const operations = [
  38. // make sure they survive serialization
  39. roundTripOperation(operation0),
  40. roundTripOperation(operation1),
  41. ]
  42. const primeOperations = Operation.transform(operation0, operation1)
  43. const originalSnapshot = snapshot || makeEmptySnapshot()
  44. const snapshotA = deepCopySnapshot(originalSnapshot)
  45. const snapshotB = deepCopySnapshot(originalSnapshot)
  46. operations[0].applyTo(snapshotA)
  47. operations[1].applyTo(snapshotB)
  48. primeOperations[0].applyTo(snapshotB)
  49. primeOperations[1].applyTo(snapshotA)
  50. expect(snapshotA).to.eql(snapshotB)
  51. return {
  52. snapshot: snapshotA,
  53. operations,
  54. primeOperations,
  55. log() {
  56. console.log(this)
  57. return this
  58. },
  59. expectNoTransform() {
  60. expect(this.operations).to.eql(this.primeOperations)
  61. return this
  62. },
  63. swap() {
  64. return runConcurrently(operation1, operation0, originalSnapshot)
  65. },
  66. expectFiles(files) {
  67. this.expectedFiles = files
  68. expect(this.snapshot.countFiles()).to.equal(_.size(files))
  69. _.forOwn(files, (expectedFile, pathname) => {
  70. if (_.isString(expectedFile)) {
  71. expectedFile = { content: expectedFile, metadata: {} }
  72. }
  73. const file = this.snapshot.getFile(pathname)
  74. expect(file.getContent()).to.equal(expectedFile.content)
  75. expect(file.getMetadata()).to.eql(expectedFile.metadata)
  76. })
  77. return this
  78. },
  79. expectSymmetry() {
  80. if (!this.expectedFiles) {
  81. throw new Error('must call expectFiles before expectSymmetry')
  82. }
  83. this.swap().expectFiles(this.expectedFiles)
  84. return this
  85. },
  86. expectPrime(index, klass) {
  87. expect(this.primeOperations[index]).to.be.an.instanceof(klass)
  88. return this
  89. },
  90. tap(fn) {
  91. fn.call(this)
  92. return this
  93. },
  94. }
  95. }
  96. // shorthand for creating an edit operation
  97. function edit(pathname, textOperationOps) {
  98. return Operation.editFile(
  99. pathname,
  100. TextOperation.fromJSON({ textOperation: textOperationOps })
  101. )
  102. }
  103. it('transforms AddFile-AddFile with different names', function () {
  104. runConcurrently(addFile('foo', ''), addFile('bar', 'a'))
  105. .expectNoTransform()
  106. .expectFiles({ bar: 'a', foo: '' })
  107. .expectSymmetry()
  108. })
  109. it('transforms AddFile-AddFile with same name', function () {
  110. // the second file 'wins'
  111. runConcurrently(addFile('foo', ''), addFile('foo', 'a'))
  112. .expectFiles({ foo: 'a' })
  113. // if the first add was committed first, the second add overwrites it
  114. .expectPrime(1, AddFileOperation)
  115. // if the second add was committed first, the first add becomes a no-op
  116. .expectPrime(0, NoOperation)
  117. .swap()
  118. .expectFiles({ foo: '' })
  119. })
  120. it('transforms AddFile-MoveFile with no conflict', function () {
  121. runConcurrently(
  122. Operation.moveFile('foo', 'baz'),
  123. addFile('bar', 'a'),
  124. makeOneFileSnapshot()
  125. )
  126. .expectNoTransform()
  127. .expectFiles({ bar: 'a', baz: '' })
  128. .expectSymmetry()
  129. })
  130. it('transforms AddFile-MoveFile with move from new file', function () {
  131. runConcurrently(
  132. Operation.moveFile('foo', 'baz'),
  133. addFile('foo', 'a'),
  134. makeOneFileSnapshot()
  135. )
  136. .expectFiles({ baz: 'a' })
  137. // if the move was committed first, the add overwrites it
  138. .expectPrime(1, AddFileOperation)
  139. // if the add was committed first, the move appears in the history
  140. .expectPrime(0, MoveFileOperation)
  141. .expectSymmetry()
  142. })
  143. it('transforms AddFile-MoveFile with move to new file', function () {
  144. runConcurrently(
  145. Operation.moveFile('foo', 'baz'),
  146. addFile('baz', 'a'),
  147. makeOneFileSnapshot()
  148. )
  149. .expectFiles({ baz: 'a' })
  150. // if the move was committed first, the add overwrites it
  151. .expectPrime(1, AddFileOperation)
  152. // if the add was committed first, the move becomes a delete
  153. .expectPrime(0, MoveFileOperation)
  154. .tap(function () {
  155. expect(this.primeOperations[0].isRemoveFile()).to.be.true
  156. })
  157. .expectSymmetry()
  158. })
  159. it('transforms AddFile-RemoveFile with no conflict', function () {
  160. runConcurrently(
  161. Operation.removeFile('foo'),
  162. addFile('bar', 'a'),
  163. makeOneFileSnapshot()
  164. )
  165. .expectNoTransform()
  166. .expectFiles({ bar: 'a' })
  167. .expectSymmetry()
  168. })
  169. it('transforms AddFile-RemoveFile that removes added file', function () {
  170. runConcurrently(
  171. Operation.removeFile('foo'),
  172. addFile('foo', 'a'),
  173. makeOneFileSnapshot()
  174. )
  175. .expectFiles({ foo: 'a' })
  176. // if the move was committed first, the add overwrites it
  177. .expectPrime(1, AddFileOperation)
  178. // if the add was committed first, the move gets dropped
  179. .expectPrime(0, NoOperation)
  180. .expectSymmetry()
  181. })
  182. it('transforms AddFile-EditFile with no conflict', function () {
  183. runConcurrently(
  184. edit('foo', ['x']),
  185. addFile('bar', 'a'),
  186. makeOneFileSnapshot()
  187. )
  188. .expectNoTransform()
  189. .expectFiles({ bar: 'a', foo: 'x' })
  190. .expectSymmetry()
  191. })
  192. it('transforms AddFile-EditFile when new file is edited', function () {
  193. runConcurrently(
  194. edit('foo', ['x']),
  195. addFile('foo', 'a'),
  196. makeOneFileSnapshot()
  197. )
  198. .expectFiles({ foo: 'a' })
  199. // if the edit was committed first, the add overwrites it
  200. .expectPrime(1, AddFileOperation)
  201. // if the add was committed first, the edit gets dropped
  202. .expectPrime(0, NoOperation)
  203. .expectSymmetry()
  204. })
  205. it('transforms AddFile-SetFileMetadata with no conflict', function () {
  206. const testMetadata = { baz: 1 }
  207. runConcurrently(
  208. addFile('bar', 'a'),
  209. Operation.setFileMetadata('foo', testMetadata),
  210. makeOneFileSnapshot()
  211. )
  212. .expectNoTransform()
  213. .expectFiles({ foo: { content: '', metadata: testMetadata }, bar: 'a' })
  214. .expectSymmetry()
  215. })
  216. it('transforms AddFile-SetFileMetadata with same name', function () {
  217. const testMetadata = { baz: 1 }
  218. runConcurrently(
  219. addFile('foo', 'x'),
  220. Operation.setFileMetadata('foo', testMetadata),
  221. makeEmptySnapshot()
  222. )
  223. .expectFiles({ foo: { content: 'x', metadata: testMetadata } })
  224. .expectSymmetry()
  225. })
  226. it('transforms MoveFile-MoveFile with no conflict', function () {
  227. runConcurrently(
  228. Operation.moveFile('foo', 'baz'),
  229. Operation.moveFile('bar', 'bat'),
  230. makeTwoFileSnapshot()
  231. )
  232. .expectFiles({ bat: 'a', baz: '' })
  233. .expectNoTransform()
  234. .expectSymmetry()
  235. })
  236. it('transforms MoveFile-MoveFile same move foo->foo, foo->foo', function () {
  237. runConcurrently(
  238. Operation.moveFile('foo', 'foo'),
  239. Operation.moveFile('foo', 'foo'),
  240. makeOneFileSnapshot()
  241. )
  242. .expectFiles({ foo: '' })
  243. .expectPrime(1, NoOperation)
  244. .expectPrime(0, NoOperation)
  245. .expectSymmetry()
  246. })
  247. it('transforms MoveFile-MoveFile no-op foo->foo, foo->bar', function () {
  248. runConcurrently(
  249. Operation.moveFile('foo', 'foo'),
  250. Operation.moveFile('foo', 'bar'),
  251. makeOneFileSnapshot()
  252. )
  253. .expectFiles({ bar: '' })
  254. .expectPrime(1, MoveFileOperation)
  255. .expectPrime(0, NoOperation)
  256. .expectSymmetry()
  257. })
  258. it('transforms MoveFile-MoveFile no-op foo->foo, bar->foo', function () {
  259. runConcurrently(
  260. Operation.moveFile('foo', 'foo'),
  261. Operation.moveFile('foo', 'bar'),
  262. makeTwoFileSnapshot()
  263. )
  264. .expectFiles({ bar: '' })
  265. .expectPrime(1, MoveFileOperation)
  266. .expectPrime(0, NoOperation)
  267. .expectSymmetry()
  268. })
  269. it('transforms MoveFile-MoveFile no-op foo->foo, bar->bar', function () {
  270. runConcurrently(
  271. Operation.moveFile('foo', 'foo'),
  272. Operation.moveFile('bar', 'bar'),
  273. makeTwoFileSnapshot()
  274. )
  275. .expectFiles({ bar: 'a', foo: '' })
  276. .expectPrime(1, NoOperation)
  277. .expectPrime(0, NoOperation)
  278. .expectSymmetry()
  279. })
  280. it('transforms MoveFile-MoveFile same move foo->bar, foo->bar', function () {
  281. runConcurrently(
  282. Operation.moveFile('foo', 'bar'),
  283. Operation.moveFile('foo', 'bar'),
  284. makeOneFileSnapshot()
  285. )
  286. .expectFiles({ bar: '' })
  287. .expectPrime(1, NoOperation)
  288. .expectPrime(0, NoOperation)
  289. .expectSymmetry()
  290. })
  291. it('transforms MoveFile-MoveFile opposite foo->bar, bar->foo', function () {
  292. runConcurrently(
  293. Operation.moveFile('foo', 'bar'),
  294. Operation.moveFile('bar', 'foo'),
  295. makeTwoFileSnapshot()
  296. )
  297. .expectFiles([])
  298. .expectPrime(1, MoveFileOperation)
  299. .expectPrime(0, MoveFileOperation)
  300. .tap(function () {
  301. expect(this.primeOperations[1].isRemoveFile()).to.be.true
  302. expect(this.primeOperations[1].getPathname()).to.equal('bar')
  303. expect(this.primeOperations[0].isRemoveFile()).to.be.true
  304. expect(this.primeOperations[0].getPathname()).to.equal('foo')
  305. })
  306. .expectSymmetry()
  307. })
  308. it('transforms MoveFile-MoveFile no-op foo->foo, bar->baz', function () {
  309. runConcurrently(
  310. Operation.moveFile('foo', 'foo'),
  311. Operation.moveFile('bar', 'baz'),
  312. makeTwoFileSnapshot()
  313. )
  314. .expectFiles({ baz: 'a', foo: '' })
  315. .expectPrime(1, MoveFileOperation)
  316. .expectPrime(0, NoOperation)
  317. .expectSymmetry()
  318. })
  319. it('transforms MoveFile-MoveFile diverge foo->bar, foo->baz', function () {
  320. runConcurrently(
  321. Operation.moveFile('foo', 'bar'),
  322. Operation.moveFile('foo', 'baz'),
  323. makeOneFileSnapshot()
  324. )
  325. .expectFiles({ baz: '' })
  326. // if foo->bar was committed first, the second move becomes bar->baz
  327. .expectPrime(1, MoveFileOperation)
  328. // if foo->baz was committed first, the second move becomes a no-op
  329. .expectPrime(0, NoOperation)
  330. .tap(function () {
  331. expect(this.primeOperations[1].getPathname()).to.equal('bar')
  332. expect(this.primeOperations[1].getNewPathname()).to.equal('baz')
  333. })
  334. .swap()
  335. .expectFiles({ bar: '' })
  336. })
  337. it('transforms MoveFile-MoveFile transitive foo->baz, bar->foo', function () {
  338. runConcurrently(
  339. Operation.moveFile('foo', 'baz'),
  340. Operation.moveFile('bar', 'foo'),
  341. makeTwoFileSnapshot()
  342. )
  343. .expectFiles({ baz: 'a' })
  344. .expectPrime(1, MoveFileOperation)
  345. .expectPrime(0, MoveFileOperation)
  346. .expectSymmetry()
  347. })
  348. it('transforms MoveFile-MoveFile transitive foo->bar, bar->baz', function () {
  349. runConcurrently(
  350. Operation.moveFile('foo', 'bar'),
  351. Operation.moveFile('bar', 'baz'),
  352. makeTwoFileSnapshot()
  353. )
  354. .expectFiles({ baz: '' })
  355. .expectPrime(1, MoveFileOperation)
  356. .expectPrime(0, MoveFileOperation)
  357. .expectSymmetry()
  358. })
  359. it('transforms MoveFile-MoveFile converge foo->baz, bar->baz', function () {
  360. runConcurrently(
  361. Operation.moveFile('foo', 'baz'),
  362. Operation.moveFile('bar', 'baz'),
  363. makeTwoFileSnapshot()
  364. )
  365. .expectFiles({ baz: 'a' })
  366. .expectPrime(1, MoveFileOperation)
  367. .expectPrime(0, MoveFileOperation)
  368. .tap(function () {
  369. // if foo->baz was committed first, we just apply the move
  370. expect(this.primeOperations[1]).to.eql(this.operations[1])
  371. // if bar->baz was committed first, the other move becomes a remove
  372. expect(this.primeOperations[0].isRemoveFile()).to.be.true
  373. expect(this.primeOperations[0].getPathname()).to.equal('foo')
  374. })
  375. .swap()
  376. .expectFiles({ baz: '' })
  377. })
  378. it('transforms MoveFile-RemoveFile no-op foo->foo, foo->', function () {
  379. runConcurrently(
  380. Operation.moveFile('foo', 'foo'),
  381. Operation.removeFile('foo'),
  382. makeOneFileSnapshot()
  383. )
  384. .expectFiles([])
  385. .expectPrime(1, MoveFileOperation)
  386. .expectPrime(0, NoOperation)
  387. .tap(function () {
  388. expect(this.primeOperations[1].isRemoveFile()).to.be.true
  389. })
  390. .expectSymmetry()
  391. })
  392. it('transforms MoveFile-RemoveFile same move foo->, foo->', function () {
  393. runConcurrently(
  394. Operation.removeFile('foo'),
  395. Operation.removeFile('foo'),
  396. makeOneFileSnapshot()
  397. )
  398. .expectFiles([])
  399. .expectPrime(1, NoOperation)
  400. .expectPrime(0, NoOperation)
  401. .expectSymmetry()
  402. })
  403. it('transforms MoveFile-RemoveFile no conflict foo->, bar->', function () {
  404. runConcurrently(
  405. Operation.removeFile('foo'),
  406. Operation.removeFile('bar'),
  407. makeTwoFileSnapshot()
  408. )
  409. .expectFiles([])
  410. .expectNoTransform()
  411. .expectSymmetry()
  412. })
  413. it('transforms MoveFile-RemoveFile no conflict foo->foo, bar->', function () {
  414. runConcurrently(
  415. Operation.moveFile('foo', 'foo'),
  416. Operation.removeFile('bar'),
  417. makeTwoFileSnapshot()
  418. )
  419. .expectFiles({ foo: '' })
  420. .expectPrime(1, MoveFileOperation)
  421. .expectPrime(0, NoOperation)
  422. .tap(function () {
  423. expect(this.primeOperations[1].isRemoveFile()).to.be.true
  424. })
  425. .expectSymmetry()
  426. })
  427. it('transforms MoveFile-RemoveFile transitive foo->, bar->foo', function () {
  428. runConcurrently(
  429. Operation.removeFile('foo'),
  430. Operation.moveFile('bar', 'foo'),
  431. makeTwoFileSnapshot()
  432. )
  433. .expectFiles([])
  434. .expectPrime(1, MoveFileOperation)
  435. .expectPrime(0, MoveFileOperation)
  436. .tap(function () {
  437. expect(this.primeOperations[1].isRemoveFile()).to.be.true
  438. expect(this.primeOperations[1].getPathname()).to.equal('bar')
  439. expect(this.primeOperations[0].isRemoveFile()).to.be.true
  440. expect(this.primeOperations[0].getPathname()).to.equal('foo')
  441. })
  442. .expectSymmetry()
  443. })
  444. it('transforms MoveFile-RemoveFile transitive foo->bar, bar->', function () {
  445. runConcurrently(
  446. Operation.moveFile('foo', 'bar'),
  447. Operation.removeFile('bar'),
  448. makeTwoFileSnapshot()
  449. )
  450. .expectFiles({})
  451. .expectPrime(1, MoveFileOperation)
  452. .expectPrime(0, MoveFileOperation)
  453. .tap(function () {
  454. expect(this.primeOperations[1].isRemoveFile()).to.be.true
  455. expect(this.primeOperations[1].getPathname()).to.equal('bar')
  456. expect(this.primeOperations[0].isRemoveFile()).to.be.true
  457. expect(this.primeOperations[0].getPathname()).to.equal('foo')
  458. })
  459. .expectSymmetry()
  460. })
  461. it('transforms MoveFile-EditFile with no conflict', function () {
  462. runConcurrently(
  463. Operation.moveFile('bar', 'baz'),
  464. edit('foo', ['x']),
  465. makeTwoFileSnapshot()
  466. )
  467. .expectFiles({ baz: 'a', foo: 'x' })
  468. .expectNoTransform()
  469. .expectSymmetry()
  470. })
  471. it('transforms MoveFile-EditFile with edit on pathname', function () {
  472. runConcurrently(
  473. Operation.moveFile('foo', 'bar'),
  474. edit('foo', ['x']),
  475. makeOneFileSnapshot()
  476. )
  477. .expectFiles({ bar: 'x' })
  478. .expectPrime(1, EditFileOperation)
  479. .expectPrime(0, MoveFileOperation)
  480. .tap(function () {
  481. expect(this.primeOperations[1].getPathname()).to.equal('bar')
  482. expect(this.primeOperations[0].getPathname()).to.equal('foo')
  483. expect(this.primeOperations[0].getNewPathname()).to.equal('bar')
  484. })
  485. .expectSymmetry()
  486. })
  487. it('transforms MoveFile-EditFile with edit on new pathname', function () {
  488. runConcurrently(
  489. Operation.moveFile('bar', 'foo'),
  490. edit('foo', ['x']),
  491. makeTwoFileSnapshot()
  492. )
  493. .expectFiles({ foo: 'a' })
  494. .expectPrime(1, NoOperation)
  495. .tap(function () {
  496. expect(this.primeOperations[0]).to.eql(this.operations[0])
  497. })
  498. .expectSymmetry()
  499. })
  500. it('transforms MoveFile-EditFile with no-op move', function () {
  501. runConcurrently(
  502. Operation.moveFile('foo', 'foo'),
  503. edit('foo', ['x']),
  504. makeOneFileSnapshot()
  505. )
  506. .expectFiles({ foo: 'x' })
  507. .expectNoTransform()
  508. .expectSymmetry()
  509. })
  510. it('transforms MoveFile-SetFileMetadata with no conflict', function () {
  511. const testMetadata = { baz: 1 }
  512. runConcurrently(
  513. Operation.moveFile('foo', 'baz'),
  514. Operation.setFileMetadata('bar', testMetadata),
  515. makeTwoFileSnapshot()
  516. )
  517. .expectNoTransform()
  518. .expectFiles({ bar: { content: 'a', metadata: testMetadata }, baz: '' })
  519. .expectSymmetry()
  520. })
  521. it('transforms MoveFile-SetFileMetadata with set on pathname', function () {
  522. const testMetadata = { baz: 1 }
  523. runConcurrently(
  524. Operation.moveFile('foo', 'bar'),
  525. Operation.setFileMetadata('foo', testMetadata),
  526. makeOneFileSnapshot()
  527. )
  528. .expectFiles({ bar: { content: '', metadata: testMetadata } })
  529. .expectSymmetry()
  530. })
  531. it('transforms MoveFile-SetFileMetadata w/ set on new pathname', function () {
  532. const testMetadata = { baz: 1 }
  533. runConcurrently(
  534. Operation.moveFile('foo', 'bar'),
  535. Operation.setFileMetadata('bar', testMetadata),
  536. makeTwoFileSnapshot()
  537. )
  538. // move wins
  539. .expectFiles({ bar: { content: '', metadata: {} } })
  540. .expectSymmetry()
  541. })
  542. it('transforms MoveFile-SetFileMetadata with no-op move', function () {
  543. const testMetadata = { baz: 1 }
  544. runConcurrently(
  545. Operation.moveFile('foo', 'foo'),
  546. Operation.setFileMetadata('foo', testMetadata),
  547. makeOneFileSnapshot()
  548. )
  549. .expectFiles({ foo: { content: '', metadata: testMetadata } })
  550. .expectSymmetry()
  551. })
  552. it('transforms EditFile-EditFile with no conflict', function () {
  553. runConcurrently(
  554. edit('foo', ['x']),
  555. edit('bar', [1, 'x']),
  556. makeTwoFileSnapshot()
  557. )
  558. .expectFiles({ bar: 'ax', foo: 'x' })
  559. .expectNoTransform()
  560. .expectSymmetry()
  561. })
  562. it('transforms EditFile-EditFile on same file', function () {
  563. runConcurrently(
  564. edit('foo', ['x']),
  565. edit('foo', ['y']),
  566. makeOneFileSnapshot()
  567. )
  568. .expectFiles({ foo: 'xy' })
  569. .expectPrime(1, EditFileOperation)
  570. .expectPrime(0, EditFileOperation)
  571. .tap(function () {
  572. expect(this.primeOperations[1].getOperation().toJSON()).to.eql({
  573. textOperation: [1, 'y'],
  574. })
  575. expect(this.primeOperations[0].getOperation().toJSON()).to.eql({
  576. textOperation: ['x', 1],
  577. })
  578. })
  579. .swap()
  580. .expectFiles({ foo: 'yx' })
  581. })
  582. it('transforms EditFile-RemoveFile with no conflict', function () {
  583. runConcurrently(
  584. edit('foo', ['x']),
  585. Operation.removeFile('bar'),
  586. makeTwoFileSnapshot()
  587. )
  588. .expectFiles({ foo: 'x' })
  589. .expectNoTransform()
  590. .expectSymmetry()
  591. })
  592. it('transforms EditFile-RemoveFile on same file', function () {
  593. runConcurrently(
  594. edit('foo', ['x']),
  595. Operation.removeFile('foo'),
  596. makeOneFileSnapshot()
  597. )
  598. .expectFiles({})
  599. .expectSymmetry()
  600. })
  601. it('transforms EditFile-SetFileMetadata with no conflict', function () {
  602. const testMetadata = { baz: 1 }
  603. runConcurrently(
  604. edit('foo', ['x']),
  605. Operation.setFileMetadata('bar', testMetadata),
  606. makeTwoFileSnapshot()
  607. )
  608. .expectNoTransform()
  609. .expectFiles({
  610. foo: { content: 'x', metadata: {} },
  611. bar: { content: 'a', metadata: testMetadata },
  612. })
  613. .expectSymmetry()
  614. })
  615. it('transforms EditFile-SetFileMetadata on same file', function () {
  616. const testMetadata = { baz: 1 }
  617. runConcurrently(
  618. edit('foo', ['x']),
  619. Operation.setFileMetadata('foo', testMetadata),
  620. makeOneFileSnapshot()
  621. )
  622. .expectNoTransform()
  623. .expectFiles({ foo: { content: 'x', metadata: testMetadata } })
  624. .expectSymmetry()
  625. })
  626. it('transforms SetFileMetadata-SetFileMetadata w/ no conflict', function () {
  627. runConcurrently(
  628. Operation.setFileMetadata('foo', { baz: 1 }),
  629. Operation.setFileMetadata('bar', { baz: 2 }),
  630. makeTwoFileSnapshot()
  631. )
  632. .expectNoTransform()
  633. .expectFiles({
  634. foo: { content: '', metadata: { baz: 1 } },
  635. bar: { content: 'a', metadata: { baz: 2 } },
  636. })
  637. .expectSymmetry()
  638. })
  639. it('transforms SetFileMetadata-SetFileMetadata on same file', function () {
  640. runConcurrently(
  641. Operation.setFileMetadata('foo', { baz: 1 }),
  642. Operation.setFileMetadata('foo', { baz: 2 }),
  643. makeOneFileSnapshot()
  644. )
  645. // second op wins
  646. .expectFiles({ foo: { content: '', metadata: { baz: 2 } } })
  647. .swap()
  648. // first op wins
  649. .expectFiles({ foo: { content: '', metadata: { baz: 1 } } })
  650. })
  651. it('transforms SetFileMetadata-RemoveFile with no conflict', function () {
  652. const testMetadata = { baz: 1 }
  653. runConcurrently(
  654. Operation.setFileMetadata('foo', testMetadata),
  655. Operation.removeFile('bar'),
  656. makeTwoFileSnapshot()
  657. )
  658. .expectNoTransform()
  659. .expectFiles({ foo: { content: '', metadata: testMetadata } })
  660. .expectSymmetry()
  661. })
  662. it('transforms SetFileMetadata-RemoveFile on same file', function () {
  663. const testMetadata = { baz: 1 }
  664. runConcurrently(
  665. Operation.setFileMetadata('foo', testMetadata),
  666. Operation.removeFile('foo'),
  667. makeOneFileSnapshot()
  668. )
  669. .expectFiles({})
  670. .expectSymmetry()
  671. })
  672. })