|
@@ -0,0 +1,746 @@
|
|
|
|
|
+'use strict'
|
|
|
|
|
+
|
|
|
|
|
+const _ = require('lodash')
|
|
|
|
|
+const { expect } = require('chai')
|
|
|
|
|
+
|
|
|
|
|
+const ot = require('..')
|
|
|
|
|
+const File = ot.File
|
|
|
|
|
+const AddFileOperation = ot.AddFileOperation
|
|
|
|
|
+const MoveFileOperation = ot.MoveFileOperation
|
|
|
|
|
+const EditFileOperation = ot.EditFileOperation
|
|
|
|
|
+const NoOperation = ot.NoOperation
|
|
|
|
|
+const Operation = ot.Operation
|
|
|
|
|
+const TextOperation = ot.TextOperation
|
|
|
|
|
+const Snapshot = ot.Snapshot
|
|
|
|
|
+
|
|
|
|
|
+describe('Operation', function () {
|
|
|
|
|
+ function makeEmptySnapshot() {
|
|
|
|
|
+ return new Snapshot()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function makeOneFileSnapshot() {
|
|
|
|
|
+ const snapshot = makeEmptySnapshot()
|
|
|
|
|
+ snapshot.addFile('foo', File.fromString(''))
|
|
|
|
|
+ return snapshot
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function makeTwoFileSnapshot() {
|
|
|
|
|
+ const snapshot = makeOneFileSnapshot()
|
|
|
|
|
+ snapshot.addFile('bar', File.fromString('a'))
|
|
|
|
|
+ return snapshot
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function addFile(pathname, content) {
|
|
|
|
|
+ return new AddFileOperation(pathname, File.fromString(content))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function roundTripOperation(operation) {
|
|
|
|
|
+ return Operation.fromRaw(operation.toRaw())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function deepCopySnapshot(snapshot) {
|
|
|
|
|
+ return Snapshot.fromRaw(snapshot.toRaw())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function runConcurrently(operation0, operation1, snapshot) {
|
|
|
|
|
+ const operations = [
|
|
|
|
|
+ // make sure they survive serialization
|
|
|
|
|
+ roundTripOperation(operation0),
|
|
|
|
|
+ roundTripOperation(operation1),
|
|
|
|
|
+ ]
|
|
|
|
|
+ const primeOperations = Operation.transform(operation0, operation1)
|
|
|
|
|
+ const originalSnapshot = snapshot || makeEmptySnapshot()
|
|
|
|
|
+ const snapshotA = deepCopySnapshot(originalSnapshot)
|
|
|
|
|
+ const snapshotB = deepCopySnapshot(originalSnapshot)
|
|
|
|
|
+
|
|
|
|
|
+ operations[0].applyTo(snapshotA)
|
|
|
|
|
+ operations[1].applyTo(snapshotB)
|
|
|
|
|
+
|
|
|
|
|
+ primeOperations[0].applyTo(snapshotB)
|
|
|
|
|
+ primeOperations[1].applyTo(snapshotA)
|
|
|
|
|
+ expect(snapshotA).to.eql(snapshotB)
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ snapshot: snapshotA,
|
|
|
|
|
+ operations: operations,
|
|
|
|
|
+ primeOperations: primeOperations,
|
|
|
|
|
+
|
|
|
|
|
+ log() {
|
|
|
|
|
+ console.log(this)
|
|
|
|
|
+ return this
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ expectNoTransform() {
|
|
|
|
|
+ expect(this.operations).to.eql(this.primeOperations)
|
|
|
|
|
+ return this
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ swap() {
|
|
|
|
|
+ return runConcurrently(operation1, operation0, originalSnapshot)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ expectFiles(files) {
|
|
|
|
|
+ this.expectedFiles = files
|
|
|
|
|
+ expect(this.snapshot.countFiles()).to.equal(_.size(files))
|
|
|
|
|
+ _.forOwn(files, (expectedFile, pathname) => {
|
|
|
|
|
+ if (_.isString(expectedFile)) {
|
|
|
|
|
+ expectedFile = { content: expectedFile, metadata: {} }
|
|
|
|
|
+ }
|
|
|
|
|
+ const file = this.snapshot.getFile(pathname)
|
|
|
|
|
+ expect(file.getContent()).to.equal(expectedFile.content)
|
|
|
|
|
+ expect(file.getMetadata()).to.eql(expectedFile.metadata)
|
|
|
|
|
+ })
|
|
|
|
|
+ return this
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ expectSymmetry() {
|
|
|
|
|
+ if (!this.expectedFiles) {
|
|
|
|
|
+ throw new Error('must call expectFiles before expectSymmetry')
|
|
|
|
|
+ }
|
|
|
|
|
+ this.swap().expectFiles(this.expectedFiles)
|
|
|
|
|
+ return this
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ expectPrime(index, klass) {
|
|
|
|
|
+ expect(this.primeOperations[index]).to.be.an.instanceof(klass)
|
|
|
|
|
+ return this
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ tap(fn) {
|
|
|
|
|
+ fn.call(this)
|
|
|
|
|
+ return this
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // shorthand for creating an edit operation
|
|
|
|
|
+ function edit(pathname, textOperationJsonObject) {
|
|
|
|
|
+ return Operation.editFile(
|
|
|
|
|
+ pathname,
|
|
|
|
|
+ TextOperation.fromJSON(textOperationJsonObject)
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-AddFile with different names', function () {
|
|
|
|
|
+ runConcurrently(addFile('foo', ''), addFile('bar', 'a'))
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({ bar: 'a', foo: '' })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-AddFile with same name', function () {
|
|
|
|
|
+ // the second file 'wins'
|
|
|
|
|
+ runConcurrently(addFile('foo', ''), addFile('foo', 'a'))
|
|
|
|
|
+ .expectFiles({ foo: 'a' })
|
|
|
|
|
+ // if the first add was committed first, the second add overwrites it
|
|
|
|
|
+ .expectPrime(1, AddFileOperation)
|
|
|
|
|
+ // if the second add was committed first, the first add becomes a no-op
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .swap()
|
|
|
|
|
+ .expectFiles({ foo: '' })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-MoveFile with no conflict', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'baz'),
|
|
|
|
|
+ addFile('bar', 'a'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({ bar: 'a', baz: '' })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-MoveFile with move from new file', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'baz'),
|
|
|
|
|
+ addFile('foo', 'a'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ baz: 'a' })
|
|
|
|
|
+ // if the move was committed first, the add overwrites it
|
|
|
|
|
+ .expectPrime(1, AddFileOperation)
|
|
|
|
|
+ // if the add was committed first, the move appears in the history
|
|
|
|
|
+ .expectPrime(0, MoveFileOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-MoveFile with move to new file', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'baz'),
|
|
|
|
|
+ addFile('baz', 'a'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ baz: 'a' })
|
|
|
|
|
+ // if the move was committed first, the add overwrites it
|
|
|
|
|
+ .expectPrime(1, AddFileOperation)
|
|
|
|
|
+ // if the add was committed first, the move becomes a delete
|
|
|
|
|
+ .expectPrime(0, MoveFileOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[0].isRemoveFile()).to.be.true
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-RemoveFile with no conflict', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.removeFile('foo'),
|
|
|
|
|
+ addFile('bar', 'a'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({ bar: 'a' })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-RemoveFile that removes added file', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.removeFile('foo'),
|
|
|
|
|
+ addFile('foo', 'a'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: 'a' })
|
|
|
|
|
+ // if the move was committed first, the add overwrites it
|
|
|
|
|
+ .expectPrime(1, AddFileOperation)
|
|
|
|
|
+ // if the add was committed first, the move gets dropped
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-EditFile with no conflict', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ addFile('bar', 'a'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({ bar: 'a', foo: 'x' })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-EditFile when new file is edited', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ addFile('foo', 'a'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: 'a' })
|
|
|
|
|
+ // if the edit was committed first, the add overwrites it
|
|
|
|
|
+ .expectPrime(1, AddFileOperation)
|
|
|
|
|
+ // if the add was committed first, the edit gets dropped
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-SetFileMetadata with no conflict', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ addFile('bar', 'a'),
|
|
|
|
|
+ Operation.setFileMetadata('foo', testMetadata),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({ foo: { content: '', metadata: testMetadata }, bar: 'a' })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms AddFile-SetFileMetadata with same name', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ addFile('foo', 'x'),
|
|
|
|
|
+ Operation.setFileMetadata('foo', testMetadata),
|
|
|
|
|
+ makeEmptySnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: { content: 'x', metadata: testMetadata } })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile with no conflict', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'baz'),
|
|
|
|
|
+ Operation.moveFile('bar', 'bat'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ bat: 'a', baz: '' })
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile same move foo->foo, foo->foo', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: '' })
|
|
|
|
|
+ .expectPrime(1, NoOperation)
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile no-op foo->foo, foo->bar', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ bar: '' })
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile no-op foo->foo, bar->foo', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ bar: '' })
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile no-op foo->foo, bar->bar', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ Operation.moveFile('bar', 'bar'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ bar: 'a', foo: '' })
|
|
|
|
|
+ .expectPrime(1, NoOperation)
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile same move foo->bar, foo->bar', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ bar: '' })
|
|
|
|
|
+ .expectPrime(1, NoOperation)
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile opposite foo->bar, bar->foo', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ Operation.moveFile('bar', 'foo'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles([])
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, MoveFileOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[1].isRemoveFile()).to.be.true
|
|
|
|
|
+ expect(this.primeOperations[1].getPathname()).to.equal('bar')
|
|
|
|
|
+
|
|
|
|
|
+ expect(this.primeOperations[0].isRemoveFile()).to.be.true
|
|
|
|
|
+ expect(this.primeOperations[0].getPathname()).to.equal('foo')
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile no-op foo->foo, bar->baz', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ Operation.moveFile('bar', 'baz'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ baz: 'a', foo: '' })
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile diverge foo->bar, foo->baz', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ Operation.moveFile('foo', 'baz'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ baz: '' })
|
|
|
|
|
+ // if foo->bar was committed first, the second move becomes bar->baz
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ // if foo->baz was committed first, the second move becomes a no-op
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[1].getPathname()).to.equal('bar')
|
|
|
|
|
+ expect(this.primeOperations[1].getNewPathname()).to.equal('baz')
|
|
|
|
|
+ })
|
|
|
|
|
+ .swap()
|
|
|
|
|
+ .expectFiles({ bar: '' })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile transitive foo->baz, bar->foo', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'baz'),
|
|
|
|
|
+ Operation.moveFile('bar', 'foo'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ baz: 'a' })
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, MoveFileOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile transitive foo->bar, bar->baz', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ Operation.moveFile('bar', 'baz'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ baz: '' })
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, MoveFileOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-MoveFile converge foo->baz, bar->baz', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'baz'),
|
|
|
|
|
+ Operation.moveFile('bar', 'baz'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ baz: 'a' })
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, MoveFileOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ // if foo->baz was committed first, we just apply the move
|
|
|
|
|
+ expect(this.primeOperations[1]).to.eql(this.operations[1])
|
|
|
|
|
+
|
|
|
|
|
+ // if bar->baz was committed first, the other move becomes a remove
|
|
|
|
|
+ expect(this.primeOperations[0].isRemoveFile()).to.be.true
|
|
|
|
|
+ expect(this.primeOperations[0].getPathname()).to.equal('foo')
|
|
|
|
|
+ })
|
|
|
|
|
+ .swap()
|
|
|
|
|
+ .expectFiles({ baz: '' })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-RemoveFile no-op foo->foo, foo->', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ Operation.removeFile('foo'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles([])
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[1].isRemoveFile()).to.be.true
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-RemoveFile same move foo->, foo->', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.removeFile('foo'),
|
|
|
|
|
+ Operation.removeFile('foo'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles([])
|
|
|
|
|
+ .expectPrime(1, NoOperation)
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-RemoveFile no conflict foo->, bar->', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.removeFile('foo'),
|
|
|
|
|
+ Operation.removeFile('bar'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles([])
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-RemoveFile no conflict foo->foo, bar->', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ Operation.removeFile('bar'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: '' })
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, NoOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[1].isRemoveFile()).to.be.true
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-RemoveFile transitive foo->, bar->foo', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.removeFile('foo'),
|
|
|
|
|
+ Operation.moveFile('bar', 'foo'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles([])
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, MoveFileOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[1].isRemoveFile()).to.be.true
|
|
|
|
|
+ expect(this.primeOperations[1].getPathname()).to.equal('bar')
|
|
|
|
|
+
|
|
|
|
|
+ expect(this.primeOperations[0].isRemoveFile()).to.be.true
|
|
|
|
|
+ expect(this.primeOperations[0].getPathname()).to.equal('foo')
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-RemoveFile transitive foo->bar, bar->', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ Operation.removeFile('bar'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({})
|
|
|
|
|
+ .expectPrime(1, MoveFileOperation)
|
|
|
|
|
+ .expectPrime(0, MoveFileOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[1].isRemoveFile()).to.be.true
|
|
|
|
|
+ expect(this.primeOperations[1].getPathname()).to.equal('bar')
|
|
|
|
|
+
|
|
|
|
|
+ expect(this.primeOperations[0].isRemoveFile()).to.be.true
|
|
|
|
|
+ expect(this.primeOperations[0].getPathname()).to.equal('foo')
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-EditFile with no conflict', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('bar', 'baz'),
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ baz: 'a', foo: 'x' })
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-EditFile with edit on pathname', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ bar: 'x' })
|
|
|
|
|
+ .expectPrime(1, EditFileOperation)
|
|
|
|
|
+ .expectPrime(0, MoveFileOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[1].getPathname()).to.equal('bar')
|
|
|
|
|
+
|
|
|
|
|
+ expect(this.primeOperations[0].getPathname()).to.equal('foo')
|
|
|
|
|
+ expect(this.primeOperations[0].getNewPathname()).to.equal('bar')
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-EditFile with edit on new pathname', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('bar', 'foo'),
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: 'a' })
|
|
|
|
|
+ .expectPrime(1, NoOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[0]).to.eql(this.operations[0])
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-EditFile with no-op move', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: 'x' })
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-SetFileMetadata with no conflict', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'baz'),
|
|
|
|
|
+ Operation.setFileMetadata('bar', testMetadata),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({ bar: { content: 'a', metadata: testMetadata }, baz: '' })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-SetFileMetadata with set on pathname', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ Operation.setFileMetadata('foo', testMetadata),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ bar: { content: '', metadata: testMetadata } })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-SetFileMetadata w/ set on new pathname', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'bar'),
|
|
|
|
|
+ Operation.setFileMetadata('bar', testMetadata),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ // move wins
|
|
|
|
|
+ .expectFiles({ bar: { content: '', metadata: {} } })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms MoveFile-SetFileMetadata with no-op move', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.moveFile('foo', 'foo'),
|
|
|
|
|
+ Operation.setFileMetadata('foo', testMetadata),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: { content: '', metadata: testMetadata } })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms EditFile-EditFile with no conflict', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ edit('bar', [1, 'x']),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ bar: 'ax', foo: 'x' })
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms EditFile-EditFile on same file', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ edit('foo', ['y']),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: 'xy' })
|
|
|
|
|
+ .expectPrime(1, EditFileOperation)
|
|
|
|
|
+ .expectPrime(0, EditFileOperation)
|
|
|
|
|
+ .tap(function () {
|
|
|
|
|
+ expect(this.primeOperations[1].getTextOperation().toJSON()).to.eql([
|
|
|
|
|
+ 1,
|
|
|
|
|
+ 'y',
|
|
|
|
|
+ ])
|
|
|
|
|
+ expect(this.primeOperations[0].getTextOperation().toJSON()).to.eql([
|
|
|
|
|
+ 'x',
|
|
|
|
|
+ 1,
|
|
|
|
|
+ ])
|
|
|
|
|
+ })
|
|
|
|
|
+ .swap()
|
|
|
|
|
+ .expectFiles({ foo: 'yx' })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms EditFile-RemoveFile with no conflict', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ Operation.removeFile('bar'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({ foo: 'x' })
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms EditFile-RemoveFile on same file', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ Operation.removeFile('foo'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({})
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms EditFile-SetFileMetadata with no conflict', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ Operation.setFileMetadata('bar', testMetadata),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({
|
|
|
|
|
+ foo: { content: 'x', metadata: {} },
|
|
|
|
|
+ bar: { content: 'a', metadata: testMetadata },
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms EditFile-SetFileMetadata on same file', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ edit('foo', ['x']),
|
|
|
|
|
+ Operation.setFileMetadata('foo', testMetadata),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({ foo: { content: 'x', metadata: testMetadata } })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms SetFileMetadata-SetFileMetadata w/ no conflict', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.setFileMetadata('foo', { baz: 1 }),
|
|
|
|
|
+ Operation.setFileMetadata('bar', { baz: 2 }),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({
|
|
|
|
|
+ foo: { content: '', metadata: { baz: 1 } },
|
|
|
|
|
+ bar: { content: 'a', metadata: { baz: 2 } },
|
|
|
|
|
+ })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms SetFileMetadata-SetFileMetadata on same file', function () {
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.setFileMetadata('foo', { baz: 1 }),
|
|
|
|
|
+ Operation.setFileMetadata('foo', { baz: 2 }),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ // second op wins
|
|
|
|
|
+ .expectFiles({ foo: { content: '', metadata: { baz: 2 } } })
|
|
|
|
|
+ .swap()
|
|
|
|
|
+ // first op wins
|
|
|
|
|
+ .expectFiles({ foo: { content: '', metadata: { baz: 1 } } })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms SetFileMetadata-RemoveFile with no conflict', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.setFileMetadata('foo', testMetadata),
|
|
|
|
|
+ Operation.removeFile('bar'),
|
|
|
|
|
+ makeTwoFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectNoTransform()
|
|
|
|
|
+ .expectFiles({ foo: { content: '', metadata: testMetadata } })
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('transforms SetFileMetadata-RemoveFile on same file', function () {
|
|
|
|
|
+ const testMetadata = { baz: 1 }
|
|
|
|
|
+ runConcurrently(
|
|
|
|
|
+ Operation.setFileMetadata('foo', testMetadata),
|
|
|
|
|
+ Operation.removeFile('foo'),
|
|
|
|
|
+ makeOneFileSnapshot()
|
|
|
|
|
+ )
|
|
|
|
|
+ .expectFiles({})
|
|
|
|
|
+ .expectSymmetry()
|
|
|
|
|
+ })
|
|
|
|
|
+})
|