From 71a327992600e90555f4fd752951529bb4407e3b Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 07:01:29 +0530 Subject: [PATCH 01/11] Decaffeinate Tests. Closes #7 --- package.json | 3 +- test/cursor.coffee | 76 -- test/cursor.js | 114 ++ test/genOp.coffee | 215 ---- test/genOp.js | 271 +++++ test/immutable.coffee | 50 - test/immutable.js | 78 ++ test/test.coffee | 2268 ------------------------------------ test/test.js | 2577 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 3041 insertions(+), 2611 deletions(-) delete mode 100644 test/cursor.coffee create mode 100644 test/cursor.js delete mode 100644 test/genOp.coffee create mode 100644 test/genOp.js delete mode 100644 test/immutable.coffee create mode 100644 test/immutable.js delete mode 100644 test/test.coffee create mode 100644 test/test.js diff --git a/package.json b/package.json index af4f737..8f18fdd 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "ot-text-unicode": "^3.0.0" }, "devDependencies": { - "coffeescript": "^2.3.2", "mocha": "^5.2.0", "ot-fuzzer": "^1.1.0", "ot-simple": "^1.0.0", @@ -16,7 +15,7 @@ "terser": "^3.11.0" }, "scripts": { - "test": "mocha test/cursor.coffee test/test.coffee test/immutable.coffee", + "test": "mocha test/cursor.js test/test.js test/immutable.js", "fuzzer": "node test/fuzzer.js", "prepare": "terser -d process.env.JSON1_RELEASE_MODE=true -c pure_funcs=log,keep_fargs=false,passes=2 -b < lib/json1.js > lib/json1.release.js" }, diff --git a/test/cursor.coffee b/test/cursor.coffee deleted file mode 100644 index 113803c..0000000 --- a/test/cursor.coffee +++ /dev/null @@ -1,76 +0,0 @@ -{writeCursor, readCursor} = require '../lib/cursor' -assert = require 'assert' - -data = require('fs').readFileSync(__dirname + '/ops.json', 'utf8').split('\n').filter((x) -> x != '').map(JSON.parse) - -describe 'cursors', -> - describe 'writeCursor duplicates', -> - test = (op) -> - w = writeCursor() - - f = (l) -> - assert Array.isArray l - depth = 0 - for c in l - if typeof c in ['string', 'number'] - depth++ - w.descend c - else if Array.isArray c - f c - else if typeof c is 'object' - for k, v of c - w.write k, v - - w.ascend() for [0...depth] - - f op if op != null - assert.deepEqual op, w.get() - - for d in data - do (d) -> it "#{JSON.stringify d}", -> test d - - describe 'copy using read cursors', -> - test = (op) -> - r = readCursor op - w = writeCursor() - path = [] - do f = -> - if component = r.getComponent() - # console.log 'component', component - w.write k, v for k, v of component - - assert.deepStrictEqual r.getPath(), path - assert.deepStrictEqual w.getPath(), path - - for k from r - path.push k - w.descend k - f() - w.ascend() - path.pop() - - assert.deepEqual op, w.get() - # console.log op - # console.log w.get() - for d in data - do (d) -> it "#{JSON.stringify d}", -> test d - - describe 'fuzzer', -> - - it 'cleans up position after mergeTree', -> - a = [ 1, 'c', { d: 1 } ] - w = writeCursor(a) - - w.descend(0) - - w.descend('a') - w.write('p', 1) - w.ascend() - w.ascend() - - w.descend(1) - w.mergeTree([{r:true}]) - w.descend('b') - w.write('p', 0) # Crash! - w.ascend() - w.ascend() diff --git a/test/cursor.js b/test/cursor.js new file mode 100644 index 0000000..26764e7 --- /dev/null +++ b/test/cursor.js @@ -0,0 +1,114 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const {writeCursor, readCursor} = require('../lib/cursor'); +const assert = require('assert'); + +const data = require('fs').readFileSync(__dirname + '/ops.json', 'utf8').split('\n').filter(x => x !== '').map(JSON.parse); + +describe('cursors', function() { + describe('writeCursor duplicates', function() { + const test = function(op) { + const w = writeCursor(); + + var f = function(l) { + assert(Array.isArray(l)); + let depth = 0; + for (let c of Array.from(l)) { + if (['string', 'number'].includes(typeof c)) { + depth++; + w.descend(c); + } else if (Array.isArray(c)) { + f(c); + } else if (typeof c === 'object') { + for (let k in c) { + const v = c[k]; + w.write(k, v); + } + } + } + + return __range__(0, depth, false).map((i) => w.ascend()); + }; + + if (op !== null) { f(op); } + return assert.deepEqual(op, w.get()); + }; + + return Array.from(data).map((d) => + (d => it(`${JSON.stringify(d)}`, () => test(d)))(d)); + }); + + describe('copy using read cursors', function() { + const test = function(op) { + let f; + const r = readCursor(op); + const w = writeCursor(); + const path = []; + (f = function() { + let component, k; + if (component = r.getComponent()) { + // console.log 'component', component + for (k in component) { const v = component[k]; w.write(k, v); } + } + + assert.deepStrictEqual(r.getPath(), path); + assert.deepStrictEqual(w.getPath(), path); + + return (() => { + const result = []; + for (k of r) { + path.push(k); + w.descend(k); + f(); + w.ascend(); + result.push(path.pop()); + } + return result; + })(); + })(); + + return assert.deepEqual(op, w.get()); + }; + // console.log op + // console.log w.get() + return Array.from(data).map((d) => + (d => it(`${JSON.stringify(d)}`, () => test(d)))(d)); + }); + + return describe('fuzzer', () => + + it('cleans up position after mergeTree', function() { + const a = [ 1, 'c', { d: 1 } ]; + const w = writeCursor(a); + + w.descend(0); + + w.descend('a'); + w.write('p', 1); + w.ascend(); + w.ascend(); + + w.descend(1); + w.mergeTree([{r:true}]); + w.descend('b'); + w.write('p', 0); // Crash! + w.ascend(); + return w.ascend(); + }) + ); +}); + +function __range__(left, right, inclusive) { + let range = []; + let ascending = left < right; + let end = !inclusive ? right : ascending ? right + 1 : right - 1; + for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { + range.push(i); + } + return range; +} \ No newline at end of file diff --git a/test/genOp.coffee b/test/genOp.coffee deleted file mode 100644 index 63215c6..0000000 --- a/test/genOp.coffee +++ /dev/null @@ -1,215 +0,0 @@ -{randomInt, randomReal, randomWord} = require 'ot-fuzzer' -# require 'ot-fuzzer' - -assert = require 'assert' -{writeCursor} = require '../lib/cursor' -log = require '../lib/log' -{type} = require '../lib/json1' - - -# This is an awful function to clone a document snapshot for use by the random -# op generator. .. Since we don't want to corrupt the original object with -# the changes the op generator will make. -clone = (o) -> if typeof o is 'object' then JSON.parse(JSON.stringify(o)) else o - -randomKey = (obj) -> # this works on arrays too! - if Array.isArray obj - if obj.length is 0 - return undefined - else - return randomInt obj.length - else - keys = Object.keys obj - if keys.length is 0 - return undefined - else - return keys[randomInt keys.length] - -letters = 'abxyz' - -# Generate a random new key for a value in obj. -randomNewKey = (obj) -> - if Array.isArray obj - return randomInt obj.length + 1 - else - loop - # Mostly try just use a small letter - key = if randomInt(20) == 0 then randomWord() else letters[randomInt(letters.length)] - break if obj[key] == undefined - return key - -# Generate a random object -randomThing = -> - switch randomInt 7 - when 0 then null - when 1 then '' - when 2 then randomWord() - when 3 - obj = {} - obj[randomNewKey(obj)] = randomThing() for [1..randomInt(2)] - obj - when 4 then (randomThing() for [1..randomInt(2)]) - when 5 then 0 # bias toward zeros to find accidental truthy checks - when 6 then randomInt(50) - -# Pick a random path to something in the object. -randomPath = (data) -> - return [] if !data? or typeof data != 'object' - - path = [] - while randomReal() < 0.9 and data? and typeof data == 'object' - key = randomKey data - break unless key? - - path.push key - data = data[key] - - path - -randomWalkPick = (w, container) -> - path = randomPath container.data - parent = container - key = 'data' - #log 'rwp', container, path, parent - - for p in path - parent = parent[key] - key = p - w.descend key - operand = parent[key] - return [path, parent, key, operand] - -# Returns [path, parent, key] if we can drop here, or null if no drop is -# possible -randomWalkDrop = (w, container) -> - if container.data == undefined - return [[], container, 'data'] - else if typeof container.data != 'object' or container.data == null - return null # Can't insert into a document that is a string or number - - [path, parent, key, operand] = randomWalkPick w, container - if typeof operand == 'object' and operand != null - parent = operand - else - w.ascend() - w.reset() - path.pop() - key = randomNewKey parent - # log 'key', key - return [path, parent, key] - -set = (container, key, value) -> - if value == undefined - if Array.isArray container - container.splice key, 1 - else - delete container[key] - else - if Array.isArray container - container.splice key, 0, value - else - container[key] = value - -genRandomOpPart = (data) -> - # log 'genRandomOp', data - - container = {data} - w = writeCursor() - - # Remove something - - # Things we can do: - # 0. remove something - # 1. move something - # 2. insert something - # 3. edit a string - mode = if data == undefined and randomReal() < 0.9 then 2 else randomInt 4 - #mode = 1 - #log 'mode', mode - switch mode - when 0, 1 - [path, parent, key, operand] = randomWalkPick w, container - #log 'ppko', path, parent, key, operand - if mode is 1 and typeof operand is 'string' - # Edit it! - genString = require 'ot-text/test/genOp' - [stringOp, result] = genString operand - w.write 'es', stringOp - parent[key] = result - else if mode is 1 and typeof operand is 'number' - increment = randomInt 10 - w.write 'ena', increment - parent[key] += increment - else - # Remove it - if operand != undefined - w.write 'r', true #operand - set parent, key, undefined - - when 2 - # insert something - walk = randomWalkDrop w, container - if walk != null - [path, parent, key] = walk - #log 'walk', walk - val = randomThing() - w.descend key if parent != container - w.write 'i', val - set parent, key, clone val - - when 3 - # Move something. We'll pick up the current operand... - [path1, parent1, key1, operand] = randomWalkPick w, container - if operand != undefined - set parent1, key1, undefined # remove it from the result... - - if parent1 == container # We're removing the whole thing. - w.write 'r', true - else - w.write 'p', 0 - - # ... and find another place to insert it! - w.ascend() for [0...path1.length] - w.reset() - [path2, parent2, key2] = randomWalkDrop w, container - w.descend key2 - set parent2, key2, operand - w.write 'd', 0 - - doc = container.data - op = w.get() - - type.checkValidOp op - - # assert.deepEqual doc, type.apply clone(data), op - - return [op, doc] - -module.exports = genRandomOp = (doc) -> - doc = clone doc - - # 90% chance of adding an op the first time through, then 50% each successive time. - chance = 0.99 - - op = null # Aggregate op - - while randomReal() < chance - [opc, doc] = genRandomOpPart(doc) - log opc - # type.setDebug false - op = type.compose op, opc - - chance = 0.5 - - # log.quiet = false - log '-> generated op', op, 'doc', doc - return [op, doc] - -if require.main == module - # log genRandomOp {} - # log genRandomOp({x:5, y:7, z:[1,2,3]}) for [1..10] - for [1..10] - type.debug = true - log.quiet = false - log genRandomOp({x:"hi", y:'omg', z:[1,'whoa',3]}) - # log genRandomOp(undefined) diff --git a/test/genOp.js b/test/genOp.js new file mode 100644 index 0000000..d716901 --- /dev/null +++ b/test/genOp.js @@ -0,0 +1,271 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS202: Simplify dynamic range loops + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let genRandomOp; +const {randomInt, randomReal, randomWord} = require('ot-fuzzer'); +// require 'ot-fuzzer' + +const assert = require('assert'); +const {writeCursor} = require('../lib/cursor'); +const log = require('../lib/log'); +const {type} = require('../lib/json1'); + + +// This is an awful function to clone a document snapshot for use by the random +// op generator. .. Since we don't want to corrupt the original object with +// the changes the op generator will make. +const clone = function(o) { if (typeof o === 'object') { return JSON.parse(JSON.stringify(o)); } else { return o; } }; + +const randomKey = function(obj) { // this works on arrays too! + if (Array.isArray(obj)) { + if (obj.length === 0) { + return undefined; + } else { + return randomInt(obj.length); + } + } else { + const keys = Object.keys(obj); + if (keys.length === 0) { + return undefined; + } else { + return keys[randomInt(keys.length)]; + } + } +}; + +const letters = 'abxyz'; + +// Generate a random new key for a value in obj. +const randomNewKey = function(obj) { + if (Array.isArray(obj)) { + return randomInt(obj.length + 1); + } else { + let key; + while (true) { + // Mostly try just use a small letter + key = randomInt(20) === 0 ? randomWord() : letters[randomInt(letters.length)]; + if (obj[key] === undefined) { break; } + } + return key; + } +}; + +// Generate a random object +var randomThing = function() { + switch (randomInt(7)) { + case 0: return null; + case 1: return ''; + case 2: return randomWord(); + case 3: + var obj = {}; + for (let i = 1, end = randomInt(2), asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { obj[randomNewKey(obj)] = randomThing(); } + return obj; + case 4: return (__range__(1, randomInt(2), true).map((j) => randomThing())); + case 5: return 0; // bias toward zeros to find accidental truthy checks + case 6: return randomInt(50); + } +}; + +// Pick a random path to something in the object. +const randomPath = function(data) { + if ((data == null) || (typeof data !== 'object')) { return []; } + + const path = []; + while ((randomReal() < 0.9) && (data != null) && (typeof data === 'object')) { + const key = randomKey(data); + if (key == null) { break; } + + path.push(key); + data = data[key]; + } + + return path; +}; + +const randomWalkPick = function(w, container) { + const path = randomPath(container.data); + let parent = container; + let key = 'data'; + //log 'rwp', container, path, parent + + for (let p of Array.from(path)) { + parent = parent[key]; + key = p; + w.descend(key); + } + const operand = parent[key]; + return [path, parent, key, operand]; +}; + +// Returns [path, parent, key] if we can drop here, or null if no drop is +// possible +const randomWalkDrop = function(w, container) { + if (container.data === undefined) { + return [[], container, 'data']; + } else if ((typeof container.data !== 'object') || (container.data === null)) { + return null; // Can't insert into a document that is a string or number + } + + let [path, parent, key, operand] = Array.from(randomWalkPick(w, container)); + if ((typeof operand === 'object') && (operand !== null)) { + parent = operand; + } else { + w.ascend(); + w.reset(); + path.pop(); + } + key = randomNewKey(parent); + // log 'key', key + return [path, parent, key]; +}; + +const set = function(container, key, value) { + if (value === undefined) { + if (Array.isArray(container)) { + return container.splice(key, 1); + } else { + return delete container[key]; + } + } else { + if (Array.isArray(container)) { + return container.splice(key, 0, value); + } else { + return container[key] = value; + } + } +}; + +const genRandomOpPart = function(data) { + // log 'genRandomOp', data + + let key1, parent1, path1; + const container = {data}; + const w = writeCursor(); + + // Remove something + + // Things we can do: + // 0. remove something + // 1. move something + // 2. insert something + // 3. edit a string + const mode = (data === undefined) && (randomReal() < 0.9) ? 2 : randomInt(4); + //mode = 1 + //log 'mode', mode + switch (mode) { + case 0: case 1: + var [path, parent, key, operand] = Array.from(randomWalkPick(w, container)); + //log 'ppko', path, parent, key, operand + if ((mode === 1) && (typeof operand === 'string')) { + // Edit it! + const genString = require('ot-text/test/genOp'); + const [stringOp, result] = Array.from(genString(operand)); + w.write('es', stringOp); + parent[key] = result; + } else if ((mode === 1) && (typeof operand === 'number')) { + const increment = randomInt(10); + w.write('ena', increment); + parent[key] += increment; + } else { + // Remove it + if (operand !== undefined) { + w.write('r', true); //operand + set(parent, key, undefined); + } + } + break; + + case 2: + // insert something + var walk = randomWalkDrop(w, container); + if (walk !== null) { + [path, parent, key] = Array.from(walk); + //log 'walk', walk + const val = randomThing(); + if (parent !== container) { w.descend(key); } + w.write('i', val); + set(parent, key, clone(val)); + } + break; + + case 3: + // Move something. We'll pick up the current operand... + [path1, parent1, key1, operand] = Array.from(randomWalkPick(w, container)); + if (operand !== undefined) { + set(parent1, key1, undefined); // remove it from the result... + + if (parent1 === container) { // We're removing the whole thing. + w.write('r', true); + } else { + w.write('p', 0); + + // ... and find another place to insert it! + for (let i = 0, end = path1.length, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) { w.ascend(); } + w.reset(); + const [path2, parent2, key2] = Array.from(randomWalkDrop(w, container)); + w.descend(key2); + set(parent2, key2, operand); + w.write('d', 0); + } + } + break; + } + + const doc = container.data; + const op = w.get(); + + type.checkValidOp(op); + + // assert.deepEqual doc, type.apply clone(data), op + + return [op, doc]; +}; + +module.exports = (genRandomOp = function(doc) { + doc = clone(doc); + + // 90% chance of adding an op the first time through, then 50% each successive time. + let chance = 0.99; + + let op = null; // Aggregate op + + while (randomReal() < chance) { + let opc; + [opc, doc] = Array.from(genRandomOpPart(doc)); + log(opc); + // type.setDebug false + op = type.compose(op, opc); + + chance = 0.5; + } + + // log.quiet = false + log('-> generated op', op, 'doc', doc); + return [op, doc]; +}); + +if (require.main === module) { + // log genRandomOp {} + // log genRandomOp({x:5, y:7, z:[1,2,3]}) for [1..10] + for (let i = 1; i <= 10; i++) { + type.debug = true; + log.quiet = false; + log(genRandomOp({x:"hi", y:'omg', z:[1,'whoa',3]})); + } +} + // log genRandomOp(undefined) + +function __range__(left, right, inclusive) { + let range = []; + let ascending = left < right; + let end = !inclusive ? right : ascending ? right + 1 : right - 1; + for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { + range.push(i); + } + return range; +} \ No newline at end of file diff --git a/test/immutable.coffee b/test/immutable.coffee deleted file mode 100644 index 0f9522d..0000000 --- a/test/immutable.coffee +++ /dev/null @@ -1,50 +0,0 @@ -assert = require 'assert' -{type} = require '../lib/json1' -log = require '../lib/log' -genOp = require './genOp' -deepClone = require '../lib/deepClone' - -# This tests that none of apply / compose / transform / genOp mutate their -# input - -describe 'immutable guarantees', -> - origDoc = {x:"hi", y:'omg', z:[1,'whoa',3]} - expectDoc = deepClone origDoc - n = 1000 - this.slow n*10 - - it 'apply does not mutate', -> - for [1..n] - [op, doc] = genOp origDoc - assert.deepStrictEqual origDoc, expectDoc - - expectOp = deepClone op - type.apply origDoc, op - - assert.deepStrictEqual origDoc, expectDoc - assert.deepStrictEqual op, expectOp - - it 'compose does not mutate', -> - for [1..n] - [op1, doc] = genOp origDoc - [op2, doc] = genOp doc - - expectOp1 = deepClone op1 - expectOp2 = deepClone op2 - type.compose op1, op2 - - assert.deepStrictEqual op1, expectOp1 - assert.deepStrictEqual op2, expectOp2 - - it 'transform does not mutate', -> - for [1..n] - [op1, doc1] = genOp origDoc - [op2, doc2] = genOp origDoc - - expectOp1 = deepClone op1 - expectOp2 = deepClone op2 - - type.transformNoConflict op1, op2, 'left' - type.transformNoConflict op2, op1, 'right' - assert.deepStrictEqual op1, expectOp1 - assert.deepStrictEqual op2, expectOp2 diff --git a/test/immutable.js b/test/immutable.js new file mode 100644 index 0000000..b262d2f --- /dev/null +++ b/test/immutable.js @@ -0,0 +1,78 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS202: Simplify dynamic range loops + * DS205: Consider reworking code to avoid use of IIFEs + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const assert = require('assert'); +const {type} = require('../lib/json1'); +const log = require('../lib/log'); +const genOp = require('./genOp'); +const deepClone = require('../lib/deepClone'); + +// This tests that none of apply / compose / transform / genOp mutate their +// input + +describe('immutable guarantees', function() { + const origDoc = {x:"hi", y:'omg', z:[1,'whoa',3]}; + const expectDoc = deepClone(origDoc); + const n = 1000; + this.slow(n*10); + + it('apply does not mutate', () => + (() => { + const result = []; + for (let i = 1, end = n, asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { + const [op, doc] = Array.from(genOp(origDoc)); + assert.deepStrictEqual(origDoc, expectDoc); + + const expectOp = deepClone(op); + type.apply(origDoc, op); + + assert.deepStrictEqual(origDoc, expectDoc); + result.push(assert.deepStrictEqual(op, expectOp)); + } + return result; + })() + ); + + it('compose does not mutate', () => + (() => { + const result = []; + for (let i = 1, end = n, asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { + let op2; + let [op1, doc] = Array.from(genOp(origDoc)); + [op2, doc] = Array.from(genOp(doc)); + + const expectOp1 = deepClone(op1); + const expectOp2 = deepClone(op2); + type.compose(op1, op2); + + assert.deepStrictEqual(op1, expectOp1); + result.push(assert.deepStrictEqual(op2, expectOp2)); + } + return result; + })() + ); + + return it('transform does not mutate', () => + (() => { + const result = []; + for (let i = 1, end = n, asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { + const [op1, doc1] = Array.from(genOp(origDoc)); + const [op2, doc2] = Array.from(genOp(origDoc)); + + const expectOp1 = deepClone(op1); + const expectOp2 = deepClone(op2); + + type.transformNoConflict(op1, op2, 'left'); + type.transformNoConflict(op2, op1, 'right'); + assert.deepStrictEqual(op1, expectOp1); + result.push(assert.deepStrictEqual(op2, expectOp2)); + } + return result; + })() + ); +}); diff --git a/test/test.coffee b/test/test.coffee deleted file mode 100644 index 8d550ec..0000000 --- a/test/test.coffee +++ /dev/null @@ -1,2268 +0,0 @@ -# Unit tests for the JSON1 OT type. -# -# These tests are quite unstructured. You can see the skeletons of a few -# organizing systems, but ultimately there's just lots of test cases to run. -# -# Cleanups welcome, so long as you don't remove any tests. - -assert = require 'assert' -# {type} = require '../index' -{type} = require '../lib/json1' -log = require '../lib/log' -deepClone = require '../lib/deepClone' - -{transform} = type -{DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE} = type - -apply = ({doc:snapshot, op, expect}) -> - type.setDebug(false) - - orig = deepClone snapshot - try - result = type.apply snapshot, op - assert.deepStrictEqual snapshot, orig, 'Original snapshot was mutated' - assert.deepStrictEqual result, expect - catch e - console.log "Apply failed! Repro apply( #{JSON.stringify(snapshot)}, #{JSON.stringify(op)} )" - console.log "expected output: #{JSON.stringify(expect)}" - throw e - -d = (fn) -> - type.setDebug(true) - fn() - type.setDebug(false) - -compose = ({op1, op2, expect}) -> - try - result = type.compose op1, op2 - assert.deepStrictEqual result, expect - catch e - d -> - console.error 'FAIL! Repro with:' - console.log "compose( #{JSON.stringify(op1)}, #{JSON.stringify(op2)} )" - console.log "expected output: #{JSON.stringify(expect)}" - type.compose op1, op2 - throw e - -invConflict = ({type, op1, op2}) -> {type, op1:op2, op2:op1} - - - -otherSide = (side) -> if side == 'left' then 'right' else 'left' -checkConflict = ({op1, op2, side, conflict: expectConflict, expect}) -> - # We should get the same conflict with xf(op1, op2, left) and xf(op2, op1, right). - if expectConflict? - expectConflict.op1 = type.normalize(op1) if !expectConflict.op1 - expectConflict.op2 = type.normalize(op2) if !expectConflict.op2 - - for [side_, op1_, op2_, ec] in [ - [side, op1, op2, expectConflict], - [otherSide(side), op2, op1, if expectConflict then invConflict(expectConflict) else null] - ] - try - - # d -> log('tryTransform', side_, op1_, op2_) - {ok, conflict} = type.tryTransform op1_, op2_, side_ - if !ec? - # We don't care what the result is here; just that it doesn't conflict. - assert ok - else - assert !ok, "Conflict erroneously succeeded (#{side_})" - # d -> log('conflict', conflict) - conflict.op1 = type.normalize conflict.op1 - conflict.op2 = type.normalize conflict.op2 - assert.deepStrictEqual conflict, ec - catch e - d -> - console.error 'FAIL! Repro with:' - console.log "tryTransform(#{JSON.stringify(op1_)}, #{JSON.stringify(op2_)}, '#{side_}')" - type.tryTransform op1_, op2_, side_ - throw e - -xf = ({op1, op2, conflict, conflictLeft, conflictRight, expect, expectLeft, expectRight}) -> - if expect != undefined then expectLeft = expectRight = expect - if conflict != undefined then conflictLeft = conflictRight = conflict - - for [side, e, c] in [['left', expectLeft, conflictLeft], ['right', expectRight, conflictRight]] - checkConflict {op1, op2, side, conflict: c, expect: e} - - try - result = if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side - assert.deepStrictEqual result, e - catch e - d -> - console.error 'FAIL! Repro with:' - console.log "transform(#{JSON.stringify(op1)}, #{JSON.stringify(op2)}, '#{side}')" - # if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side - throw e - - - -diamond = ({doc, op1, op2}) -> - type.setDebug(false) - - try - # Test that the diamond property holds - op1_ = transform op1, op2, 'left' - op2_ = transform op2, op1, 'right' - - doc1 = type.apply doc, op1 - doc2 = type.apply doc, op2 - - doc12 = type.apply doc1, op2_ - doc21 = type.apply doc2, op1_ - - assert.deepStrictEqual doc12, doc21 - catch e - log.quiet = false - log '\nOops! Diamond property does not hold. Given document', doc - log 'op1 ', op1, ' / op2', op2 - log 'op1_', op1_, ' / op2_', op2_ - log '---- 1' - log 'op1', op1, '->', doc1 - log 'op2', op2_, '->', doc12 - log '---- 2' - log 'op2', op2, '->', doc2 - log 'op1', op1_, '->', doc21 - log '----------' - log doc12, '!=', doc21 - throw e - - -path = (path, {op, expect}) -> - expect = path.slice() if expect == undefined - - result = type.transformPosition path, op - assert.deepStrictEqual result, expect - - # Also check that path+X = expect+X - path2 = path.concat 'x' - expect2 = if expect? then expect.concat('x') else null - - result2 = type.transformPosition path2, op - assert.deepStrictEqual result2, expect2 - - -describe 'json1', -> - before -> - type.registerSubtype require 'ot-simple' - type.setDebug(true) - after -> type.setDebug(false) - - describe 'checkOp', -> - pass = (op) -> - try - type.checkValidOp op - catch e - console.log("FAIL! Repro with:\ncheckOp( #{JSON.stringify(op)} )") - throw e - - fail = (op) -> - try - assert.throws -> type.checkValidOp op - catch e - console.log("FAIL! Repro with:\ncheckOp( #{JSON.stringify(op)} )") - console.log('Should throw!') - throw e - - it 'allows some simple valid ops', -> - pass null - pass [{i:[1,2,3]}] - pass [{r:{}}] - pass [['x',{p:0}], ['y',{d:0}]] - pass [[0,{p:0}], [10,{d:0}]] - pass [['a',{p:0}],['b',{d:0}],['x',{p:1}],['y',{d:1}]] - pass [{e:"hi", et:'simple'}] - pass [{es:["hi"]}] - pass [{ena:5}] - - it 'disallows invalid syntax', -> - fail undefined - fail {} - fail "hi" - fail true - fail false - fail 0 - fail 10 - fail [{}] - fail [{invalid:true}] - fail [10, {}] - fail [10, {invalid:true}] - fail [10, 'hi'] - - it 'throws if there is any empty leaves', -> - fail [] - fail ['x'] - fail ['x', {}] - fail ['x', []] - fail [10] - fail [10, {}] - fail [10, []] - - it 'ensures path components are non-zero integers or strings', -> - fail [-1, {r:{}}] - fail [0.5, {r:{}}] - fail [true, {r:{}}] - fail [false, {r:{}}] - fail [null, {r:{}}] - fail [undefined, {r:{}}] - - it 'does not allow two pickups or two drops in a component', -> - fail [{p:0, r:{}}] - fail [{p:1, r:{}}] - fail ['x', {p:0, r:{}}] - fail ['x', {p:1, r:{}}] - - fail [{d:0, i:'hi'}] - fail [{d:1, i:'hi'}] - fail [10, {d:0, i:'hi'}] - fail [10, {d:1, i:'hi'}] - - it 'throws if there are mismatched pickups / drops', -> - fail [{p:0}] - fail [{d:0}] - fail ['x', {p:0}] - fail [10, {p:0}] - fail ['x', {d:0}] - fail [10, {d:0}] - - it 'throws if pick/drop indexes dont start at 0', -> - fail [['x', {p:1}], ['y', {d:1}]] - fail [[10, {p:1}], [20, {d:1}]] - - it 'throws if a descent starts with an edit', -> - fail [10, [{i:"hi"}]] - - it 'throws if descents are out of order', -> - fail ['x', ['b', {r:{}}], ['a', {r:{}}]] - fail ['x', [10, {r:{}}], [5, {r:{}}]] - fail ['x', ['a', {r:{}}], [5, {r:{}}]] - fail ['x', ['a', {r:{}}], ['a', {r:{}}]] - fail ['x', [10, {r:{}}], [10, {r:{}}]] - - it 'throws if descents start with the same scalar', -> - fail ['x', ['a', {r:{}}], ['a', {e:{}}]] - - it 'throws if descents have two adjacent edits', -> - fail [{r:{}}, {p:0}] - fail ['x', {r:{}}, {p:0}] - fail ['x', {r:{}}, {p:0}, 'y', {r:{}}] - - it.skip 'does not allow ops to overwrite their own inserted data', -> - fail [{i:{x:5}}, 'x', {i:6}] - fail [{i:['hi']}, 0, {i:'omg'}] - - it.skip 'does not allow immediate data directly parented in other immediate data', -> - fail [{i:{}}, 'x', {i:5}] - fail [{i:{x:5}}, 'x', 'y', {i:6}] - fail [{i:[]}, 0, {i:5}] - - it 'does not allow the final item to be a single descent', -> - fail ['a', ['b', r:{}]] # It should be ['a', 'b', r:{}] - - it 'does not allow anything after the descents at the end', -> - fail [[1, r:{}], [2, r:{}], 5] - fail [[1, r:{}], [2, r:{}], 5, r:{}] - fail [[1, r:{}], [2, r:{}], r:{}] - - it 'allows removes inside removes', -> - pass ['x', r:true, 'y', r:true] - pass ['x', r:{}, 'y', r:true] - pass [['x', r:true, 'y', p:0, 'z', r:true], ['y', d:0]] - pass [['x', r:{}, 'y', p:0, 'z', r:true], ['y', d:0]] - - it 'allows inserts inside inserts', -> - pass [1, i:{}, 'x', i:10] - pass [[0, 'x', p:0], [1, i:{}, 'x', d:0, 'y', i:10]] - - it.skip 'fails if the operation drops items inside something it picked up', -> - fail ['x', r:true, 1, i:'hi'] - fail ['x', d:0, 1, p:0] - fail [r:true, 1, p:0, d:0] - - describe 'edit', -> - it 'requires all edits to specify their type', -> - fail [{e:{}}] - fail [5, {e:{}}] - pass [{e:{}, et:'simple'}] - - it 'allows edits to have null or false for the operation', -> - # These aren't valid operations according to the simple type, but the - # type doesn't define a checkValidOp so we wouldn't be able to tell - # anyway. - pass [{e:null, et:'simple'}] - pass [5, {e:null, et:'simple'}] - pass [{e:false, et:'simple'}] - pass [5, {e:false, et:'simple'}] - - it 'does not allow an edit to use an unregistered type', -> - fail [{e:{}, et:'an undefined type'}] - fail [{e:null, et:'an undefined type'}] - - it 'does not allow two edits in the same operation', -> - fail [{e:{}, et:'simple', es:[1,2,3]}] - fail [{es:[], ena:5}] - fail [{e:{}, et:'simple', ena:5}] - - it 'fails if the type is missing', -> - fail [et:'missing', e:{}] - - it 'does not allow anything inside an edited subtree' - - it.skip 'does not allow an edit inside removed or picked up content', -> - fail [r:true, 1, es:['hi']] - pass [1, r:true, 1, es:['hi']] - fail ['x', r:true, 1, es:['hi']] - pass [[1, p:0, 1, es:['hi']], [2, d:0]] - fail [['x', p:0, 1, es:['hi']], ['y', d:0]] - - # This is actually ok. - pass [ 0, { p: 0 }, [ 'a', { es: [], r: true } ], [ 'x', { d: 0 } ] ] - - it.skip 'does not allow you to drop inside something that was removed', -> - # These insert into the next list item - pass [[1, r:true, 1, d:0], [2, p:0]] - pass [1, {p: 0}, 'x', {d: 0}] - - # But this is not ok. - fail ['x', {p:0}, 'a', {d:0}] - - describe 'normalize', -> - n = (opIn, expect) -> - expect = opIn if expect == undefined - op = type.normalize opIn - assert.deepStrictEqual op, expect - - it 'does the right thing for noops', -> - n null - n [], null - - it 'normalizes some regular ops', -> - n [{i:'hi'}] - n [{i:'hi'}, 1,2,3], [{i:'hi'}] - n [[1,2,3, {p:0}], [1,2,3, {d:0}]], [1,2,3, {p:0, d:0}] - n [[1,2,3, {p:0}], [1,2,30, {d:0}]], [1,2, [3, {p:0}], [30, {d:0}]] - n [[1,2,30, {p:0}], [1,2,3, {d:0}]], [1,2, [3, {d:0}], [30, {p:0}]] - - it 'will let you insert null', -> - n [{i:null}] - - it 'normalizes embedded ops when available', -> - n [{es:[0, 'hi']}], [{es:['hi']}] - n [{et:'text-unicode', e:['hi']}], [{es:['hi']}] - n [{et:'text-unicode', e:[0, 'hi']}], [{es:['hi']}] - n [{et:'simple', e:{}}] - n [{et:'number', e:5}], [{ena:5}] - n [{ena:5}] - - it.skip 'normalizes embedded removes', -> - n [1, {r:true}, 2, {r:true}], [1, {r:true}] - n [{r:true}, 2, {r:true}], [{r:true}] - - it 'throws if the type is missing', -> - # Not sure if this is the best behaviour but ... eh. - assert.throws -> n [et:'missing', e:{}] - - it 'corrects weird pick and drop ids', -> - n [['x', p:1], ['y', d:1]], [['x', p:0], ['y', d:0]] - -# ****** Apply ****** - - describe 'apply', -> - it 'Can set properties', -> - apply - doc: [] - op: [0, {i:17}] - expect: [17] - - apply - doc: {} - op: ['x', {i:5}] - expect: {x:5} - - it 'can edit the root', -> - apply - doc: {x:5} - op: [r:true] - expect: undefined - - apply - doc: '' - op: [r:true] - expect: undefined - - apply - doc: 'hi' - op: [r:true, i:null] - expect: null - - apply - doc: 'hi' - op: [es:[2, ' there']] - expect: 'hi there' - - assert.throws -> type.apply null, [{i:5}] - - apply - doc: undefined - op: [i:5] - expect: 5 - - apply - doc: {x:5} - op: [r:{}, i:[1,2,3]] - expect: [1,2,3] - - # TODO: And an edit of the root. - - it 'can move 1', -> apply - doc: {x:5} - op: [['x', p:0], ['y', d:0]] - expect: {y:5} - - it 'can move 2', -> apply - doc: [0,1,2] - op: [[1, p:0], [2, d:0]] - expect: [0,2,1] - - it 'can handle complex list index stuff', -> apply - doc: [0,1,2,3,4,5] - op: [[1, r:{}, i:11], [2, r:{}, i:12]] - expect: [0,11,12,3,4,5] - - it 'correctly handles interspersed descent and edits', -> apply - doc: {x: {y: {was:'y'}, was:'x'}} - op: [['X',{d:0},'Y',{d:1}], ['x',{p:0},'y',{p:1}]] - expect: {X: {Y: {was:'y'}, was:'x'}} - - it 'can edit strings', -> apply - doc: "errd" - op: [{es:[2,"maghe"]}] - expect: "ermagherd" - - it 'can edit numbers', -> apply - doc: 5 - op: [ena:10] - expect: 15 - - it 'can edit child numbers', -> apply - doc: [20] - op: [0, ena:-100] - expect: [-80] - - it 'can edit subdocuments using an embedded type', -> apply - doc: {str:'hai'} - op: [{e:{position:2, text:'wai'}, et:'simple'}] - expect: {str:'hawaii'} - - it 'applies edits after drops', -> apply - doc: {x: "yooo"} - op: [['x', p:0], ['y', d:0, es:['sup']]] - expect: {y: "supyooo"} - - it 'throws when the op traverses missing items', -> - assert.throws -> type.apply [0, 'hi'], [1, p:0, 'x', d:0] - assert.throws -> type.apply {}, [p:0, 'a', d:0] - - it 'throws if the type is missing', -> - assert.throws -> type.apply {}, [et:'missing', e:{}] - - - describe 'apply path', -> - it 'does not modify path when op is unrelated', -> - path ['a', 'b', 'c'], op: null - path ['a', 'b', 'c'], op: ['x', i:5] - path ['a', 'b', 'c'], op: ['x', r:true] - path ['a', 'b', 'c'], op: [['x', p:0], ['y', d:0]] - path [1,2,3], op: [2, i:5] - path [1,2,3], op: [1, 2, 4, i:5] - path [1], op: [1, 2, r:true] - path ['x'], op: ['x', 'y', r:true] - - it 'adjusts list indicies', -> - path [2], op: [1, i:5], expect: [3] - path [2], op: [2, i:5], expect: [3] - path [2], op: [1, r:true], expect: [1] - path [2], op: [[1, p:0], [3, d:0]], expect: [1] - path [2], op: [[1, d:0], [3, p:0]], expect: [3] - path [2], op: [[2, d:0], [3, p:0]], expect: [3] - - it 'returns null when the object at the path was removed', -> - path ['x'], op: [r:true], expect: null - path ['x'], op: ['x', r:true], expect: null - path [1], op: [r:true], expect: null - path [1], op: [1, r:true], expect: null - - it 'moves the path', -> - path ['a', 'z'], op: [['a', p:0], ['y', d:0]], expect: ['y', 'z'] - path ['a', 'b'], op: [['a', 'b', p:0], ['z', d:0]], expect: ['z'] - path ['a', 'b'], op: [['a', 'b', 'c', p:0], ['z', d:0]] - path [1,2], op: [[1, p:0], [10, d:0]], expect: [10, 2] - path [1,2], op: [[1, 2, p:0], [10, d:0]], expect: [10] - path [1,2], op: [1, [1, d:0], [2, p:0]], expect: [1, 1] - path [1,2], op: [[1, 2, 3, p:0], [10, d:0]] - - it 'handles pick parent and move', -> - path ['a', 'b', 'c'], op: [['a', r:true, 'b', p:0], ['x', d:0]], expect: ['x', 'c'] - - it 'adjusts indicies under a pick', -> - path ['a', 'b', 10], op: [['a', p:0, 'b', 1, r:true], ['x', d:0]], expect: ['x', 'b', 9] - - it.skip 'gen ops', -> - # This should do something like: - # - Generate a document - # - Generate op, a random operation - # - Generate a path to somewhere in the document and an edit we can do there -> op2 - # - Check that transform(op2, op) == op2 at transformPosition(path) or something like that. - - it 'calls transformPosition with embedded string edits if available', -> - # For embedded string operations (and other things that have - # transformPosition or transformPosition or whatever) we should call that. - path ['x','y','z', 1], op: ['x','y','z', es:['abc']], expect: ['x','y','z', 4] - path ['x','y','z', 1], op: ['x','y','z', es:['💃']], expect: ['x','y','z', 2] - path ['x','y','z'], op: ['x','y','z', es:['💃']], expect: ['x','y','z'] - - -# ******* Compose ******* - - describe 'compose', -> - it 'composes empty ops to nothing', -> compose - op1: null - op2: null - expect: null - - describe 'op1 drop', -> - it 'vs remove', -> compose - op1: [['x', p:0], ['y', d:0]] - op2: ['y', r:true] - expect: ['x', r:true] - - it 'vs remove parent', -> compose - op1: [['x', p:0], ['y', 0, d:0]] - op2: ['y', r:true] - expect: [['x', r:true], ['y', r:true]] - - it 'vs remove child', -> compose - op1: [['x', p:0], ['y', d:0]] - op2: ['y', 'a', r:true] - expect: [['x', p:0, 'a', r:true], ['y', d:0]] - - it 'vs remove and pick child', -> compose - op1: [['x', p:0], ['y', d:0]] - op2: [['y', r:true, 'a', p:0], ['z', d:0]] - expect: [['x', r:true, 'a', p:0], ['z', d:0]] - - it 'vs pick', -> compose - op1: [['x', p:0], ['y', d:0]] - op2: [['y', p:0], ['z', d:0]] - expect: [['x', p:0], ['z', d:0]] - - it 'is transformed by op2 picks', -> compose - op1: [['x', p:0], ['y', 10, d:0]] - op2: ['y', 0, r:true] - expect: [['x', p:0], ['y', [0, r:true], [9, d:0]]] - - describe 'op1 insert', -> - it 'vs remove', -> compose - op1: ['x', i:{a:'hi'}] - op2: ['x', r:true] - expect: null - - it 'vs remove parent', -> compose - op1: ['x', 0, i:{a:'hi'}] - op2: ['x', r:true] - expect: ['x', r:true] - - it 'vs remove child', -> compose - op1: ['x', i:{a:'hi', b:'woo'}] - op2: ['x', 'a', r:true] - expect: ['x', i:{b:'woo'}] - - it 'vs remove and pick child', -> compose - op1: ['x', i:{a:'hi', b:'woo'}] - op2: [['x', r:true, 'a', p:0], ['y', d:0]] - expect: ['y', i:'hi'] - - it 'vs remove an embedded insert', -> compose - op1: ['x', i:{}, 'y', i:'hi'] - op2: ['x', 'y', r:true] - expect: ['x', i:{}] - - it 'vs remove from an embedded insert', -> compose - op1: ['x', i:{}, 'y', i:[1,2,3]] - op2: ['x', 'y', 1, r:true] - expect: ['x', i:{}, 'y', i:[1, 3]] - - it 'picks the correct element of an embedded insert', -> compose - op1: ['x', i:['a', 'b', 'c'], 1, i:'XX'] - op2: [['x', 1, p:0], ['y', d:0]] - expect: [['x', i:['a', 'b', 'c']], ['y', i:'XX']] - - it 'picks the correct element of an embedded insert 2', -> compose - op1: ['x', i:['a', 'b', 'c'], 1, i:'XX'] - op2: [['x', 3, p:0], ['y', d:0]] # should grab 'c'. - expect: [['x', i:['a', 'b'], 1, i:'XX'], ['y', i:'c']] - - - it 'moves all children', -> compose - op1: ['x', i:{}, 'y', i:[1,2,3]] - op2: [['x', p:0], ['z', d:0]] - expect: ['z', i:{}, 'y', i:[1,2,3]] - - it 'removes all children', -> compose - op1: ['x', i:{}, 'y', i:[1,2,3]] - op2: ['x', r:true] - expect: null - - it 'removes all children when removed at the destination', -> compose - op1: [['x', p:0], ['y', d:0, 0, i:'hi']] - op2: ['y', r:true] - expect: ['x', r:true] - - it 'vs op2 insert', -> compose # Inserts aren't folded together. - op1: [i:{}] - op2: ['x', i:'hi'] - expect: [i:{}, 'x', i:'hi'] - - it 'vs op2 string edit', -> compose - op1: [i:'hi'] - op2: [es:[2, ' there']] - expect: [i:'hi', es:[2, ' there']] - - it 'vs op2 number edit', -> compose - op1: [i:10] - op2: [ena:20] - expect: [i:10, ena:20] - - describe 'op1 edit', -> - it 'removes the edit if the edited object is deleted', -> compose - op1: ['x', es:['hi']] - op2: ['x', r:true] - expect: ['x', r:true] - - it 'removes the edit in an embedded insert 1', -> compose - op1: ['x', i:'', es:['hi']] - op2: ['x', r:true] - expect: null - - it 'removes the edit in an embedded insert 2', -> compose - op1: ['x', i:[''], 0, es:['hi']] - op2: ['x', 0, r:true] - expect: ['x', i:[]] - - it 'composes string edits', -> compose - op1: [es:['hi']] - op2: [es:[2, ' there']] - expect: [es:['hi there']] - - it 'composes number edits', -> compose - op1: [ena:10] - op2: [ena:-8] - expect: [ena:2] - - it 'transforms and composes edits', -> compose - op1: ['x', es:['hi']] - op2: [['x', p:0], ['y', d:0, es:[2, ' there']]] - expect: [['x', p:0], ['y', d:0, es:['hi there']]] - - it 'preserves inserts with edits', -> compose - op1: ['x', i:'hi'] - op2: [['x', p:0], ['y', d:0, es:[' there']]] - expect: ['y', i:'hi', es:[' there']] - - it 'allows a different edit in the same location', -> compose - op1: ['x', es:['hi']] - op2: ['x', r:true, i:'yo', es:[2, ' there']] - expect: ['x', r:true, i:'yo', es:[2, ' there']] - - it 'throws if the type is missing', -> - assert.throws -> type.compose [et:'missing', e:{}], [et:'missing', e:{}] - - describe 'op2 pick', -> - it 'gets untransformed by op1 drops', -> - op1: [5, i:'hi'] - op2: [6, r:true] - expect: [5, r:true, i:'hi'] - - describe 'op1 insert containing a drop', -> - it 'vs pick at insert', -> compose - op1: [['x', p:0], ['y', i:{}, 'x', d:0]] - op2: [['y', p:0], ['z', d:0]] - expect: [['x', p:0], ['z', i:{}, 'x', d:0]] - - describe 'fuzzer tests', -> - it 'complicated transform of indicies', -> compose - op1: [ 0, { p: 0 }, 'x', 2, { d: 0 } ] - op2: [ 0, 'x', 0, { r: true } ] - expect: [ - [0, p:0, 'x', 1, d:0], - [1, 'x', 0, r:true] - ] - - describe 'setnull interaction', -> - # Currently failing. - it 'reorders items inside a setnull region', -> compose - op1: [{i:[]}, [0, {i:'a'}], [1, {i:'b'}]] - op2: [[0, {p:0}], [1, {d:0}]] - expect: [{i:[]}, [0, {i:'b'}], [1, {i:'a'}]] - - it 'lets a setnull child be moved', -> compose - op1: ['list', {i:[]}, 0, {i:'hi'}] - op2: [['list', 0, p:0], ['z', d:0]] - expect: [['list', {i:[]}], ['z', i:'hi']] - - it 'lets a setnull child get modified', -> compose - op1: [{i:[]}, 0, {i:['a']}] - op2: [0, 0, {r:'a', i:'b'}] - expect: [{i:[]}, 0, {i: []}, 0, {i: 'b'}] - #expect: [{i:[]}, 0, {i:['b']}] # Maybe better?? - - describe 'regression', -> - it 'skips op2 drops when calculating op1 drop index simple', -> compose - op1: [[ 0, { p: 0 } ], [ 2, { d: 0 } ]] - op2: [[ 0, { p: 0 } ], [ 1, { d: 0 } ]] - expect: [ [ 0, { p: 1 } ], [ 1, { p: 0, d: 0 } ], [ 2, { d: 1 } ] ] - - it 'skips op2 drops when calculating op1 drop index complex', -> compose - op1: [[0, {p:0, d:1}], [1, p:1], [2, d:0]] - op2: [[0, p:0], [1, d:0]] - # expect: [[0, {p:1}], [1, {d:0, p:0}], [2, d:1]] - expect: [[0, p:1], [1, {p:0, d:0}], [2, d:1]] - - it '3', -> compose - op1: [ { i: [ null, [] ] }, 0, { i: '' } ] - op2: [ 1, { p: 0 }, 0, { d: 0 } ] - # ... it'd be way more consistent to drop the null separately rather than merging it?? - expect: [ { i: [ [] ] }, [ 0, { i: '' } ], [ 1, 0, { i: null } ] ] - - it '4', -> compose # This one triggered a bug in cursor! - op1: [ 0, - [ 0, [ 'a', { r: true } ], [ 'b', { d: 0 } ] ], - [ 2, { p: 0 } ] ] - op2: [ 0, 0, 'c', { i: 'd' } ] - expect: [ 0, - [ 0, [ 'a', { r: true } ], [ 'b', { d: 0 } ], [ 'c', { i: 'd' } ] ], - [ 2, { p: 0 } ] - ] - - # *** Old stuff - describe 'old compose', -> - it 'gloms together unrelated edits', -> - compose - op1: [['a', p:0], ['b', d:0]] - op2: [['x', p:0], ['y', d:0]] - expect: [['a', p:0], ['b', d:0], ['x', p:1], ['y', d:1]] - - compose - op1: [2, i:'hi'] - op2: [0, 'x', r:true] - expect: [[0, 'x', r:true], [2, i:"hi"]] - - it 'translates drops in objects', -> compose - op1: ['x', ['a', p:0], ['b', d:0]] # x.a -> x.b - op2: [['x', p:0], ['y', d:0]] # x -> y - expect: [['x', {p:0}, 'a', {p:1}], ['y', {d:0}, 'b', {d:1}]] # x.a -> y.b, x -> y - - it 'untranslates picks in objects', -> compose - op1: [['x', p:0], ['y', d:0]] # x -> y - op2: [['y', 'a', p:0], ['z', d:0]] # y.a -> z - expect: [['x',p:0,'a',p:1], ['y',d:0], ['z',d:1]] # x.a -> z, x -> y - - it 'insert gets carried wholesale', -> compose - op1: ['x', i:'hi there'] - op2: [['x', p:0], ['y', d:0]] # x -> y - expect: ['y', i:'hi there'] - - it 'insert gets edited by the op', -> compose - op1: ['x', {i:{a:1, b:2, c:3}}] - op2: [['x', 'a', p:0], ['y', d:0]] - expect: [['x', {i:{b:2, c:3}}], ['y', i:1]] - - it 'does not merge mutual inserts', -> compose - op1: [i:{}] - op2: ['x', i:"hi"] - expect: [i:{}, 'x', i:'hi'] - - # TODO: List nonsense. - - # TODO: Edits. - - -# ****** Transform ****** - - describe 'transform', -> - describe 'op1 pick', -> - it 'vs delete', -> xf - op1: [['x', p:0], ['y', d:0]] - op2: ['x', r:true] - expect: null - it 'vs delete parent', -> xf - op1: [['x', 'a', p:0], ['y', d:0]] - op2: ['x', r:true] - expect: null - it 'vs delete parent 2', -> xf - op1: ['x', ['a', p:0], ['b', d:0]] - op2: ['x', r:true] - expect: null - - it 'vs pick', -> xf - op1: [['x', p:0], ['z', d:0]] - op2: [['x', p:0], ['y', d:0]] - # Consider adding a conflict for this case. - expectLeft: [['y', p:0], ['z', d:0]] - expectRight: null - it 'vs pick parent', -> xf - op1: [['x', 'a', p:0], ['z', d:0]] - op2: [['x', p:0], ['y', d:0]] - expect: [['y', 'a', p:0], ['z', d:0]] - - it 'vs pick and pick child', -> xf # regression - op1: [ # a -> xa, a.c -> xc - ['a', p:0, 'c', p:1] - ['xa', d:0] - ['xc', d:1] - ] - op2: [['a', p:0], ['b', d:0]] # a -> b - expectLeft: [ - ['b', p:0, 'c', p:1] - ['xa', d:0] - ['xc', d:1] - ] - expectRight: [ - ['b', 'c', p:0] - ['xc', d:0] - ] - - it 'vs edit', -> xf - op1: [['x', p:0], ['z', d:0]] - op2: ['x', es:['hi']] - expect: [['x', p:0], ['z', d:0]] - - it 'vs delete, drop', -> xf - op1: [['x', p:0], ['y', d:0]] - op2: [['a', p:0], ['x', r:0, d:0]] - expect: null - - it 'vs delete, insert', -> xf - op1: [['x', p:0], ['y', d:0]] - op2: ['x', r:0, i:5] - expect: null - - it 'vs pick, drop to self', - -> xf - op1: [['x', p:0], ['y', d:0]] - op2: [['x', p:0], ['y', d:0]] - expect: null - - -> xf - op1: [['a', 1, p:0], ['y', d:0]] - op2: [['a', 1, p:0], ['y', d:0]] - expect: null - - it 'vs pick, drop', -> xf - op1: [['x', p:0], ['z', d:0]] # x->z - op2: [['a', p:0], ['x', p:1, d:0], ['y', d:1]] # a->x, x->y - expectLeft: [['y', p:0], ['z', d:0]] - expectRight: null - - it 'vs pick, insert', -> xf - op1: [['x', p:0], ['z', d:0]] - op2: [['x', p:0, i:5], ['y', d:0]] - expectLeft: [['y', p:0], ['z', d:0]] - expectRight: null - - it 'vs pick, edit', -> - op1: [['x', p:0], ['z', d:0]] - op2: [['x', es:['hi'], p:0], ['y', d:0]] - expectLeft: [['y', p:0], ['z', d:0]] - expectRight: null - - describe 'op1 delete', -> - it 'vs delete', -> xf - op1: ['x', r:true] - op2: ['x', r:true] - expect: null - it 'vs delete parent', -> xf - op1: ['x', 'a', r:true] - op2: ['x', r:true] - expect: null - - it 'vs pick', -> xf - op1: ['x', r:true] - op2: [['x', p:0], ['y', d:0]] - expect: ['y', r:true] - it 'vs pick parent', -> xf - op1: ['x', 'a', r:true] - op2: [['x', p:0], ['y', d:0]] - expect: ['y', 'a', r:true] - - it 'vs pick and drop', -> xf - op1: ['x', r:true] - op2: [['a', p:0], ['x', d:0, p:1], ['z', d:1]] - expect: ['z', r:true] - - it 'vs edit', -> xf - op1: ['x', r:true] - op2: ['x', es:['hi']] - conflict: type: RM_UNEXPECTED_CONTENT - expect: ['x', r:true] - - it 'vs move and insert', -> xf - op1: [ 'a', 1, { r: true } ] - op2: [ - [ 'a', { p: 0 } ], - [ 'b', { d: 0 }, [ 0, { i: 5 } ], [ 1, { i: 5 } ] ] - ] - expect: ['b', 3, r:true] - - describe 'vs pick child', -> - it 'move in', -> xf - op1: ['x', r:true] - op2: [['a', p:0], ['x', 'y', d:0]] - conflict: type: RM_UNEXPECTED_CONTENT - expect: ['x', r:true, 'y', r:true] # Also ok if its just x, r:true - - it 'move across', -> xf - op1: ['x', r:true] # delete doc.x - op2: ['x', ['y', p:0], ['z', d:0]] - expect: ['x', r:true] - - it 'move out', -> xf - op1: ['x', r:true] - op2: [['x', 'y', p:0], ['y', d:0]] # move doc.x.y -> doc.y - expect: [['x', r:true], ['y', r:true]] # delete doc.x and doc.y - - it 'multiple out', -> xf - op1: ['x', r:true] - op2: [['x', 'y', p:0, 'z', p:1], ['y', d:0], ['z', d:1]] - expect: [['x', r:true], ['y', r:true], ['z', r:true]] - - it 'chain out', -> xf - op1: ['x', r:true] - op2: [['x', 'y', p:0], ['y', p:1], ['z', d:0, 'a', d:1]] - conflict: - type: RM_UNEXPECTED_CONTENT - op2: [['y', p:0], ['z', 'a', d:0]] # cMv(['y'], ['z', 'a']) - expect: [['x', r:true], ['z', r:true, 'a', r:true]] - - it 'mess', -> xf - # yeesh - op1: [['x', r:true, 'y', 'z', p:0], ['z', d:0]] - op2: [['x', 'y', p:0], ['y', d:0]] - expect: [['x', r:true], ['y', r:true, 'z', p:0], ['z', d:0]] - - describe 'op1 drop', -> - it 'vs delete parent', -> xf - op1: [['x', p:0], ['y', 'a', d:0]] - op2: ['y', r:true] - conflict: type: RM_UNEXPECTED_CONTENT - expect: ['x', r:true] - - it 'vs a cancelled parent', -> xf - # This is actually a really complicated case. - op1: [['x', 'y', p:0], ['y', p:1], ['z', d:0, 'a', d:1]] - op2: ['x', r:true] - conflict: - type: RM_UNEXPECTED_CONTENT - op1: [['y', p:0], ['z', 'a', d:0]] # c1: cMv(['y'], ['z', 'a']) - expect: ['y', r:true] - - it 'vs pick parent', -> xf - op1: [['x', p:0], ['y', 'a', d:0]] - op2: [['y', p:0], ['z', d:0]] - expect: [['x', p:0], ['z', 'a', d:0]] - - it 'vs drop', -> xf - op1: [['x', p:0], ['z', d:0]] - op2: [['y', p:0], ['z', d:0]] - conflict: type: DROP_COLLISION - expectLeft: [['x', p:0], ['z', r:true, d:0]] - expectRight: ['x', r:true] - - it 'vs drop (list)', -> xf - op1: [[0, p:0], [4, d:0]] - op2: [[5, d:0], [10, p:0]] - expectLeft: [[0, p:0], [4, d:0]] - expectRight: [[0, p:0], [5, d:0]] - - it 'vs drop (chained)', -> xf - op1: [['a', p:1], ['x', p:0], ['z', d:0, 'a', d:1]] - op2: [['y', p:0], ['z', d:0]] - conflict: - type: DROP_COLLISION - op1: [['x', p:0], ['z', d:0]] #cMv(['x'], ['z']) - expectLeft: [['a', p:0], ['x', p:1], ['z', r:true, d:1, 'a', d:0]] - expectRight: [['a', r:true], ['x', r:true]] - - it 'vs insert', -> xf - op1: [['x', p:0], ['z', d:0]] - op2: ['z', i:5] - conflict: type: DROP_COLLISION - expectLeft: [['x', p:0], ['z', r:true, d:0]] - expectRight: ['x', r:true] - - it 'vs pick (a->b->c vs b->x)', -> xf - op1: [['a', p:0], ['b', {p:1, d:0}], ['c', d:1]] - op2: [['b', p:0], ['x', d:0]] - expectLeft: [['a', p:0], ['b', d:0], ['c', d:1], ['x', p:1]] - expectRight: [['a', p:0], ['b', d:0]] - - describe.skip 'vs move inside me', -> - # Note: This is *not* blackholeing! The edits are totally fine; we - # just need one edit to win. - # The current behaviour just nukes both. - it 'in objects', -> xf - op1: [['x', p:0], ['y', 'a', d:0]] - op2: [['x', 'a', d:0], ['y', p:0]] - expectLeft: [['x', p:0, 'a', p:1], ['y', d:1, 'x', d:0]] - expectRight: null - - it 'in lists', -> xf - op1: [0, p:0, 'x', d:0] - op2: [[0, 'y', d:0], [1, p:0]] - expectLeft: [0, {p:0, d:1}, ['x', d:0], ['y', p:1]] - expectRight: null - - it 'multiple', -> xf - # a->x.a, b->x.b - op1: [['a', p:0], ['b', p:1], ['x', 'a', d:0, 'b', d:1]] - op2: [['a', 'x', d:0], ['x', p:0]] # x->a.x - expectLeft: [['a', p:0, 'x', p:1], ['b', p:2], - ['x', d:1, ['a', d:0], ['b', d:2]]] - expectRight: null - - describe 'op1 insert', -> - it 'vs delete parent', -> xf - op1: ['y', 'a', i:5] - op2: ['y', r:true] - conflict: type: RM_UNEXPECTED_CONTENT - expect: null - - it 'vs pick parent', -> xf - op1: ['y', 'a', i:5] - op2: [['y', p:0], ['z', d:0]] - expect: ['z', 'a', i:5] - - it 'vs drop', -> xf - op1: ['z', i:5] - op2: [['y', p:0], ['z', d:0]] - conflict: type: DROP_COLLISION - expectLeft: ['z', r:true, i:5] - expectRight: null - - it 'vs insert', -> xf - op1: ['z', i:5] - op2: ['z', i:10] - conflict: type: DROP_COLLISION - expectLeft: ['z', r:true, i:5] - expectRight: null - - it 'vs insert at list position', -> xf - op1: [5, i:'hi'] - op2: [5, i:'there'] - expectLeft: [5, i:'hi'] - expectRight: [6, i:'hi'] - - it 'vs identical insert', -> xf - op1: ['z', i:5] - op2: ['z', i:5] - expect: null - - # This is the new setNull for setting up schemas - it 'vs embedded inserts', -> - xf - op1: ['x', i:{}] - op2: ['x', i:{}, 'y', i:5] - expect: null - - xf - op1: ['x', i:{}, 'y', i:5] - op2: ['x', i:{}] - expect: ['x', 'y', i:5] - - xf - op1: ['x', i:{}, 'y', i:5] - op2: ['x', i:{}, 'y', i:5] - expect: null - - xf - op1: ['x', i:{}, 'y', i:5] - op2: ['x', i:{}, 'y', i:6] - conflict: - type: DROP_COLLISION - op1: ['x', 'y', i:5] - op2: ['x', 'y', i:6] - expectLeft: ['x', 'y', r:true, i:5] - expectRight: null - - it 'with embedded edits', -> xf - op1: [i:'', es:['aaa']] - op2: [i:'', es:['bbb']] - expectLeft: [es:['aaa']] - expectRight: [es:[3, 'aaa']] - - describe 'op1 edit', -> - it 'vs delete', -> xf - op1: ['x', es:['hi']] - op2: ['x', r:true] - conflict: type: RM_UNEXPECTED_CONTENT - expect: null - - it 'vs delete parent', -> xf - op1: ['x', 'y', es:['hi']] - op2: ['x', r:true] - conflict: type: RM_UNEXPECTED_CONTENT - expect: null - - it 'vs pick', -> xf - op1: ['x', es:['hi']] - op2: [['x', p:0], ['y', d:0]] - expect: ['y', es:['hi']] - - it 'vs edit string', -> xf - op1: ['x', es:['ab']] - op2: ['x', es:['cd']] - expectLeft: ['x', es:['ab']] - expectRight: ['x', es:[2, 'ab']] - - it 'vs edit number', -> xf - op1: [ena:5] - op2: [ena:100] - expect: [ena:5] - - it 'throws if edit types arent compatible', -> - assert.throws -> type.transform [es:[]], [ena:5], 'left' - - it 'vs move and edit', -> xf - op1: ['x', es:[1, 'ab']] - op2: [['x', p:0], ['y', d:0, es:[d:1, 'cd']]] - expectLeft: ['y', es:['ab']] - expectRight: ['y', es:[2, 'ab']] - - it 'throws if the type is missing', -> - assert.throws -> type.transform [et:'missing', e:{}], [et:'missing', e:{}], 'left' - - describe 'op2 cancel move', -> - it 'and insert', -> xf - op1: ['x', r:true] - op2: [['x', 'a', p:0], ['y', d:0, 'b', i:5]] - conflict: - type: RM_UNEXPECTED_CONTENT - op2: ['y', 'b', i:5] - expect: [['x', r:true], ['y', r:true, 'b', r:true]] - - it 'and another move (rm x vs x.a -> y, q -> y.b)', -> xf - op1: ['x', r:true] - op2: [['q', p:1], ['x', 'a', p:0], ['y', d:0, 'b', d:1]] - conflict: - type: RM_UNEXPECTED_CONTENT - op2: [['q', p:0], ['y', 'b', d:0]] - expect: [['x', r:true], ['y', r:true, 'b', r:true]] - - describe 'op2 list move an op1 drop', -> - it 'vs op1 remove', -> xf - op1: [[0, r:true, 'a', i:'hi'], [5, r:true]] - op2: [[1, p:0], [4, d:0]] - expect: [[0, r:true], [3, 'a', i:'hi'], [5, r:true]] - - it 'vs op1 remove 2', -> xf - op1: [[0, r:true, 'a', i:'hi'], [1, r:true], [2, r:true]] - op2: [[3, p:0], [4, d:0]] - expect: [[0, r:true], [1, r:true, 'a', i:'hi'], [2, r:true]] - - it 'vs op1 insert before', -> xf - op1: [[0, i:'a'], [1, i:'b'], [2, 'a', i:'hi']] - op2: [[0, p:0], [1, d:0]] - expect: [[0, i:'a'], [1, i:'b'], [3, 'a', i:'hi']] - - - it 'vs op1 insert before and replace', -> xf - op1: [[0, i:'xx', 'a', r:true], [1, 'a', i:'hi']] - op2: [[0, p:0], [3, d:0]] - expect: [[0, i:'xx'], [3, 'a', r:true], [4, 'a', i:'hi']] - - - describe 'list', -> - describe 'drop', -> - it 'transforms by p1 drops', -> xf - op1: [[5, i:5], [10, i:10]] - op2: [9, i:9] - expectLeft: [[5, i:5], [10, i:10]] - expectRight: [[5, i:5], [11, i:10]] - - it 'transforms by p1 picks' - it 'transforms by p2 picks' - it 'transforms by p2 drops' - - describe 'conflicts', -> - describe 'drop into remove / rm unexpected', -> - # xfConflict does both xf(op1, op2, left) and xf(op2, op1, right), and - # uses invConflict. So this also tests RM_UNEXPECTED_CONTENT with each - # test case. - it 'errors if you insert', -> xf - op1: ['a', 'b', i:5] - op2: ['a', r:true] - conflict: type: RM_UNEXPECTED_CONTENT - expect: null - - it 'errors if you drop', -> xf - op1: [['a', p:0], ['x', 'b', d:0]] - op2: ['x', r:true] - conflict: type: RM_UNEXPECTED_CONTENT - expect: ['a', r:true] - - it 'errors if you rm then insert in a child', -> xf - op1: ['a', 'b', r:true, i:5] - op2: ['a', r:true] - conflict: - type: RM_UNEXPECTED_CONTENT - op1: ['a', 'b', i:5] - expect: null - - it 'errors if the object is replaced', -> xf - op1: ['a', 'b', i:5] - op2: ['a', r:true, i:10] - conflict: - type: RM_UNEXPECTED_CONTENT - op2: ['a', r:true] - expect: null - - it 'handles a delete of the source parent by op2', -> xf - op1: [['a', p:0], ['b', 'b', d:0]] - op2: [['a', p:0], ['b', r:true, 'c', d:0]] - conflictLeft: - type: RM_UNEXPECTED_CONTENT - op2: ['b', r:true] - expectLeft: ['b', 'c', r:true] - expectRight: null - - it.skip 'returns symmetric errors when both ops delete the other', -> xf - # The problem here is that there's two conflicts we want to return. - # Which one should be returned first? It'd be nice for the order of - # conflict returning to be symmetric - that is, if we know multiple - # conflicts happen, order them based on left/right. But I haven't done - # that, so we get different conflicts out of this in a first pass. - op1: [ [ 'x', { r: true } ], [ 'y', 'a', { i: {} } ] ] - op2: [ [ 'x', 'a', { i: {} } ], [ 'y', { r: true } ] ] - conflict: type: RM_UNEXPECTED_CONTENT - expect: ['x', r:true] - - describe 'overlapping drop', -> - it 'errors if two ops insert different content into the same place in an object', -> xf - op1: ['x', i:'hi'] - op2: ['x', i:'yo'] - conflict: type: DROP_COLLISION - expectLeft: ['x', r:true, i:'hi'] - expectRight: null - - it 'does not conflict if inserts are identical', -> xf - op1: ['x', i:'hi'] - op2: ['x', i:'hi'] - expectLeft: null - expectRight: null - - it 'does not conflict if the two operations make identical moves', -> xf - op1: [['a', p:0], ['x', d:0]] - op2: [['a', p:0], ['x', d:0]] - expect: null # ??? Also ok for left: ['x', p:0, d:0] - - it 'does not conflict if inserts are into a list', -> xf - op1: [1, i:'hi'] - op2: [1, i:'yo'] - expectLeft: [1, i:'hi'] - expectRight: [2, i:'hi'] - - it 'errors if the inserts are at the root', -> xf - op1: [i:1] - op2: [i:2] - conflict: type: DROP_COLLISION - expectLeft: [r:true, i:1] - expectRight: null - - it 'errors with insert vs drop', -> xf - op1: ['x', i:'hi'] - op2: [['a', p:0], ['x', d:0]] - # ???? - conflict: type: DROP_COLLISION - expectLeft: ['x', r:true, i:'hi'] - expectRight: null - - it 'errors with drop vs insert', -> xf - op1: [['a', p:0], ['x', d:0]] - op2: ['x', i:'hi'] - conflict: type: DROP_COLLISION - expectLeft: [['a', p:0], ['x', r:true, d:0]] - expectRight: ['a', r:true] - - it 'errors with drop vs drop', -> xf - op1: [['a', p:0], ['x', d:0]] - op2: [['b', p:0], ['x', d:0]] - conflict: type: DROP_COLLISION - expectLeft: [['a', p:0], ['x', r:true, d:0]] - expectRight: ['a', r:true] - - it 'errors if the two sides insert in the vacuum', -> xf - op1: [['a', p:0], ['b', d:0], ['c', i:5]] - op2: [['a', p:0], ['b', i:6], ['c', d:0]] - conflictLeft: - type: DROP_COLLISION - op1: [['a', p:0], ['b', d:0]] - op2: ['b', i:6] - expectLeft: [['b', r:true, d:0], ['c', p:0, i:5]] - conflictRight: - type: DROP_COLLISION - op1: ['c', i:5] - op2: [['a', p:0], ['c', d:0]] - expectRight: null - - - describe 'discarded edit', -> - it 'edit removed directly', -> xf - op1: ['a', es:[]] - op2: ['a', r:true] - conflict: type: RM_UNEXPECTED_CONTENT - expect: null - - it 'edit inside new content throws RM_UNEXPECTED_CONTENT', -> xf - op1: ['a', 'b', i: 'hi', es:[]] - op2: ['a', r:true] - conflict: - type: RM_UNEXPECTED_CONTENT - op1: ['a', 'b', i:'hi'] - expect: null - - describe 'blackhole', -> - it 'detects and errors', -> xf - op1: [['x', p:0], ['y', 'a', d:0]] - op2: [['x', 'a', d:0], ['y', p:0]] - conflict: type: BLACKHOLE - expect: ['x', r:true, 'a', r:true] # Also equivalent: ['x', r:true] - - it 'blackhole logic does not apply when op2 removes parent', -> xf - # TODO: Although you wouldn't know it, since this result is very similar. - op1: [['x', p:0], ['y', 'xx', 'a', d:0]] - op2: [['x', 'a', d:0], ['y', p:0, 'xx', r:true]] - conflict: - type: RM_UNEXPECTED_CONTENT - op2: ['y', 'xx', r:true] - expect: ['x', r:true, 'a', r:true] # Also ok: ['x', r:true] - - it 'blackhole logic still applies when op2 inserts', -> xf - op1: [['x', p:0], ['y', 'a', i:{}, 'b', d:0]] - op2: [['x', 'a', i:{}, 'b', d:0], ['y', p:0]] - conflict: - type: BLACKHOLE - op1: [['x', p:0], ['y', 'a', 'b', d:0]] - op2: [['x', 'a', 'b', d:0], ['y', p:0]] - expect: ['x', r:true, 'a', r:true, 'b', r:true] - - it 'blackholes items in lists correctly', -> xf - op1: [1, p:0, 'a', d:0] - op2: [[1, 'b', d:0], [2, p:0]] - conflict: type: BLACKHOLE - expect: [1, r:true, 'b', r:true] - - it 'blackholes items despite scrambled pick and drop slots', -> xf - op1: [ [ 'a', { p: 1, d: 1 } ], [ 'x', { p: 0 } ], [ 'y', 'a', { d: 0 } ] ] - op2: [ [ 'x', 'a', { d: 0 } ], [ 'y', { p: 0 } ] ] - conflict: - type: BLACKHOLE - op1: [ [ 'x', { p: 0 } ], [ 'y', 'a', { d: 0 } ] ] - expect: [['a', p:0, d:0], ['x', r:true, 'a', r:true]] - - it 'handles chained blackholes', -> xf - op1: [ [ 'a', { p: 0 } ], # a->b.b, c->d.d - [ 'b', 'b', { d: 0 } ], - [ 'c', { p: 1 } ], - [ 'd', 'd', { d: 1 } ] - ] - op2: [ [ 'a', 'a', { d: 1 } ], # b->c.c, d->a.a - [ 'b', { p: 0 } ], - [ 'c', 'c', { d: 0 } ], - [ 'd', { p: 1 } ] - ] - conflict: type: BLACKHOLE - # c1: cMv(['a'], ['b', 'b']) - # c2: cMv(['b'], ['c', 'c']) - expect: [['a', r:true, 'a', r:true], ['c', r:true, 'c', r:true]] - - it 'creates conflict return values with valid slot ids', -> xf - op1: [['a', p:0], ['b', d:0], ['x', p:1], ['y', 'a', d:1]] - op2: [['x', 'a', d:0], ['y', p:0]] - conflict: - type: BLACKHOLE - op1: [['x', p:0], ['y', 'a', d:0]] - expect: [['a', p:0], ['b', d:0], ['x', r:true, 'a', r:true]] - - - describe 'transform-old', -> - it 'foo', -> - xf - op1: [ - ['x', ['a', {p:0}], ['b', {d:0}]], - ['y', ['a', {p:1}], ['b', {d:1}]] - ] - op2: ['x', {r:true}] - expect: ['y', ['a', {p:0}], ['b', {d:0}]] - - # it 'hard', -> - # op1: ['x', [1, r:true], [2, r:true, es:['hi']]] # Edit at index 4 originally. - # # move the edited string to .y[4] which - # op2: [['x', 4, p:0], ['y', [2, r:true], [4, d:0]]] - # expect: - - describe 'object edits', -> - it 'can reparent with some extra junk', -> xf - op1: [['x', p:0], ['y', d:0]] - op2: [ - ['_a', d:1] - ['_x', d:0] - ['x', p:0, 'a', p:1] - ] - expectLeft: [['_x', p:0], ['y', d:0]] - expectRight: null # the object was moved fair and square. - - describe 'deletes', -> - - it.skip 'delete parent of a move', -> xf - # The current logic of transform actually just burns everything (in a - # consistant way of course). I'm not sure if this is better or worse - - # basically we'd be saying that if a move could end up in one of two places, - # put it in the place where it won't be killed forever. But that introduces new - # complexity, so I'm going to skip this for now. - - # x.a -> a, delete x - op1: [['x', r:true, 'a', p:0], ['z', d:0]] - # x.a -> x.b. - op2: ['x', ['a', p:0], ['b', d:0]] - expect: [['x', r:true, 'b', p:0], ['z', d:0]] # TODO: It would be better to do this in both cases. - #expectRight: ['x', r:true] - - it 'awful delete nonsense', -> - xf - op1: [['x', r:true], ['y', i:'hi']] # delete doc.x, insert doc.y - op2: [['x', 'a', p:0], ['y', d:0]] # move doc.x.a -> doc.y - expect: [['x', r:true], ['y', r:true, i:'hi']] # del doc.x and doc.y, insert doc.y - - xf - op1: [['x', 'a', p:0], ['y', d:0]] # x.a -> y - op2: [['x', r:true], ['y', i:'hi']] # delete x, ins y - expect: null - - xf - op1: [10, r:true] - op2: [[5, d:0], [10, 1, p:0]] - expect: [[5, r:true], [11, r:true]] - # And how do those indexes interact with pick / drop operations?? - - - describe 'swap', -> - swap = [ - ['a', p:0, 'b', p:1] - ['b', d:1, 'a', d:0] - ] - - it 'noop vs swap', -> xf - op1: null - op2: swap - expect: null - - it 'can swap two edits', -> xf - op1: ['a', es:['a edit'], 'b', es:['b edit']] - op2: swap - expect: ['b', es:['b edit'], 'a', es:['a edit']] - - describe 'lists', -> - it 'can rewrite simple list indexes', -> - xf - op1: [10, es:['edit']] - op2: [0, i:'oh hi'] - expect: [11, es:['edit']] - - xf - op1: [10, r:true] - op2: [0, i:'oh hi'] - expect: [11, r:true] - - xf - op1: [10, i:{}] - op2: [0, i:'oh hi'] - expect: [11, i:{}] - - it 'can change the root from an object to a list', -> xf - op1: ['a', es:['hi']] - op2: [{i:[], r:true}, [0, d:0], ['a', p:0]] - expect: [0, es:['hi']] - - it 'can handle adjacent drops', -> xf - op1: [[11, i:1], [12, i:2], [13, i:3]] - op2: [0, r:true] - expect: [[10, i:1], [11, i:2], [12, i:3]] - - it 'fixes drop indexes correctly 1', -> xf - op1: [[0, r:true], [1, i:'hi']] - op2: [1, r:true] - expect: [0, r:true, i:'hi'] - - it 'list drop vs delete uses the correct result index', -> - xf - op1: [2, i:'hi'] - op2: [2, r:true] - expect: [2, i:'hi'] - - xf - op1: [3, i:'hi'] - op2: [2, r:true] - expect: [2, i:'hi'] - - it 'list drop vs drop uses the correct result index', -> xf - op1: [2, i:'hi'] - op2: [2, i:'other'] - expectLeft: [2, i:'hi'] - expectRight: [3, i:'hi'] - - it 'list drop vs delete and drop', -> - xf - op1: [2, i:'hi'] - op2: [2, r:true, i:'other'] - expectLeft: [2, i:'hi'] - expectRight: [3, i:'hi'] - - xf - op1: [3, i:'hi'] - op2: [[2, r:true], [3, i:'other']] - expect: [2, i:'hi'] - - xf - op1: [4, i:'hi'] - op2: [[2, r:true], [3, i:'other']] - expectLeft: [3, i:'hi'] - expectRight: [4, i:'hi'] - - it 'list delete vs drop', -> - xf - op1: [1, r:true] - op2: [2, i:'hi'] - expect: [1, r:true] - - xf - op1: [2, r:true] - op2: [2, i:'hi'] - expect: [3, r:true] - - xf - op1: [3, r:true] - op2: [2, i:'hi'] - expect: [4, r:true] - - it 'list delete vs delete', -> - xf - op1: [1, r:true] - op2: [1, r:true] - expect: null # It was already deleted. - - it 'fixes drop indexes correctly 2', -> xf - op1: [[0, r:true], [1, i:'hi']] - op2: [2, r:true] # Shouldn't affect the op. - expect: [[0, r:true], [1, i:'hi']] - - it 'insert vs delete parent', -> xf - op1: [2, 'x', i:'hi'] - op2: [2, r:true] - conflict: type: RM_UNEXPECTED_CONTENT - expect: null - - it 'transforms against inserts in my own list', -> - xf #[0,1,2,3] -> [a,0,b,1,2,3...] - op1: [[0, i:'a'], [2, i:'b']] - op2: [1, r:true] - expect: [[0, i:'a'], [2, i:'b']] - - it 'vs cancelled op2 drop', -> xf - doc: {x:{a:'x.a'}, y:['a','b','c']} - op1: [['x', r:true], ['y', 3, i:5]] - op2: [['x', 'a', p:0], ['y', 2, d:0]] - expect: [['x', r:true], ['y', [2, r:true], [3, i:5]]] - - it 'vs cancelled op1 drop', -> xf - op1: [['x', p:0], ['y', [3, d:0], [4, i:5]]] - op2: ['x', r:true] - expect: ['y', 3, i:5] - - it 'vs cancelled op1 pick', -> xf - doc: Array.from 'abcdefg' - op1: [[1, p:0], [4, r:true, i:4], [6, d:0]] - op2: [1, r:true] - expect: [[3, r:true], [4, i:4]] - - it 'xxxxx 1', -> diamond # TODO Regression. - doc: Array.from('abcdef') - op1: [[1, {p:0, i:'AAA'}], [3, {i:'BBB'}], [5, {d:0}]] - op2: [1, {r:true}] - - it 'xxxxx 2', -> diamond - doc: Array.from('abcdef') - op1: [[1, {p:0, i:'AAA'}], [3, {d:0}], [5, {i:'CCC'}]] - op2: [1, {r:true}] - - - describe 'edit', -> - it 'transforms edits by one another', -> xf - op1: [1, es:[2, 'hi']] - op2: [1, es:['yo']] - expect: [1, es:[4, 'hi']] - - it 'copies in ops otherwise', -> xf - op1: ['x', {e:{position:2, text:'wai'}, et:'simple'}] - op2: ['y', r:true] - expect: ['x', {e:{position:2, text:'wai'}, et:'simple'}] - - it 'allows edits at the root', -> xf - op1: [{e:{position:2, text:'wai'}, et:'simple'}] - op2: [{e:{position:0, text:'omg'}, et:'simple'}] - expect: [{e:{position:5, text:'wai'}, et:'simple'}] - - it 'applies edits in the right order', -> xf - # Edits happen *after* the drop phase. - op1: [1, es:[2, 'hi']] - op2: [[1, i:{}], [2, es:['yo']]] - expect: [2, es:[4, 'hi']] - - it 'an edit on a deleted object goes away', -> xf - op1: [1, es:[2, 'hi']] - op2: [1, r:"yo"] - conflict: - type: RM_UNEXPECTED_CONTENT - op2: [1, r:true] # .... It'd be better if this copied the remove. - expect: null - - # TODO Numbers - - -# ***** Test cases found by the fuzzer which have caused issues - describe 'fuzzer tests', -> - it 'asdf', -> apply - doc: { the: '', Twas: 'the' } - op: [ 'the', { es: [] } ] - expect: { the: '', Twas: 'the' } - - it 'does not duplicate list items from edits', -> apply - doc: ['eyes'] - op: [ 0, { es: [] } ] - expect: ['eyes'] - - it 'will edit the root document', -> apply - doc: '' - op: [es:[]] - expect: '' - - # ------ These have nothing to do with apply. TODO: Move them out of this grouping. - - it 'diamond', -> - # TODO: Do this for all combinations. - diamond - doc: Array.from 'abcde' - op1: [ [ 0, { p: 0 } ], [ 1, { d: 0 } ] ] - op2: [ [ 0, { p: 0 } ], [ 4, { d: 0 } ] ] - - it 'shuffles lists correctly', -> xf - op1: [ [ 0, { p: 0 } ], [ 1, { d: 0 } ] ] - op2: [ [ 0, { p: 0 } ], [ 10, { d: 0 } ] ] - expectLeft: [ [ 1, { d: 0 } ], [ 10, { p: 0 } ] ] - expectRight: null - - it 'inserts before edits', -> - xf - op1: [0, 'x', i:5] - op2: [0, i:35] - expect: [1, 'x', i:5] - - xf - op1: [0, es:[]] - op2: [0, i:35] - expect: [1, es:[]] - - it 'duplicates become noops in a list', - -> xf - op1: [0,{"p":0,"d":0}] - op2: [0,{"p":0,"d":0}] - expectLeft: [0,{"p":0,"d":0}] # This is a bit weird. - expectRight: null - - -> xf - op1: [0, r:true, i:'a'] - op2: [0, i:'b'] - expectLeft: [[0, i:'a'], [1, r:true]] - expectRight: [1, r:true, i:'a'] - - -> xf - op1: [0, r:true, i:5] - op2: [0, r:true] - expect: [0, i:5] - - it 'p1 pick descends correctly', -> - xf - op1: [2, r:true, 1, es:['hi']] - op2: [3, 1, r:true] - conflict: - type: RM_UNEXPECTED_CONTENT - op1: [2, 1, es:['hi']] - expect: [2, r:true] - - xf - op1: [[2, r:true, 1, es:['hi']], [3, 1, r:true]] - op2: [3, 2, r:true] - conflict: - type: RM_UNEXPECTED_CONTENT - op1: [2, 1, es:['hi']] - expect: [[2, r:true], [3, 1, r:true]] - - it 'transforms picks correctly', -> xf - op1: [1, 1, r:true] - op2: [0, p:0, d:0] - expect: [1, 1, r:true] - - it 'pick & drop vs insert after the picked item', -> xf - op1: [0, p:0,d:0] # Remove / insert works the same. - op2: [1, i:"hi"] - expectLeft: [0, p:0,d:0] - expectRight: [[0, p:0], [1, d:0]] - - it 'pick same item vs shuffle list', -> xf - op1: [1, ['x', p:0], ['y', d:0]] - op2: [1, {d:0}, 'x', {p:0}] - expectLeft: [1, p:0, 'y', d:0] - expectRight: null - - it 'remove the same item in a list', -> xf - op1: [ 0, { r: true } ] - op2: [ 0, { r: true } ] - expect: null - - it 'rm vs hold item', -> xf - op1: [ 0, { r: true } ] - op2: [ 0, { p: 0, d: 0 } ] - expect: [ 0, { r: true } ] - - it 'moves child elements correctly', -> xf - doc: ['a', [0,10,20], 'c'] - op1: [ 1, 0, { p: 0, d: 0 } ] - op2: [ [ 1, { d: 0 } ], [ 2, { p: 0 } ] ] - expect: [ 2, 0, { d:0, p:0 } ] - - it 'moves list indexes', -> xf - doc: [[], 'b', 'c'] - op1: [ [ 0, 'hi', { d: 0 } ], [ 1, { p: 0 } ] ] - op2: [ [ 0, { p: 0 } ], [ 20, { d: 0 } ] ] - expect: [[0, p:0], [19, 'hi', d:0]] - - it 'insert empty string vs insert null', -> xf - doc: undefined - op1: [i:'hi'] - op2: [i:null] - conflict: type: DROP_COLLISION - expectLeft: [r:true, i:'hi'] - expectRight: null - - it 'move vs emplace', -> xf - doc: ['a', 'b'] - op1: [[0, p:0], [1, d:0]] - op2: [1, {p:0, d:0}] - expectLeft: [0, {p:0, d:0}] - expectRight: [[0, p:0], [1, d:0]] - - it 'rm chases a subdocument that was moved out', -> xf - doc: [ [ 'aaa' ] ] - op1: [ 0, { r: true } ] - op2: [ 0, { d: 0 }, 0, { p: 0 } ] # Valid because lists. - expect: [[0, r:true], [1, r:true]] - - it 'colliding drops', -> xf - doc: [ 'a', 'b', {} ] - op1: [[0, p:0], [1, 'x', d:0]] # -> ['b', x:'a'] - op2: [1, p:0, 'x', d:0] # -> ['a', x:'b'] - conflict: type: DROP_COLLISION - expectLeft: [[0, p:0, 'x', d:0], [1, 'x', r:true]] - expectRight: [0, r:true] - - it 'transform crash', -> xf - op1: [ [ 'the', { r: true, d: 0 } ], [ 'whiffling', { p: 0 } ] ] - op2: [ 'the', { p: 0, d: 0 } ] - expect: [ [ 'the', { d: 0, r: true } ], [ 'whiffling', { p: 0 } ] ] - - it 'transforms drops when the parent is moved by a remove', -> xf - op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]] - op2: ['a', 0, {r:1}] - expect: [['a', {p:0}], ['b', {d:0}, 0, {i:2}]] - - it 'transforms drops when the parent is moved by a drop', -> xf - op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]] - op2: ['a', 0, {i:1}] - expect: [['a', {p:0}], ['b', {d:0}, 2, {i:2}]] - - it 'transforms conflicting drops obfuscated by a move', -> xf - op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]] - op2: ['a', 1, {i:1}] - expectLeft: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]] - expectRight: [['a', {p:0}], ['b', {d:0}, 2, {i:2}]] - - it 'transforms edits when the parent is moved', -> xf - op1: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 1, 'xxx' ] } ] ] - op2: [ 'x', { es: [ d: 1, 'Z' ] } ] - expectLeft: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 'xxx' ] } ] ] - expectRight: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 1, 'xxx' ] } ] ] - - it 'xf lots', -> xf - op1: [['a', p:0], ['b', d:0, es:['hi']]] - op2: [['a', p:0], ['c', d:0]] - expectLeft: [['b', d:0, es:['hi']], ['c', p:0]] - expectRight: ['c', es:['hi']] - - it 'inserts are moved back by the other op', -> xf - op1: [['a', p:0], ['b', d:0, 'x', i:'hi']] - op2: [['a', p:0], ['c', d:0]] - expectLeft: [['b', d:0, 'x', i:'hi'], ['c', p:0]] - expectRight: ['c', 'x', i:'hi'] - - it 'more awful edit moves', -> xf - op1: [['a', p:0], ['c', d:0, 'x', es:[]]] - op2: ['a', ['b', d:0], ['x', p:0]] - expect: [['a', p:0], ['c', d:0, 'b', es:[]]] - - it 'inserts null', -> xf - op1: [ 'x', 'a', { i: null } ] - op2: [ [ 'x', { p: 0 } ], [ 'y', { d: 0 } ] ] - expect: [ 'y', 'a', { i: null } ] - - it 'preserves local insert if both sides delete', -> xf - op1: [ { i: {}, r: true }, 'x', { i: 'yo' } ] - op2: [ { r: true } ] - expect: [ { i: {} }, 'x', { i: 'yo' } ] - - it 'handles insert/delete vs move', -> xf - op1: [ 'a', { i: {}, r: true }, 'x', { i: 'yo' } ] - op2: [ [ 'a', { p: 0 } ], [ 'b', { d: 0 } ] ] - expect: [ [ 'a', { i: {} }, 'x', { i: 'yo' } ], [ 'b', { r: true }, ] ] - - it 'insert pushes edit target', -> xf - op1: [[ 0, { i: "yo" } ], [ 1, 'a', { es: [] }]] - op2: [0, [ 'a', { p: 0 } ], [ 'b', { d: 0 } ]] - expect: [[0, { i: 'yo' }], [1, 'b', { es: [] }]] - - it 'composes simple regression', -> - compose - op1: [ 0, { p: 0, d: 0 } ] - op2: [ { r: true } ] - expect: [ { r: true }, 0, { r: true } ] - - compose - op1: [ 'a', 1, { r: true } ] - op2: [ 'a', { r: true } ] - expect: [ 'a', { r: true }, 1, { r: true } ] - - it 'ignores op2 inserts for index position after op1 insert', -> xf - op1: [ { r:true, i: [] }, 0, { i: '' } ] - op2: [ 0, { i: 0 } ], - conflict: - type: RM_UNEXPECTED_CONTENT - op1: [r:true] - expect: [ { r: true, i: [] }, 0, { r:true, i: '' } ] - - it 'edit moved inside a removed area should be removed', -> xf - op1: [[ 0, { r: true } ], [ 2, { es: [ ] } ]] - op2: [[ 0, 'x', { d: 0 } ], [ 3, { p: 0 } ]] - conflict: - type: RM_UNEXPECTED_CONTENT - op1: [0, r:true] - expect: [ 0, { r: true }, 'x', {r:true} ] - - it 'advances indexes correctly with mixed numbers', -> xf - op1: [ [ 'x', [ 0, { p: 0 } ], [ 1, { d: 1 } ] ], [ 'y', { p: 1 } ], [ 'zzz', { d: 0 } ] ] - op2: [ [ 'x', 2, { i: 'hi' } ], [ 'y', { p: 0 } ], [ 'z', { d: 0 } ] ] - expectLeft: [ [ 'x', [ 0, { p: 1 } ], [ 1, { d: 0 } ] ], [ 'z', { p: 0 } ], [ 'zzz', { d: 1 } ] ] - expectRight: [ [ 'x', 0, { p: 0 } ], [ 'zzz', { d: 0 } ] ] - - it 'handles index positions past cancelled drops 1', -> xf - op1: [ 0, { r: true, i: [ '' ] } ], - op2: [ [ 0, { p: 0, d: 0 } ], [ 1, { i: 23 } ] ] - expectLeft: [ 0, { r: true, i: [ '' ] } ] - expectRight: [ [ 0, { r: true } ], [ 1, { i: [ '' ] } ] ] - - it 'handles index positions past cancelled drops 2', -> xf - # This looks more complicated, but its a simpler version of the above test. - op1: [ [ 'a', { r: true } ], [ 'b', 0, { i: 'hi' } ] ] - op2: [ [ 'a', { p: 0 } ], [ 'b', [ 0, { d: 0 } ], [ 1, { i: 'yo' } ] ] ] - expectLeft: [ 'b', 0, { i: 'hi', r: true } ] - expectRight: [ 'b', [ 0, { r: true } ], [ 1, { i: 'hi' } ] ] - - it 'calculates removed drop indexes correctly', -> xf - op1: [ [ 0, { i: 'hi', p: 0 } ], [ 1, 1, { d: 0 } ], [ 2, { r: true } ] ] - op2: [ [ 0, { i: 'yo', p: 0 } ], [ 1, 1, { d: 0 } ] ] - expectLeft: [ [ 0, { i: 'hi' } ], [ 1, 1, { p: 0 } ], [ 2, { r: true }, 1, { d: 0 } ] ] - expectRight: [ [ 1, { i: 'hi' } ], [ 2, { r: true } ] ] - - it 'removed drop indexes calc regression', -> xf - op1: [ [ 1, { p: 0 }, 'burbled', { d: 0 } ], [ 3, { r: true } ] ] - op2: [ [ 0, { i: 'to', r: true } ], [ 1, { p: 1 }, [ 'its', { d: 0 } ], [ 'thought', { d: 1 } ] ], [ 3, { p: 0 } ] ] - expectLeft: [ 1, [ 'burbled', { d: 0 } ], [ 'its', { r: true } ], [ 'thought', { p: 0 } ] ] - expectRight: [ 1, 'its', { r: true } ] - - it 'removed drop indexes tele to op1 pick', -> xf - op1: [ 'a', 0, [ 0, { es: [] } ], [ 2, { r: true } ] ] - op2: [ [ 'a', { p: 0 }, 0, 0, { p: 1 } ], [ 'b', { d: 0 }, 0, 1, 0, { d: 1 } ] ] - conflict: - type: RM_UNEXPECTED_CONTENT - op1: ['a', 0, 2, r:true] - op2: [ [ 'a', 0, 0, { p: 0 } ], [ 'b', 0, 1, 0, { d: 0 } ] ] - expect: [ 'b', 0, 1, { r: true }, 0, { r: true } ] - - it 'tracks removed drop index teleports', -> xf - # rm 0.a, move 0.b -> 0.c - doc: [{a:['a'], b:'b'}] - op1: [ 0, [ 'a', { r: true } ], [ 'b', { p: 0 } ], [ 'c', { d: 0 } ] ] # [{c:'b'}] - op2: [ 0, { d: 0, p: 1 }, [ 0, { d: 1 } ], [ 'a', { p: 0 } ] ] # [[{b:'b'}, 'a']] - conflict: - type: RM_UNEXPECTED_CONTENT - op1: [0, 'a', r:true] - op2: [0, p:0, 0, d:0] - expect: [ 0, { r: true }, 0, { r: true } ] - - it 'handles transforming past cancelled move', -> xf - op1: [ [ 0, { r: true } ], [ 10, { i: [ '' ] } ] ] - op2: [ 0, { p: 0, d: 0 } ] - expect: [ [ 0, { r: true } ], [ 10, { i: [ '' ] } ] ] - - it 'correctly adjusts indexes in another fuzzer great', -> xf - op1: [ [ 0, { d: 0, r: true } ], [ 3, { p: 0 } ] ] - op2: [ [ 0, { p: 0 } ], [ 3, { d: 0 } ] ] - expect: [[0, d:0], [2, p:0], [3, r:true]] - - it 'op2 moves into something op1 removes and op1 moves into that', -> xf - op1: [ [ 'a', { r: true }, 'aa', { p: 0 } ], [ 'b', 'x', { d: 0 } ] ] - op2: [ [ 'a', 'bb', { d: 0 } ], [ 'b', { p: 0 } ] ] - conflict: - type: RM_UNEXPECTED_CONTENT - op1: ['a', r:true] - expect: [ 'a', { r: true }, ['aa', r:true], ['bb', r:true]] # Also ok if we miss the second rs. - - it 'op2 moves into op1 remove edge cases', -> - # Sorry not minified. - xf - op1: [ 'Came', 0, [ 0, { r: true }, 'he', { p: 0 } ], [ 1, { d: 0 }, 0, { i: 'time' } ] ] - op2: [ 'Came', 0, [ 0, 'he', [ 0, { d: 0 } ], [ 1, { es: [] } ] ], [ 1, { p: 0 } ] ], - expectLeft: [ 'Came', 0, 0, { r: true, d: 0 }, [ 0, { i: 'time' } ], [ 'he', { p: 0 } ] ] - expectRight: [ 'Came', 0, 0, { r: true, d: 0 }, [ 1, { i: 'time' } ], [ 'he', { p: 0 } ] ] - - xf - op1: [ [ 0, [ 1, { p: 0 } ], [ 2, { r: true } ] ], [ 1, 'xxx', { d: 0 } ] ] - op2: [ 0, 1, { i: {}, p: 0 }, 'b', { d: 0 } ] - expectLeft: [ [ 0, [ 1, 'b', { p: 0 } ], [ 2, { r: true } ] ], [ 1, 'xxx', { d: 0 } ] ] - expectRight: [ 0, 2, { r: true } ] - - it 'translates indexes correctly in this fuzzer find', -> xf - op1: [ 0, { p: 0 }, 'x', { d: 0 } ] - op2: [ [ 0, { p: 0, d: 0 } ], [ 1, { i: 'y' } ] ] - expectLeft: [[0, { p: 0 }], [1, 'x', { d: 0 }]] - expectRight: null - - it 'buries children of blackholed values', -> xf - op1: [ [ 0, [ 'a', { p: 0 } ], [ 'b', { d: 0 } ], [ 'c', { d: 1 } ] ], [ 1, { p: 1 } ] ] - op2: [ 0, { p: 0 }, 'x', { d: 0 } ] - # This is a bit interesting. The question is, which op2 picks and drops - # should we include in the output? For now the answer is that we include - # anything in both ops thats going to end up inside the blackholed - # content. - conflict: type: BLACKHOLE - - # op1: [[0, 'c', d:0], [1, p:0]] - expect: [ 0, r: true, 'x', r:true ] - - it 'does not conflict when removed target gets moved inside removed container', -> - # This edge case is interesting because we don't generate the same - # conflicts on left and right. We want our move of a.x to escape the - # object before removing it, but when we're right, the other operation's - # move holds the object and we get an unexpected rm conflict. - xf - op1: [ [ 'a', { r: true }, 'x', { p: 0 } ], [ 'b', { d: 0 } ] ] - op2: [ 'a', [ 'x', { p: 0 } ], [ 'y', { d: 0 } ] ] - conflictRight: - type: RM_UNEXPECTED_CONTENT - op1: ['a', r:true] - expectLeft: [ [ 'a', { r: true }, 'y', { p: 0 } ], [ 'b', { d: 0 } ] ] - expectRight: [ 'a', { r: true }, 'y', {r:true}] - - xf - op1: [ [ 'a', { r: true }, 1, { p: 0 } ], [ 'b', { d: 0 } ] ] - op2: [ 'a', [ 0, { d: 0 } ], [ 1, { p: 0 } ] ] - expectLeft: [ [ 'a', { r: true }, 0, { p: 0 } ], [ 'b', { d: 0 } ] ] - conflictRight: - type: RM_UNEXPECTED_CONTENT - op1: ['a', r:true] - expectRight: [ 'a', { r: true }, 0, {r:true}] - - expect: [ [ 'a', { r: true }, 0, { p: 0 } ], [ 'b', { d: 0 } ] ] - - it 'compose copies op2 edit data', -> compose - op1: [ 'a', { r: true } ] - op2: [ [ 'x', { p: 0 } ], [ 'y', { d: 0 }, 'b', { es: [] } ] ] - expect: [ - ['a', r:true] - ['x', {p:0}] - ['y', {d: 0}, 'b', {es: []}] - ] - - it 'does not conflict when the dest is salvaged', -> xf - op1: [ [ 'a', { p: 0 } ], [ 'b', { i: 'hi' } ], [ 'c', { d: 0 } ] ] - op2: [ [ 'a', { p: 0 } ], [ 'b', { d: 0 } ] ] - expectLeft: [['b', {p:0, i:'hi'}], ['c', d:0]] - conflictRight: - type: DROP_COLLISION - op1: [ 'b', { i: 'hi' } ] - expectRight: null - - it 'does not conflict on identical r/i pairs', -> xf - op1: [{ i: [], r: true }] - op2: [{ i: [], r: true }] - expect: null - - it 'allows embedded edits in identical r/i', -> xf - op1: [ { r: true, i: '', es: [] } ] - op2: [ { r: true, i: '' } ] - expect: [es:[]] - - it 'does not conflict on identical r/i pairs with identical drops inside', -> xf - op1: [ { i: {}, r: true }, 'a', { i: 'a' } ] - op2: [ { i: {}, r: true }, 'a', { i: 'a' } ] - expect: null - - it 'generates a DROP_COLLISION on children', -> xf - op1: [ { i: {}, r: true }, 'a', { i: 'a' } ] - op2: [ { i: {}, r: true }, 'a', { i: 'b' } ] - conflict: - type: DROP_COLLISION - op1: ['a', { i: 'a' } ] - op2: ['a', { i: 'b' } ] - expectLeft: ['a', r:true, i:'a'] - expectRight: null - - it 'Transforms edit moves into the right dest', -> xf - op1: [ 0, { p: 0, d: 0 }, - # These parts are all needed for some reason. - [ 0, { i: 1 } ], - [ 1, { r: true } ], - [ 3, { es: [] } ] - ] - op2: [ 0, [ 0, { d: 0 } ], [ 3, { p: 0 } ] ] - expectLeft: [ 0, {p:0, d:0}, - [0, i:1], - [1, es:[]], - [2, r:true] - ] - expectRight: [0, {p:0, d:0} - [0, es:[]], - [1, i:1], - [2, r:true] - ] - - it 'adjusts indexes of pick -> drop', -> xf - op1: [ 0, { p: 0, d: 0 } ] - op2: [ [ 0, { i: 'yo', p: 0 } ], [ 1, { d: 0 } ] ], - expectLeft: [ [ 0, { d: 0 } ], [ 1, { p: 0 } ] ] - expectRight: null - - it 'clears output outDrop when theres no pick', -> xf - # Again, not minimized. We return the right data, we were just double- - # descending into outDrop. - op1: [ [ 'the', { d: 0, p: 0 } ], [ 'toves', { r: true } ] ] - op2: [ - [ 'bird', { d: 0 } ], - [ 'slain', { d: 1 } ], - [ 'the', { p: 1 } ], - [ 'toves', { p: 0 } ] - ] - expectLeft: [ - [ 'bird', { r: true } ], - [ 'slain', { p: 0 } ], - [ 'the', { d: 0 } ] - ] - expectRight: [ 'bird', { r: true } ] - - it 'pushes drop indexes by other held items', -> xf - op1: [ [ 0, { p: 0 }], - [ 1, - [ 0, { i: 'hi' } ], - [ 1, { d: 0, es: [] } ] ] - ] - op2: [ - [ 0, { p: 1 }, 1, { d: 0 }, 2, { d: 1 } ], - [ 2, { p: 0 } ] - ] - expectLeft: [ 0, 1, - [ 0, { i: 'hi' } ], - [ 1, { d: 0, es: [] } ], - [ 2, { p: 0 } ] - ] - expectRight: [ 0, 1, [ 0, { i: 'hi' } ], [ 3, { es: [] } ] ] - - it 'composes correctly with lots of removes', -> compose - op1: [ 3, 1, { r: true } ], - op2: [ - [ 0, { es: [] } ], - [ 1, { r: true, es: [] } ], - [ 2, { r: true } ] - ] - expect: [ - [ 0, { es: [] } ], - [ 1, { es: [], r: true } ], - [ 2, { r: true } ], - [ 3, 1, { r: true } ] - ] - - it 'does not descend twice when p/r on an identical insert', -> xf - op1: [ [ 'a', { p: 0, i: '' } ], [ 'b', { d: 0 } ] ] - op2: [ 'a', { r: true, i: '' } ] - expect: null - - it 'conflicts underneath a moved / inserted child', -> xf - op1: [ [ 'a', { p: 0, i: {} }, 'x', {i:5} ], [ 'b', { d: 0 } ] ] - op2: [ 'a', { r: true, i: {} }, 'x', {i:6} ] - conflict: - type: DROP_COLLISION - op1: ['a', 'x', i:5] - op2: ['a', 'x', i:6] - expectLeft: ['a', 'x', {r:true, i:5}] - expectRight: null - - it 'clears drop2 in transform moves', -> xf - doc: [b: a: 'hi'] - op1: [0, d:0, - [ 'a', { es: [] } ], - [ 'b', { p: 0 } ] - ] - op2: [ 0, 'b', - [ 'a', { p: 0 } ], - [ 'b', { d: 0 } ] - ] - expect: [0, d:0, 'b', { p: 0, es:[] }] - - it 'descends correctly when op2 picks and drops', -> xf - op1: [ - [ 'b', { d: 0 }, [ 1, { es: [] } ], [ 2, { i: null } ] ], - [ 'e', { p: 0 } ] - ] - op2: [ { p: 0, d: 0 }, 'e', 1, { p: 1, d: 1 } ] - expectLeft: [ - [ 'b', { d: 0 }, [ 1, { i: null } ], [ 2, { es: [] } ]], - [ 'e', { p: 0 } ] - ] - expectRight: [ - [ 'b', { d: 0 }, [ 1, { es: [] } ], [ 2, { i: null } ] ], - [ 'e', { p: 0 } ] - ] - - it 'composes a pick out of the insert', -> compose - op1: [ { i: [ 5, { x: 6 } ] } ] - op2: [ [ 0, { r: true }, 'c', { d: 0 } ], [ 1, 'x', { p: 0 } ] ] - # expect: [{i: [{c: 6}]}] - expect: [ { i: [ {} ] }, 0, 'c', { i: 6 } ] - - it 'is not overeager to remove intermediate literal array items', -> compose - op1: [ [ 0, { i: [ 'a', 'b' ] }, 0, { p: 0 } ], [ 1, 0, { d: 0 } ] ] - op2: [ 0, { r: ['a'] }, 1, { r: 'b' } ] - expect: [ 0, 0, { d: 0, p: 0 } ] - - it 'descends down insert indexes correctly', -> compose - op1: [ { i: [ {}, 'a' ] }, 1, { i: 'b' } ] - op2: [ [ 1, { r: 'b' } ], [ 2, { r: 'a' } ] ] - expect: [ { i: [ {} ] } ] - - it 'handles composes with ena: 0', -> compose - op1: [i:10] - op2: [ena:0] - expect: [i:10, ena:0] # Also ok: just discarding the ena:0. - - it 'handles rm parent with cross move', -> compose - op1: [ [ 'a', { p: 0 } ], [ 'b', 1, { d: 0 } ] ] - op2: [ [ 'b', { r: true }, 1, { p: 0 } ], [ 'c', { d: 0 } ]] - expect: [ [ 'a', { p: 0 } ], [ 'b', { r: true } ], [ 'c', { d: 0 } ] ] - - it 'lets you remove children of an op at 2 levels', -> compose - op1: [ { i: [ 'a', { x: 'hi' } ] } ] - op2: [ { r: true }, 1, 'x', { r: true } ] - expect: null - - it 'discards op1 inserts inside a removed chunk', -> compose - op1: [ 'y', [ 1, { i: 'x' } ], [ 2, { i: [ 'a', 'b' ] } ] ] - op2: [ { r: true }, 'y', 2, 0, { r: true } ] - expect: [ { r: true } ] - - it 'handles deeply nested blackhole operations', -> xf - op1: [ - [ 'x', { p: 0 } ], - [ 'y', - [ 'a', - [ 'j', { p: 1 } ], - [ 'k', { d: 1 } ] - ], - [ 'b', { d: 0 }] - ] - ] - op2: [ - [ 'x', 'xx', { d: 0 }, 'j', 'jj', { d: 1 } ], - [ 'y', { p: 1 }, 'a', { p: 0 } ] - ] - conflict: type: BLACKHOLE - expect: ['x', r:true, 'xx', r:true, 'j', 'jj', r:true] - - it 'does not list removed op1 moves in the blackhole info', -> xf - op1: [ - [ 'a', [ 'j', { d: 0 } ], [ 'k', { d: 1 } ] ], - [ 'b', { p: 0 }, 'z', 0, { p: 1 } ] - ] - op2: [ - [ 'a', { p: 0 } ], - [ 'b', [ 'y', { d: 0 } ], [ 'z', { r: true } ] ] - ] - conflict: - type: BLACKHOLE - op1: [ [ 'a', 'j', { d: 0 } ], [ 'b', { p: 0 } ] ] - op2: [ [ 'a', { p: 0 } ], [ 'b', 'y', { d: 0 } ] ] - expect: ['b', r:true, 'y', r:true] - - it 'handles overlapping pick in blackholes', -> xf - # This looks complicated, but its really not so bad. Its: - # a->b.0, a.x -> z - # vs - # b -> a.x -> a.y - # - # Its a bit twisty because we're both picking up the same element and - # putting it in different places. This is why we have different left and - # right results. - op1: [ - [ 'a', { p: 1 }, 'x', { p: 0 } ], - [ 'b', 0, { d: 1 } ], - [ 'z', { d: 0 } ] - ] - op2: [ - [ 'a', [ 'x', { d: 0, p: 1 } ], [ 'y', { d: 1 } ] ], - [ 'b', { p: 0 } ] - ] - conflictLeft: - type: BLACKHOLE - op1: [['a', p:0], ['b', 0, d:0]] - op2: [['a', 'x', d:0], ['b', p:0]] - expectLeft: [ - [ 'a', { r: true }, - [ 'x', { r: true } ], - [ 'y', { p: 0 } ] - ], - [ 'z', { d: 0 } ] - ] - conflictRight: - type: BLACKHOLE - op1: [['a', p:0], ['b', 0, d:0]] - expectRight: [ 'a', { r: true }, - [ 'x', { r: true } ], - [ 'y', { r: true } ] - ] - \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..9d76edd --- /dev/null +++ b/test/test.js @@ -0,0 +1,2577 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +// Unit tests for the JSON1 OT type. +// +// These tests are quite unstructured. You can see the skeletons of a few +// organizing systems, but ultimately there's just lots of test cases to run. +// +// Cleanups welcome, so long as you don't remove any tests. + +const assert = require('assert'); +// {type} = require '../index' +const {type} = require('../lib/json1'); +const log = require('../lib/log'); +const deepClone = require('../lib/deepClone'); + +const {transform} = type; +const {DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE} = type; + +const apply = function({doc:snapshot, op, expect}) { + type.setDebug(false); + + const orig = deepClone(snapshot); + try { + const result = type.apply(snapshot, op); + assert.deepStrictEqual(snapshot, orig, 'Original snapshot was mutated'); + return assert.deepStrictEqual(result, expect); + } catch (e) { + console.log(`Apply failed! Repro apply( ${JSON.stringify(snapshot)}, ${JSON.stringify(op)} )`); + console.log(`expected output: ${JSON.stringify(expect)}`); + throw e; + } +}; + +const d = function(fn) { + type.setDebug(true); + fn(); + return type.setDebug(false); +}; + +const compose = function({op1, op2, expect}) { + try { + const result = type.compose(op1, op2); + return assert.deepStrictEqual(result, expect); + } catch (e) { + d(function() { + console.error('FAIL! Repro with:'); + console.log(`compose( ${JSON.stringify(op1)}, ${JSON.stringify(op2)} )`); + console.log(`expected output: ${JSON.stringify(expect)}`); + return type.compose(op1, op2); + }); + throw e; + } +}; + +const invConflict = ({type, op1, op2}) => ({type, op1:op2, op2:op1}); + + + +const otherSide = function(side) { if (side === 'left') { return 'right'; } else { return 'left'; } }; +const checkConflict = function({op1, op2, side, conflict: expectConflict, expect}) { + // We should get the same conflict with xf(op1, op2, left) and xf(op2, op1, right). + if (expectConflict != null) { + if (!expectConflict.op1) { expectConflict.op1 = type.normalize(op1); } + if (!expectConflict.op2) { expectConflict.op2 = type.normalize(op2); } + } + + return (() => { + const result = []; + for (var [side_, op1_, op2_, ec] of [ + [side, op1, op2, expectConflict], + [otherSide(side), op2, op1, expectConflict ? invConflict(expectConflict) : null] + ]) { + try { + + // d -> log('tryTransform', side_, op1_, op2_) + const {ok, conflict} = type.tryTransform(op1_, op2_, side_); + if ((ec == null)) { + // We don't care what the result is here; just that it doesn't conflict. + result.push(assert(ok)); + } else { + assert(!ok, `Conflict erroneously succeeded (${side_})`); + // d -> log('conflict', conflict) + conflict.op1 = type.normalize(conflict.op1); + conflict.op2 = type.normalize(conflict.op2); + result.push(assert.deepStrictEqual(conflict, ec)); + } + } catch (e) { + d(function() { + console.error('FAIL! Repro with:'); + console.log(`tryTransform(${JSON.stringify(op1_)}, ${JSON.stringify(op2_)}, '${side_}')`); + return type.tryTransform(op1_, op2_, side_); + }); + throw e; + } + } + return result; + })(); +}; + +const xf = function({op1, op2, conflict, conflictLeft, conflictRight, expect, expectLeft, expectRight}) { + if (expect !== undefined) { expectLeft = (expectRight = expect); } + if (conflict !== undefined) { conflictLeft = (conflictRight = conflict); } + + return (() => { + const result1 = []; + for (var [side, e, c] of [['left', expectLeft, conflictLeft], ['right', expectRight, conflictRight]]) { + checkConflict({op1, op2, side, conflict: c, expect: e}); + + try { + const result = (c != null) ? type.transformNoConflict(op1, op2, side) : transform(op1, op2, side); + result1.push(assert.deepStrictEqual(result, e)); + } catch (error) { + e = error; + d(function() { + console.error('FAIL! Repro with:'); + return console.log(`transform(${JSON.stringify(op1)}, ${JSON.stringify(op2)}, '${side}')`); + }); + // if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side + throw e; + } + } + return result1; + })(); +}; + + + +const diamond = function({doc, op1, op2}) { + let doc1, doc12, doc2, doc21, op1_, op2_; + type.setDebug(false); + + try { + // Test that the diamond property holds + op1_ = transform(op1, op2, 'left'); + op2_ = transform(op2, op1, 'right'); + + doc1 = type.apply(doc, op1); + doc2 = type.apply(doc, op2); + + doc12 = type.apply(doc1, op2_); + doc21 = type.apply(doc2, op1_); + + return assert.deepStrictEqual(doc12, doc21); + } catch (e) { + log.quiet = false; + log('\nOops! Diamond property does not hold. Given document', doc); + log('op1 ', op1, ' / op2', op2); + log('op1_', op1_, ' / op2_', op2_); + log('---- 1'); + log('op1', op1, '->', doc1); + log('op2', op2_, '->', doc12); + log('---- 2'); + log('op2', op2, '->', doc2); + log('op1', op1_, '->', doc21); + log('----------'); + log(doc12, '!=', doc21); + throw e; + } +}; + + +const path = function(path, {op, expect}) { + if (expect === undefined) { expect = path.slice(); } + + const result = type.transformPosition(path, op); + assert.deepStrictEqual(result, expect); + + // Also check that path+X = expect+X + const path2 = path.concat('x'); + const expect2 = (expect != null) ? expect.concat('x') : null; + + const result2 = type.transformPosition(path2, op); + return assert.deepStrictEqual(result2, expect2); +}; + + +describe('json1', function() { + before(function() { + type.registerSubtype(require('ot-simple')); + return type.setDebug(true); + }); + after(() => type.setDebug(false)); + + describe('checkOp', function() { + const pass = function(op) { + try { + return type.checkValidOp(op); + } catch (e) { + console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`); + throw e; + } + }; + + const fail = function(op) { + try { + return assert.throws(() => type.checkValidOp(op)); + } catch (e) { + console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`); + console.log('Should throw!'); + throw e; + } + }; + + it('allows some simple valid ops', function() { + pass(null); + pass([{i:[1,2,3]}]); + pass([{r:{}}]); + pass([['x',{p:0}], ['y',{d:0}]]); + pass([[0,{p:0}], [10,{d:0}]]); + pass([['a',{p:0}],['b',{d:0}],['x',{p:1}],['y',{d:1}]]); + pass([{e:"hi", et:'simple'}]); + pass([{es:["hi"]}]); + return pass([{ena:5}]); + }); + + it('disallows invalid syntax', function() { + fail(undefined); + fail({}); + fail("hi"); + fail(true); + fail(false); + fail(0); + fail(10); + fail([{}]); + fail([{invalid:true}]); + fail([10, {}]); + fail([10, {invalid:true}]); + return fail([10, 'hi']); + }); + + it('throws if there is any empty leaves', function() { + fail([]); + fail(['x']); + fail(['x', {}]); + fail(['x', []]); + fail([10]); + fail([10, {}]); + return fail([10, []]); + }); + + it('ensures path components are non-zero integers or strings', function() { + fail([-1, {r:{}}]); + fail([0.5, {r:{}}]); + fail([true, {r:{}}]); + fail([false, {r:{}}]); + fail([null, {r:{}}]); + return fail([undefined, {r:{}}]); + }); + + it('does not allow two pickups or two drops in a component', function() { + fail([{p:0, r:{}}]); + fail([{p:1, r:{}}]); + fail(['x', {p:0, r:{}}]); + fail(['x', {p:1, r:{}}]); + + fail([{d:0, i:'hi'}]); + fail([{d:1, i:'hi'}]); + fail([10, {d:0, i:'hi'}]); + return fail([10, {d:1, i:'hi'}]); + }); + + it('throws if there are mismatched pickups / drops', function() { + fail([{p:0}]); + fail([{d:0}]); + fail(['x', {p:0}]); + fail([10, {p:0}]); + fail(['x', {d:0}]); + return fail([10, {d:0}]); + }); + + it('throws if pick/drop indexes dont start at 0', function() { + fail([['x', {p:1}], ['y', {d:1}]]); + return fail([[10, {p:1}], [20, {d:1}]]); + }); + + it('throws if a descent starts with an edit', () => fail([10, [{i:"hi"}]])); + + it('throws if descents are out of order', function() { + fail(['x', ['b', {r:{}}], ['a', {r:{}}]]); + fail(['x', [10, {r:{}}], [5, {r:{}}]]); + fail(['x', ['a', {r:{}}], [5, {r:{}}]]); + fail(['x', ['a', {r:{}}], ['a', {r:{}}]]); + return fail(['x', [10, {r:{}}], [10, {r:{}}]]); + }); + + it('throws if descents start with the same scalar', () => fail(['x', ['a', {r:{}}], ['a', {e:{}}]])); + + it('throws if descents have two adjacent edits', function() { + fail([{r:{}}, {p:0}]); + fail(['x', {r:{}}, {p:0}]); + return fail(['x', {r:{}}, {p:0}, 'y', {r:{}}]); + }); + + it.skip('does not allow ops to overwrite their own inserted data', function() { + fail([{i:{x:5}}, 'x', {i:6}]); + return fail([{i:['hi']}, 0, {i:'omg'}]); + }); + + it.skip('does not allow immediate data directly parented in other immediate data', function() { + fail([{i:{}}, 'x', {i:5}]); + fail([{i:{x:5}}, 'x', 'y', {i:6}]); + return fail([{i:[]}, 0, {i:5}]); + }); + + it('does not allow the final item to be a single descent', () => fail(['a', ['b', {r:{}}]])); // It should be ['a', 'b', r:{}] + + it('does not allow anything after the descents at the end', function() { + fail([[1, {r:{}}], [2, {r:{}}], 5]); + fail([[1, {r:{}}], [2, {r:{}}], 5, {r:{}}]); + return fail([[1, {r:{}}], [2, {r:{}}], {r:{}}]); + }); + + it('allows removes inside removes', function() { + pass(['x', {r:true}, 'y', {r:true}]); + pass(['x', {r:{}}, 'y', {r:true}]); + pass([['x', {r:true}, 'y', {p:0}, 'z', {r:true}], ['y', {d:0}]]); + return pass([['x', {r:{}}, 'y', {p:0}, 'z', {r:true}], ['y', {d:0}]]); + }); + + it('allows inserts inside inserts', function() { + pass([1, {i:{}}, 'x', {i:10}]); + return pass([[0, 'x', {p:0}], [1, {i:{}}, 'x', {d:0}, 'y', {i:10}]]); + }); + + it.skip('fails if the operation drops items inside something it picked up', function() { + fail(['x', {r:true}, 1, {i:'hi'}]); + fail(['x', {d:0}, 1, {p:0}]); + return fail([{r:true}, 1, {p:0, d:0}]); + }); + + return describe('edit', function() { + it('requires all edits to specify their type', function() { + fail([{e:{}}]); + fail([5, {e:{}}]); + return pass([{e:{}, et:'simple'}]); + }); + + it('allows edits to have null or false for the operation', function() { + // These aren't valid operations according to the simple type, but the + // type doesn't define a checkValidOp so we wouldn't be able to tell + // anyway. + pass([{e:null, et:'simple'}]); + pass([5, {e:null, et:'simple'}]); + pass([{e:false, et:'simple'}]); + return pass([5, {e:false, et:'simple'}]); + }); + + it('does not allow an edit to use an unregistered type', function() { + fail([{e:{}, et:'an undefined type'}]); + return fail([{e:null, et:'an undefined type'}]); + }); + + it('does not allow two edits in the same operation', function() { + fail([{e:{}, et:'simple', es:[1,2,3]}]); + fail([{es:[], ena:5}]); + return fail([{e:{}, et:'simple', ena:5}]); + }); + + it('fails if the type is missing', () => fail([{et:'missing', e:{}}])); + + it('does not allow anything inside an edited subtree'); + + it.skip('does not allow an edit inside removed or picked up content', function() { + fail([{r:true}, 1, {es:['hi']}]); + pass([1, {r:true}, 1, {es:['hi']}]); + fail(['x', {r:true}, 1, {es:['hi']}]); + pass([[1, {p:0}, 1, {es:['hi']}], [2, {d:0}]]); + fail([['x', {p:0}, 1, {es:['hi']}], ['y', {d:0}]]); + + // This is actually ok. + return pass([ 0, { p: 0 }, [ 'a', { es: [], r: true } ], [ 'x', { d: 0 } ] ]); + }); + + return it.skip('does not allow you to drop inside something that was removed', function() { + // These insert into the next list item + pass([[1, {r:true}, 1, {d:0}], [2, {p:0}]]); + pass([1, {p: 0}, 'x', {d: 0}]); + + // But this is not ok. + return fail(['x', {p:0}, 'a', {d:0}]); + }); + }); +}); + + describe('normalize', function() { + const n = function(opIn, expect) { + if (expect === undefined) { expect = opIn; } + const op = type.normalize(opIn); + return assert.deepStrictEqual(op, expect); + }; + + it('does the right thing for noops', function() { + n(null); + return n([], null); + }); + + it('normalizes some regular ops', function() { + n([{i:'hi'}]); + n([{i:'hi'}, 1,2,3], [{i:'hi'}]); + n([[1,2,3, {p:0}], [1,2,3, {d:0}]], [1,2,3, {p:0, d:0}]); + n([[1,2,3, {p:0}], [1,2,30, {d:0}]], [1,2, [3, {p:0}], [30, {d:0}]]); + return n([[1,2,30, {p:0}], [1,2,3, {d:0}]], [1,2, [3, {d:0}], [30, {p:0}]]); + }); + + it('will let you insert null', () => n([{i:null}])); + + it('normalizes embedded ops when available', function() { + n([{es:[0, 'hi']}], [{es:['hi']}]); + n([{et:'text-unicode', e:['hi']}], [{es:['hi']}]); + n([{et:'text-unicode', e:[0, 'hi']}], [{es:['hi']}]); + n([{et:'simple', e:{}}]); + n([{et:'number', e:5}], [{ena:5}]); + return n([{ena:5}]); + }); + + it.skip('normalizes embedded removes', function() { + n([1, {r:true}, 2, {r:true}], [1, {r:true}]); + return n([{r:true}, 2, {r:true}], [{r:true}]); + }); + + it('throws if the type is missing', () => + // Not sure if this is the best behaviour but ... eh. + assert.throws(() => n([{et:'missing', e:{}}])) + ); + + return it('corrects weird pick and drop ids', () => n([['x', {p:1}], ['y', {d:1}]], [['x', {p:0}], ['y', {d:0}]])); +}); + +// ****** Apply ****** + + describe('apply', function() { + it('Can set properties', function() { + apply({ + doc: [], + op: [0, {i:17}], + expect: [17]}); + + return apply({ + doc: {}, + op: ['x', {i:5}], + expect: {x:5}}); + }); + + it('can edit the root', function() { + apply({ + doc: {x:5}, + op: [{r:true}], + expect: undefined + }); + + apply({ + doc: '', + op: [{r:true}], + expect: undefined + }); + + apply({ + doc: 'hi', + op: [{r:true, i:null}], + expect: null + }); + + apply({ + doc: 'hi', + op: [{es:[2, ' there']}], + expect: 'hi there' + }); + + assert.throws(() => type.apply(null, [{i:5}])); + + apply({ + doc: undefined, + op: [{i:5}], + expect: 5 + }); + + return apply({ + doc: {x:5}, + op: [{r:{}, i:[1,2,3]}], + expect: [1,2,3]}); + }); + + // TODO: And an edit of the root. + + it('can move 1', () => apply({ + doc: {x:5}, + op: [['x', {p:0}], ['y', {d:0}]], + expect: {y:5}}) ); + + it('can move 2', () => apply({ + doc: [0,1,2], + op: [[1, {p:0}], [2, {d:0}]], + expect: [0,2,1]}) ); + + it('can handle complex list index stuff', () => apply({ + doc: [0,1,2,3,4,5], + op: [[1, {r:{}, i:11}], [2, {r:{}, i:12}]], + expect: [0,11,12,3,4,5]}) ); + + it('correctly handles interspersed descent and edits', () => apply({ + doc: {x: {y: {was:'y'}, was:'x'}}, + op: [['X',{d:0},'Y',{d:1}], ['x',{p:0},'y',{p:1}]], + expect: {X: {Y: {was:'y'}, was:'x'}}}) ); + + it('can edit strings', () => apply({ + doc: "errd", + op: [{es:[2,"maghe"]}], + expect: "ermagherd" + }) + ); + + it('can edit numbers', () => apply({ + doc: 5, + op: [{ena:10}], + expect: 15 + }) + ); + + it('can edit child numbers', () => apply({ + doc: [20], + op: [0, {ena:-100}], + expect: [-80]}) ); + + it('can edit subdocuments using an embedded type', () => apply({ + doc: {str:'hai'}, + op: [{e:{position:2, text:'wai'}, et:'simple'}], + expect: {str:'hawaii'}}) ); + + it('applies edits after drops', () => apply({ + doc: {x: "yooo"}, + op: [['x', {p:0}], ['y', {d:0, es:['sup']}]], + expect: {y: "supyooo"}}) ); + + it('throws when the op traverses missing items', function() { + assert.throws(() => type.apply([0, 'hi'], [1, {p:0}, 'x', {d:0}])); + return assert.throws(() => type.apply({}, [{p:0}, 'a', {d:0}])); + }); + + return it('throws if the type is missing', () => assert.throws(() => type.apply({}, [{et:'missing', e:{}}]))); +}); + + + describe('apply path', function() { + it('does not modify path when op is unrelated', function() { + path(['a', 'b', 'c'], {op: null}); + path(['a', 'b', 'c'], {op: ['x', {i:5}]}); + path(['a', 'b', 'c'], {op: ['x', {r:true}]}); + path(['a', 'b', 'c'], {op: [['x', {p:0}], ['y', {d:0}]]}); + path([1,2,3], {op: [2, {i:5}]}); + path([1,2,3], {op: [1, 2, 4, {i:5}]}); + path([1], {op: [1, 2, {r:true}]}); + return path(['x'], {op: ['x', 'y', {r:true}]}); + }); + + it('adjusts list indicies', function() { + path([2], {op: [1, {i:5}], expect: [3]}); + path([2], {op: [2, {i:5}], expect: [3]}); + path([2], {op: [1, {r:true}], expect: [1]}); + path([2], {op: [[1, {p:0}], [3, {d:0}]], expect: [1]}); + path([2], {op: [[1, {d:0}], [3, {p:0}]], expect: [3]}); + return path([2], {op: [[2, {d:0}], [3, {p:0}]], expect: [3]}); + }); + + it('returns null when the object at the path was removed', function() { + path(['x'], {op: [{r:true}], expect: null}); + path(['x'], {op: ['x', {r:true}], expect: null}); + path([1], {op: [{r:true}], expect: null}); + return path([1], {op: [1, {r:true}], expect: null}); + }); + + it('moves the path', function() { + path(['a', 'z'], {op: [['a', {p:0}], ['y', {d:0}]], expect: ['y', 'z']}); + path(['a', 'b'], {op: [['a', 'b', {p:0}], ['z', {d:0}]], expect: ['z']}); + path(['a', 'b'], {op: [['a', 'b', 'c', {p:0}], ['z', {d:0}]]}); + path([1,2], {op: [[1, {p:0}], [10, {d:0}]], expect: [10, 2]}); + path([1,2], {op: [[1, 2, {p:0}], [10, {d:0}]], expect: [10]}); + path([1,2], {op: [1, [1, {d:0}], [2, {p:0}]], expect: [1, 1]}); + return path([1,2], {op: [[1, 2, 3, {p:0}], [10, {d:0}]]}); + }); + + it('handles pick parent and move', () => path(['a', 'b', 'c'], {op: [['a', {r:true}, 'b', {p:0}], ['x', {d:0}]], expect: ['x', 'c']})); + + it('adjusts indicies under a pick', () => path(['a', 'b', 10], {op: [['a', {p:0}, 'b', 1, {r:true}], ['x', {d:0}]], expect: ['x', 'b', 9]})); + + it.skip('gen ops', function() {}); + // This should do something like: + // - Generate a document + // - Generate op, a random operation + // - Generate a path to somewhere in the document and an edit we can do there -> op2 + // - Check that transform(op2, op) == op2 at transformPosition(path) or something like that. + + return it('calls transformPosition with embedded string edits if available', function() { + // For embedded string operations (and other things that have + // transformPosition or transformPosition or whatever) we should call that. + path(['x','y','z', 1], {op: ['x','y','z', {es:['abc']}], expect: ['x','y','z', 4]}); + path(['x','y','z', 1], {op: ['x','y','z', {es:['💃']}], expect: ['x','y','z', 2]}); + return path(['x','y','z'], {op: ['x','y','z', {es:['💃']}], expect: ['x','y','z']}); + }); +}); + + +// ******* Compose ******* + + describe('compose', function() { + it('composes empty ops to nothing', () => compose({ + op1: null, + op2: null, + expect: null + }) + ); + + describe('op1 drop', function() { + it('vs remove', () => compose({ + op1: [['x', {p:0}], ['y', {d:0}]], + op2: ['y', {r:true}], + expect: ['x', {r:true}]}) ); + + it('vs remove parent', () => compose({ + op1: [['x', {p:0}], ['y', 0, {d:0}]], + op2: ['y', {r:true}], + expect: [['x', {r:true}], ['y', {r:true}]]}) ); + + it('vs remove child', () => compose({ + op1: [['x', {p:0}], ['y', {d:0}]], + op2: ['y', 'a', {r:true}], + expect: [['x', {p:0}, 'a', {r:true}], ['y', {d:0}]]}) ); + + it('vs remove and pick child', () => compose({ + op1: [['x', {p:0}], ['y', {d:0}]], + op2: [['y', {r:true}, 'a', {p:0}], ['z', {d:0}]], + expect: [['x', {r:true}, 'a', {p:0}], ['z', {d:0}]]}) ); + + it('vs pick', () => compose({ + op1: [['x', {p:0}], ['y', {d:0}]], + op2: [['y', {p:0}], ['z', {d:0}]], + expect: [['x', {p:0}], ['z', {d:0}]]}) ); + + return it('is transformed by op2 picks', () => compose({ + op1: [['x', {p:0}], ['y', 10, {d:0}]], + op2: ['y', 0, {r:true}], + expect: [['x', {p:0}], ['y', [0, {r:true}], [9, {d:0}]]]}) ); + }); + + describe('op1 insert', function() { + it('vs remove', () => compose({ + op1: ['x', {i:{a:'hi'}}], + op2: ['x', {r:true}], + expect: null + }) + ); + + it('vs remove parent', () => compose({ + op1: ['x', 0, {i:{a:'hi'}}], + op2: ['x', {r:true}], + expect: ['x', {r:true}]}) ); + + it('vs remove child', () => compose({ + op1: ['x', {i:{a:'hi', b:'woo'}}], + op2: ['x', 'a', {r:true}], + expect: ['x', {i:{b:'woo'}}]}) ); + + it('vs remove and pick child', () => compose({ + op1: ['x', {i:{a:'hi', b:'woo'}}], + op2: [['x', {r:true}, 'a', {p:0}], ['y', {d:0}]], + expect: ['y', {i:'hi'}]}) ); + + it('vs remove an embedded insert', () => compose({ + op1: ['x', {i:{}}, 'y', {i:'hi'}], + op2: ['x', 'y', {r:true}], + expect: ['x', {i:{}}]}) ); + + it('vs remove from an embedded insert', () => compose({ + op1: ['x', {i:{}}, 'y', {i:[1,2,3]}], + op2: ['x', 'y', 1, {r:true}], + expect: ['x', {i:{}}, 'y', {i:[1, 3]}]}) ); + + it('picks the correct element of an embedded insert', () => compose({ + op1: ['x', {i:['a', 'b', 'c']}, 1, {i:'XX'}], + op2: [['x', 1, {p:0}], ['y', {d:0}]], + expect: [['x', {i:['a', 'b', 'c']}], ['y', {i:'XX'}]]}) ); + + it('picks the correct element of an embedded insert 2', () => compose({ + op1: ['x', {i:['a', 'b', 'c']}, 1, {i:'XX'}], + op2: [['x', 3, {p:0}], ['y', {d:0}]], // should grab 'c'. + expect: [['x', {i:['a', 'b']}, 1, {i:'XX'}], ['y', {i:'c'}]]}) ); + + + it('moves all children', () => compose({ + op1: ['x', {i:{}}, 'y', {i:[1,2,3]}], + op2: [['x', {p:0}], ['z', {d:0}]], + expect: ['z', {i:{}}, 'y', {i:[1,2,3]}]}) ); + + it('removes all children', () => compose({ + op1: ['x', {i:{}}, 'y', {i:[1,2,3]}], + op2: ['x', {r:true}], + expect: null + }) + ); + + it('removes all children when removed at the destination', () => compose({ + op1: [['x', {p:0}], ['y', {d:0}, 0, {i:'hi'}]], + op2: ['y', {r:true}], + expect: ['x', {r:true}]}) ); + + it('vs op2 insert', () => compose({ // Inserts aren't folded together. + op1: [{i:{}}], + op2: ['x', {i:'hi'}], + expect: [{i:{}}, 'x', {i:'hi'}]}) ); + + it('vs op2 string edit', () => compose({ + op1: [{i:'hi'}], + op2: [{es:[2, ' there']}], + expect: [{i:'hi', es:[2, ' there']}]}) ); + + return it('vs op2 number edit', () => compose({ + op1: [{i:10}], + op2: [{ena:20}], + expect: [{i:10, ena:20}]}) ); + }); + + describe('op1 edit', function() { + it('removes the edit if the edited object is deleted', () => compose({ + op1: ['x', {es:['hi']}], + op2: ['x', {r:true}], + expect: ['x', {r:true}]}) ); + + it('removes the edit in an embedded insert 1', () => compose({ + op1: ['x', {i:'', es:['hi']}], + op2: ['x', {r:true}], + expect: null + }) + ); + + it('removes the edit in an embedded insert 2', () => compose({ + op1: ['x', {i:['']}, 0, {es:['hi']}], + op2: ['x', 0, {r:true}], + expect: ['x', {i:[]}]}) ); + + it('composes string edits', () => compose({ + op1: [{es:['hi']}], + op2: [{es:[2, ' there']}], + expect: [{es:['hi there']}]}) ); + + it('composes number edits', () => compose({ + op1: [{ena:10}], + op2: [{ena:-8}], + expect: [{ena:2}]}) ); + + it('transforms and composes edits', () => compose({ + op1: ['x', {es:['hi']}], + op2: [['x', {p:0}], ['y', {d:0, es:[2, ' there']}]], + expect: [['x', {p:0}], ['y', {d:0, es:['hi there']}]]}) ); + + it('preserves inserts with edits', () => compose({ + op1: ['x', {i:'hi'}], + op2: [['x', {p:0}], ['y', {d:0, es:[' there']}]], + expect: ['y', {i:'hi', es:[' there']}]}) ); + + it('allows a different edit in the same location', () => compose({ + op1: ['x', {es:['hi']}], + op2: ['x', {r:true, i:'yo', es:[2, ' there']}], + expect: ['x', {r:true, i:'yo', es:[2, ' there']}]}) ); + + return it('throws if the type is missing', () => assert.throws(() => type.compose([{et:'missing', e:{}}], [{et:'missing', e:{}}]))); + }); + + describe('op2 pick', () => + it('gets untransformed by op1 drops', () => + ({ + op1: [5, {i:'hi'}], + op2: [6, {r:true}], + expect: [5, {r:true, i:'hi'}] + }) + ) + ); + + describe('op1 insert containing a drop', () => + it('vs pick at insert', () => compose({ + op1: [['x', {p:0}], ['y', {i:{}}, 'x', {d:0}]], + op2: [['y', {p:0}], ['z', {d:0}]], + expect: [['x', {p:0}], ['z', {i:{}}, 'x', {d:0}]]}) ) + ); + + describe('fuzzer tests', () => + it('complicated transform of indicies', () => compose({ + op1: [ 0, { p: 0 }, 'x', 2, { d: 0 } ], + op2: [ 0, 'x', 0, { r: true } ], + expect: [ + [0, {p:0}, 'x', 1, {d:0}], + [1, 'x', 0, {r:true}] + ]}) ) + ); + + describe('setnull interaction', function() { + // Currently failing. + it('reorders items inside a setnull region', () => compose({ + op1: [{i:[]}, [0, {i:'a'}], [1, {i:'b'}]], + op2: [[0, {p:0}], [1, {d:0}]], + expect: [{i:[]}, [0, {i:'b'}], [1, {i:'a'}]]}) ); + + it('lets a setnull child be moved', () => compose({ + op1: ['list', {i:[]}, 0, {i:'hi'}], + op2: [['list', 0, {p:0}], ['z', {d:0}]], + expect: [['list', {i:[]}], ['z', {i:'hi'}]]}) ); + + return it('lets a setnull child get modified', () => compose({ + op1: [{i:[]}, 0, {i:['a']}], + op2: [0, 0, {r:'a', i:'b'}], + expect: [{i:[]}, 0, {i: []}, 0, {i: 'b'}]}) ); + }); + //expect: [{i:[]}, 0, {i:['b']}] # Maybe better?? + + return describe('regression', function() { + it('skips op2 drops when calculating op1 drop index simple', () => compose({ + op1: [[ 0, { p: 0 } ], [ 2, { d: 0 } ]], + op2: [[ 0, { p: 0 } ], [ 1, { d: 0 } ]], + expect: [ [ 0, { p: 1 } ], [ 1, { p: 0, d: 0 } ], [ 2, { d: 1 } ] ]}) ); + + it('skips op2 drops when calculating op1 drop index complex', () => compose({ + op1: [[0, {p:0, d:1}], [1, {p:1}], [2, {d:0}]], + op2: [[0, {p:0}], [1, {d:0}]], + // expect: [[0, {p:1}], [1, {d:0, p:0}], [2, d:1]] + expect: [[0, {p:1}], [1, {p:0, d:0}], [2, {d:1}]]}) ); + + it('3', () => compose({ + op1: [ { i: [ null, [] ] }, 0, { i: '' } ], + op2: [ 1, { p: 0 }, 0, { d: 0 } ], + // ... it'd be way more consistent to drop the null separately rather than merging it?? + expect: [ { i: [ [] ] }, [ 0, { i: '' } ], [ 1, 0, { i: null } ] ]}) ); + + return it('4', () => compose({ // This one triggered a bug in cursor! + op1: [ 0, + [ 0, [ 'a', { r: true } ], [ 'b', { d: 0 } ] ], + [ 2, { p: 0 } ] ], + op2: [ 0, 0, 'c', { i: 'd' } ], + expect: [ 0, + [ 0, [ 'a', { r: true } ], [ 'b', { d: 0 } ], [ 'c', { i: 'd' } ] ], + [ 2, { p: 0 } ] + ]}) ); + }); +}); + + // *** Old stuff + describe('old compose', function() { + it('gloms together unrelated edits', function() { + compose({ + op1: [['a', {p:0}], ['b', {d:0}]], + op2: [['x', {p:0}], ['y', {d:0}]], + expect: [['a', {p:0}], ['b', {d:0}], ['x', {p:1}], ['y', {d:1}]]}); + + return compose({ + op1: [2, {i:'hi'}], + op2: [0, 'x', {r:true}], + expect: [[0, 'x', {r:true}], [2, {i:"hi"}]]}); + }); + + it('translates drops in objects', () => compose({ + op1: ['x', ['a', {p:0}], ['b', {d:0}]], // x.a -> x.b + op2: [['x', {p:0}], ['y', {d:0}]], // x -> y + expect: [['x', {p:0}, 'a', {p:1}], ['y', {d:0}, 'b', {d:1}]]}) ); // x.a -> y.b, x -> y + + it('untranslates picks in objects', () => compose({ + op1: [['x', {p:0}], ['y', {d:0}]], // x -> y + op2: [['y', 'a', {p:0}], ['z', {d:0}]], // y.a -> z + expect: [['x',{p:0},'a',{p:1}], ['y',{d:0}], ['z',{d:1}]]}) ); // x.a -> z, x -> y + + it('insert gets carried wholesale', () => compose({ + op1: ['x', {i:'hi there'}], + op2: [['x', {p:0}], ['y', {d:0}]], // x -> y + expect: ['y', {i:'hi there'}]}) ); + + it('insert gets edited by the op', () => compose({ + op1: ['x', {i:{a:1, b:2, c:3}}], + op2: [['x', 'a', {p:0}], ['y', {d:0}]], + expect: [['x', {i:{b:2, c:3}}], ['y', {i:1}]]}) ); + + return it('does not merge mutual inserts', () => compose({ + op1: [{i:{}}], + op2: ['x', {i:"hi"}], + expect: [{i:{}}, 'x', {i:'hi'}]}) ); +}); + + // TODO: List nonsense. + + // TODO: Edits. + + +// ****** Transform ****** + + describe('transform', function() { + describe('op1 pick', function() { + it('vs delete', () => xf({ + op1: [['x', {p:0}], ['y', {d:0}]], + op2: ['x', {r:true}], + expect: null + }) + ); + it('vs delete parent', () => xf({ + op1: [['x', 'a', {p:0}], ['y', {d:0}]], + op2: ['x', {r:true}], + expect: null + }) + ); + it('vs delete parent 2', () => xf({ + op1: ['x', ['a', {p:0}], ['b', {d:0}]], + op2: ['x', {r:true}], + expect: null + }) + ); + + it('vs pick', () => xf({ + op1: [['x', {p:0}], ['z', {d:0}]], + op2: [['x', {p:0}], ['y', {d:0}]], + // Consider adding a conflict for this case. + expectLeft: [['y', {p:0}], ['z', {d:0}]], + expectRight: null + }) + ); + it('vs pick parent', () => xf({ + op1: [['x', 'a', {p:0}], ['z', {d:0}]], + op2: [['x', {p:0}], ['y', {d:0}]], + expect: [['y', 'a', {p:0}], ['z', {d:0}]]}) ); + + it('vs pick and pick child', () => xf({ // regression + op1: [ // a -> xa, a.c -> xc + ['a', {p:0}, 'c', {p:1}], + ['xa', {d:0}], + ['xc', {d:1}] + ], + op2: [['a', {p:0}], ['b', {d:0}]], // a -> b + expectLeft: [ + ['b', {p:0}, 'c', {p:1}], + ['xa', {d:0}], + ['xc', {d:1}] + ], + expectRight: [ + ['b', 'c', {p:0}], + ['xc', {d:0}] + ]}) ); + + it('vs edit', () => xf({ + op1: [['x', {p:0}], ['z', {d:0}]], + op2: ['x', {es:['hi']}], + expect: [['x', {p:0}], ['z', {d:0}]]}) ); + + it('vs delete, drop', () => xf({ + op1: [['x', {p:0}], ['y', {d:0}]], + op2: [['a', {p:0}], ['x', {r:0, d:0}]], + expect: null + }) + ); + + it('vs delete, insert', () => xf({ + op1: [['x', {p:0}], ['y', {d:0}]], + op2: ['x', {r:0, i:5}], + expect: null + }) + ); + + it('vs pick, drop to self', + () => xf({ + op1: [['x', {p:0}], ['y', {d:0}]], + op2: [['x', {p:0}], ['y', {d:0}]], + expect: null + }) , + + () => xf({ + op1: [['a', 1, {p:0}], ['y', {d:0}]], + op2: [['a', 1, {p:0}], ['y', {d:0}]], + expect: null + }) + ); + + it('vs pick, drop', () => xf({ + op1: [['x', {p:0}], ['z', {d:0}]], // x->z + op2: [['a', {p:0}], ['x', {p:1, d:0}], ['y', {d:1}]], // a->x, x->y + expectLeft: [['y', {p:0}], ['z', {d:0}]], + expectRight: null + }) + ); + + it('vs pick, insert', () => xf({ + op1: [['x', {p:0}], ['z', {d:0}]], + op2: [['x', {p:0, i:5}], ['y', {d:0}]], + expectLeft: [['y', {p:0}], ['z', {d:0}]], + expectRight: null + }) + ); + + return it('vs pick, edit', () => + ({ + op1: [['x', {p:0}], ['z', {d:0}]], + op2: [['x', {es:['hi'], p:0}], ['y', {d:0}]], + expectLeft: [['y', {p:0}], ['z', {d:0}]], + expectRight: null + }) + ); + }); + + describe('op1 delete', function() { + it('vs delete', () => xf({ + op1: ['x', {r:true}], + op2: ['x', {r:true}], + expect: null + }) + ); + it('vs delete parent', () => xf({ + op1: ['x', 'a', {r:true}], + op2: ['x', {r:true}], + expect: null + }) + ); + + it('vs pick', () => xf({ + op1: ['x', {r:true}], + op2: [['x', {p:0}], ['y', {d:0}]], + expect: ['y', {r:true}]}) ); + it('vs pick parent', () => xf({ + op1: ['x', 'a', {r:true}], + op2: [['x', {p:0}], ['y', {d:0}]], + expect: ['y', 'a', {r:true}]}) ); + + it('vs pick and drop', () => xf({ + op1: ['x', {r:true}], + op2: [['a', {p:0}], ['x', {d:0, p:1}], ['z', {d:1}]], + expect: ['z', {r:true}]}) ); + + it('vs edit', () => xf({ + op1: ['x', {r:true}], + op2: ['x', {es:['hi']}], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: ['x', {r:true}]}) ); + + it('vs move and insert', () => xf({ + op1: [ 'a', 1, { r: true } ], + op2: [ + [ 'a', { p: 0 } ], + [ 'b', { d: 0 }, [ 0, { i: 5 } ], [ 1, { i: 5 } ] ] + ], + expect: ['b', 3, {r:true}]}) ); + + return describe('vs pick child', function() { + it('move in', () => xf({ + op1: ['x', {r:true}], + op2: [['a', {p:0}], ['x', 'y', {d:0}]], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: ['x', {r:true}, 'y', {r:true}]}) ); // Also ok if its just x, r:true + + it('move across', () => xf({ + op1: ['x', {r:true}], // delete doc.x + op2: ['x', ['y', {p:0}], ['z', {d:0}]], + expect: ['x', {r:true}]}) ); + + it('move out', () => xf({ + op1: ['x', {r:true}], + op2: [['x', 'y', {p:0}], ['y', {d:0}]], // move doc.x.y -> doc.y + expect: [['x', {r:true}], ['y', {r:true}]]}) ); // delete doc.x and doc.y + + it('multiple out', () => xf({ + op1: ['x', {r:true}], + op2: [['x', 'y', {p:0}, 'z', {p:1}], ['y', {d:0}], ['z', {d:1}]], + expect: [['x', {r:true}], ['y', {r:true}], ['z', {r:true}]]}) ); + + it('chain out', () => xf({ + op1: ['x', {r:true}], + op2: [['x', 'y', {p:0}], ['y', {p:1}], ['z', {d:0}, 'a', {d:1}]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: [['y', {p:0}], ['z', 'a', {d:0}]] + }, // cMv(['y'], ['z', 'a']) + expect: [['x', {r:true}], ['z', {r:true}, 'a', {r:true}]]}) ); + + return it('mess', () => xf({ + // yeesh + op1: [['x', {r:true}, 'y', 'z', {p:0}], ['z', {d:0}]], + op2: [['x', 'y', {p:0}], ['y', {d:0}]], + expect: [['x', {r:true}], ['y', {r:true}, 'z', {p:0}], ['z', {d:0}]]}) ); + }); + }); + + describe('op1 drop', function() { + it('vs delete parent', () => xf({ + op1: [['x', {p:0}], ['y', 'a', {d:0}]], + op2: ['y', {r:true}], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: ['x', {r:true}]}) ); + + it('vs a cancelled parent', () => xf({ + // This is actually a really complicated case. + op1: [['x', 'y', {p:0}], ['y', {p:1}], ['z', {d:0}, 'a', {d:1}]], + op2: ['x', {r:true}], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [['y', {p:0}], ['z', 'a', {d:0}]] + }, // c1: cMv(['y'], ['z', 'a']) + expect: ['y', {r:true}]}) ); + + it('vs pick parent', () => xf({ + op1: [['x', {p:0}], ['y', 'a', {d:0}]], + op2: [['y', {p:0}], ['z', {d:0}]], + expect: [['x', {p:0}], ['z', 'a', {d:0}]]}) ); + + it('vs drop', () => xf({ + op1: [['x', {p:0}], ['z', {d:0}]], + op2: [['y', {p:0}], ['z', {d:0}]], + conflict: { type: DROP_COLLISION + }, + expectLeft: [['x', {p:0}], ['z', {r:true, d:0}]], + expectRight: ['x', {r:true}]}) ); + + it('vs drop (list)', () => xf({ + op1: [[0, {p:0}], [4, {d:0}]], + op2: [[5, {d:0}], [10, {p:0}]], + expectLeft: [[0, {p:0}], [4, {d:0}]], + expectRight: [[0, {p:0}], [5, {d:0}]]}) ); + + it('vs drop (chained)', () => xf({ + op1: [['a', {p:1}], ['x', {p:0}], ['z', {d:0}, 'a', {d:1}]], + op2: [['y', {p:0}], ['z', {d:0}]], + conflict: { + type: DROP_COLLISION, + op1: [['x', {p:0}], ['z', {d:0}]] + }, //cMv(['x'], ['z']) + expectLeft: [['a', {p:0}], ['x', {p:1}], ['z', {r:true, d:1}, 'a', {d:0}]], + expectRight: [['a', {r:true}], ['x', {r:true}]]}) ); + + it('vs insert', () => xf({ + op1: [['x', {p:0}], ['z', {d:0}]], + op2: ['z', {i:5}], + conflict: { type: DROP_COLLISION + }, + expectLeft: [['x', {p:0}], ['z', {r:true, d:0}]], + expectRight: ['x', {r:true}]}) ); + + it('vs pick (a->b->c vs b->x)', () => xf({ + op1: [['a', {p:0}], ['b', {p:1, d:0}], ['c', {d:1}]], + op2: [['b', {p:0}], ['x', {d:0}]], + expectLeft: [['a', {p:0}], ['b', {d:0}], ['c', {d:1}], ['x', {p:1}]], + expectRight: [['a', {p:0}], ['b', {d:0}]]}) ); + + return describe.skip('vs move inside me', function() { + // Note: This is *not* blackholeing! The edits are totally fine; we + // just need one edit to win. + // The current behaviour just nukes both. + it('in objects', () => xf({ + op1: [['x', {p:0}], ['y', 'a', {d:0}]], + op2: [['x', 'a', {d:0}], ['y', {p:0}]], + expectLeft: [['x', {p:0}, 'a', {p:1}], ['y', {d:1}, 'x', {d:0}]], + expectRight: null + }) + ); + + it('in lists', () => xf({ + op1: [0, {p:0}, 'x', {d:0}], + op2: [[0, 'y', {d:0}], [1, {p:0}]], + expectLeft: [0, {p:0, d:1}, ['x', {d:0}], ['y', {p:1}]], + expectRight: null + }) + ); + + return it('multiple', () => xf({ + // a->x.a, b->x.b + op1: [['a', {p:0}], ['b', {p:1}], ['x', 'a', {d:0}, 'b', {d:1}]], + op2: [['a', 'x', {d:0}], ['x', {p:0}]], // x->a.x + expectLeft: [['a', {p:0}, 'x', {p:1}], ['b', {p:2}], + ['x', {d:1}, ['a', {d:0}], ['b', {d:2}]]], + expectRight: null + }) + ); + }); + }); + + describe('op1 insert', function() { + it('vs delete parent', () => xf({ + op1: ['y', 'a', {i:5}], + op2: ['y', {r:true}], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: null + }) + ); + + it('vs pick parent', () => xf({ + op1: ['y', 'a', {i:5}], + op2: [['y', {p:0}], ['z', {d:0}]], + expect: ['z', 'a', {i:5}]}) ); + + it('vs drop', () => xf({ + op1: ['z', {i:5}], + op2: [['y', {p:0}], ['z', {d:0}]], + conflict: { type: DROP_COLLISION + }, + expectLeft: ['z', {r:true, i:5}], + expectRight: null + }) + ); + + it('vs insert', () => xf({ + op1: ['z', {i:5}], + op2: ['z', {i:10}], + conflict: { type: DROP_COLLISION + }, + expectLeft: ['z', {r:true, i:5}], + expectRight: null + }) + ); + + it('vs insert at list position', () => xf({ + op1: [5, {i:'hi'}], + op2: [5, {i:'there'}], + expectLeft: [5, {i:'hi'}], + expectRight: [6, {i:'hi'}]}) ); + + it('vs identical insert', () => xf({ + op1: ['z', {i:5}], + op2: ['z', {i:5}], + expect: null + }) + ); + + // This is the new setNull for setting up schemas + it('vs embedded inserts', function() { + xf({ + op1: ['x', {i:{}}], + op2: ['x', {i:{}}, 'y', {i:5}], + expect: null + }); + + xf({ + op1: ['x', {i:{}}, 'y', {i:5}], + op2: ['x', {i:{}}], + expect: ['x', 'y', {i:5}]}); + + xf({ + op1: ['x', {i:{}}, 'y', {i:5}], + op2: ['x', {i:{}}, 'y', {i:5}], + expect: null + }); + + return xf({ + op1: ['x', {i:{}}, 'y', {i:5}], + op2: ['x', {i:{}}, 'y', {i:6}], + conflict: { + type: DROP_COLLISION, + op1: ['x', 'y', {i:5}], + op2: ['x', 'y', {i:6}] + }, + expectLeft: ['x', 'y', {r:true, i:5}], + expectRight: null + }); + }); + + return it('with embedded edits', () => xf({ + op1: [{i:'', es:['aaa']}], + op2: [{i:'', es:['bbb']}], + expectLeft: [{es:['aaa']}], + expectRight: [{es:[3, 'aaa']}]}) ); + }); + + describe('op1 edit', function() { + it('vs delete', () => xf({ + op1: ['x', {es:['hi']}], + op2: ['x', {r:true}], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: null + }) + ); + + it('vs delete parent', () => xf({ + op1: ['x', 'y', {es:['hi']}], + op2: ['x', {r:true}], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: null + }) + ); + + it('vs pick', () => xf({ + op1: ['x', {es:['hi']}], + op2: [['x', {p:0}], ['y', {d:0}]], + expect: ['y', {es:['hi']}]}) ); + + it('vs edit string', () => xf({ + op1: ['x', {es:['ab']}], + op2: ['x', {es:['cd']}], + expectLeft: ['x', {es:['ab']}], + expectRight: ['x', {es:[2, 'ab']}]}) ); + + it('vs edit number', () => xf({ + op1: [{ena:5}], + op2: [{ena:100}], + expect: [{ena:5}]}) ); + + it('throws if edit types arent compatible', () => assert.throws(() => type.transform([{es:[]}], [{ena:5}], 'left'))); + + it('vs move and edit', () => xf({ + op1: ['x', {es:[1, 'ab']}], + op2: [['x', {p:0}], ['y', {d:0, es:[{d:1}, 'cd']}]], + expectLeft: ['y', {es:['ab']}], + expectRight: ['y', {es:[2, 'ab']}]}) ); + + return it('throws if the type is missing', () => assert.throws(() => type.transform([{et:'missing', e:{}}], [{et:'missing', e:{}}], 'left'))); + }); + + describe('op2 cancel move', function() { + it('and insert', () => xf({ + op1: ['x', {r:true}], + op2: [['x', 'a', {p:0}], ['y', {d:0}, 'b', {i:5}]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: ['y', 'b', {i:5}] + }, + expect: [['x', {r:true}], ['y', {r:true}, 'b', {r:true}]]}) ); + + return it('and another move (rm x vs x.a -> y, q -> y.b)', () => xf({ + op1: ['x', {r:true}], + op2: [['q', {p:1}], ['x', 'a', {p:0}], ['y', {d:0}, 'b', {d:1}]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: [['q', {p:0}], ['y', 'b', {d:0}]] + }, + expect: [['x', {r:true}], ['y', {r:true}, 'b', {r:true}]]}) ); + }); + + describe('op2 list move an op1 drop', function() { + it('vs op1 remove', () => xf({ + op1: [[0, {r:true}, 'a', {i:'hi'}], [5, {r:true}]], + op2: [[1, {p:0}], [4, {d:0}]], + expect: [[0, {r:true}], [3, 'a', {i:'hi'}], [5, {r:true}]]}) ); + + it('vs op1 remove 2', () => xf({ + op1: [[0, {r:true}, 'a', {i:'hi'}], [1, {r:true}], [2, {r:true}]], + op2: [[3, {p:0}], [4, {d:0}]], + expect: [[0, {r:true}], [1, {r:true}, 'a', {i:'hi'}], [2, {r:true}]]}) ); + + it('vs op1 insert before', () => xf({ + op1: [[0, {i:'a'}], [1, {i:'b'}], [2, 'a', {i:'hi'}]], + op2: [[0, {p:0}], [1, {d:0}]], + expect: [[0, {i:'a'}], [1, {i:'b'}], [3, 'a', {i:'hi'}]]}) ); + + + return it('vs op1 insert before and replace', () => xf({ + op1: [[0, {i:'xx'}, 'a', {r:true}], [1, 'a', {i:'hi'}]], + op2: [[0, {p:0}], [3, {d:0}]], + expect: [[0, {i:'xx'}], [3, 'a', {r:true}], [4, 'a', {i:'hi'}]]}) ); + }); + + + return describe('list', () => + describe('drop', function() { + it('transforms by p1 drops', () => xf({ + op1: [[5, {i:5}], [10, {i:10}]], + op2: [9, {i:9}], + expectLeft: [[5, {i:5}], [10, {i:10}]], + expectRight: [[5, {i:5}], [11, {i:10}]]}) ); + + it('transforms by p1 picks'); + it('transforms by p2 picks'); + return it('transforms by p2 drops'); + }) + ); + }); + + describe('conflicts', function() { + describe('drop into remove / rm unexpected', function() { + // xfConflict does both xf(op1, op2, left) and xf(op2, op1, right), and + // uses invConflict. So this also tests RM_UNEXPECTED_CONTENT with each + // test case. + it('errors if you insert', () => xf({ + op1: ['a', 'b', {i:5}], + op2: ['a', {r:true}], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: null + }) + ); + + it('errors if you drop', () => xf({ + op1: [['a', {p:0}], ['x', 'b', {d:0}]], + op2: ['x', {r:true}], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: ['a', {r:true}]}) ); + + it('errors if you rm then insert in a child', () => xf({ + op1: ['a', 'b', {r:true, i:5}], + op2: ['a', {r:true}], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', 'b', {i:5}] + }, + expect: null + }) + ); + + it('errors if the object is replaced', () => xf({ + op1: ['a', 'b', {i:5}], + op2: ['a', {r:true, i:10}], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: ['a', {r:true}] + }, + expect: null + }) + ); + + it('handles a delete of the source parent by op2', () => xf({ + op1: [['a', {p:0}], ['b', 'b', {d:0}]], + op2: [['a', {p:0}], ['b', {r:true}, 'c', {d:0}]], + conflictLeft: { + type: RM_UNEXPECTED_CONTENT, + op2: ['b', {r:true}] + }, + expectLeft: ['b', 'c', {r:true}], + expectRight: null + }) + ); + + return it.skip('returns symmetric errors when both ops delete the other', () => xf({ + // The problem here is that there's two conflicts we want to return. + // Which one should be returned first? It'd be nice for the order of + // conflict returning to be symmetric - that is, if we know multiple + // conflicts happen, order them based on left/right. But I haven't done + // that, so we get different conflicts out of this in a first pass. + op1: [ [ 'x', { r: true } ], [ 'y', 'a', { i: {} } ] ], + op2: [ [ 'x', 'a', { i: {} } ], [ 'y', { r: true } ] ], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: ['x', {r:true}]}) ); + }); + + describe('overlapping drop', function() { + it('errors if two ops insert different content into the same place in an object', () => xf({ + op1: ['x', {i:'hi'}], + op2: ['x', {i:'yo'}], + conflict: { type: DROP_COLLISION + }, + expectLeft: ['x', {r:true, i:'hi'}], + expectRight: null + }) + ); + + it('does not conflict if inserts are identical', () => xf({ + op1: ['x', {i:'hi'}], + op2: ['x', {i:'hi'}], + expectLeft: null, + expectRight: null + }) + ); + + it('does not conflict if the two operations make identical moves', () => xf({ + op1: [['a', {p:0}], ['x', {d:0}]], + op2: [['a', {p:0}], ['x', {d:0}]], + expect: null + }) + ); // ??? Also ok for left: ['x', p:0, d:0] + + it('does not conflict if inserts are into a list', () => xf({ + op1: [1, {i:'hi'}], + op2: [1, {i:'yo'}], + expectLeft: [1, {i:'hi'}], + expectRight: [2, {i:'hi'}]}) ); + + it('errors if the inserts are at the root', () => xf({ + op1: [{i:1}], + op2: [{i:2}], + conflict: { type: DROP_COLLISION + }, + expectLeft: [{r:true, i:1}], + expectRight: null + }) + ); + + it('errors with insert vs drop', () => xf({ + op1: ['x', {i:'hi'}], + op2: [['a', {p:0}], ['x', {d:0}]], + // ???? + conflict: { type: DROP_COLLISION + }, + expectLeft: ['x', {r:true, i:'hi'}], + expectRight: null + }) + ); + + it('errors with drop vs insert', () => xf({ + op1: [['a', {p:0}], ['x', {d:0}]], + op2: ['x', {i:'hi'}], + conflict: { type: DROP_COLLISION + }, + expectLeft: [['a', {p:0}], ['x', {r:true, d:0}]], + expectRight: ['a', {r:true}]}) ); + + it('errors with drop vs drop', () => xf({ + op1: [['a', {p:0}], ['x', {d:0}]], + op2: [['b', {p:0}], ['x', {d:0}]], + conflict: { type: DROP_COLLISION + }, + expectLeft: [['a', {p:0}], ['x', {r:true, d:0}]], + expectRight: ['a', {r:true}]}) ); + + return it('errors if the two sides insert in the vacuum', () => xf({ + op1: [['a', {p:0}], ['b', {d:0}], ['c', {i:5}]], + op2: [['a', {p:0}], ['b', {i:6}], ['c', {d:0}]], + conflictLeft: { + type: DROP_COLLISION, + op1: [['a', {p:0}], ['b', {d:0}]], + op2: ['b', {i:6}] + }, + expectLeft: [['b', {r:true, d:0}], ['c', {p:0, i:5}]], + conflictRight: { + type: DROP_COLLISION, + op1: ['c', {i:5}], + op2: [['a', {p:0}], ['c', {d:0}]] + }, + expectRight: null + }) + ); + }); + + + describe('discarded edit', function() { + it('edit removed directly', () => xf({ + op1: ['a', {es:[]}], + op2: ['a', {r:true}], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: null + }) + ); + + return it('edit inside new content throws RM_UNEXPECTED_CONTENT', () => xf({ + op1: ['a', 'b', {i: 'hi', es:[]}], + op2: ['a', {r:true}], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', 'b', {i:'hi'}] + }, + expect: null + }) + ); + }); + + return describe('blackhole', function() { + it('detects and errors', () => xf({ + op1: [['x', {p:0}], ['y', 'a', {d:0}]], + op2: [['x', 'a', {d:0}], ['y', {p:0}]], + conflict: { type: BLACKHOLE + }, + expect: ['x', {r:true}, 'a', {r:true}]}) ); // Also equivalent: ['x', r:true] + + it('blackhole logic does not apply when op2 removes parent', () => xf({ + // TODO: Although you wouldn't know it, since this result is very similar. + op1: [['x', {p:0}], ['y', 'xx', 'a', {d:0}]], + op2: [['x', 'a', {d:0}], ['y', {p:0}, 'xx', {r:true}]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: ['y', 'xx', {r:true}] + }, + expect: ['x', {r:true}, 'a', {r:true}]}) ); // Also ok: ['x', r:true] + + it('blackhole logic still applies when op2 inserts', () => xf({ + op1: [['x', {p:0}], ['y', 'a', {i:{}}, 'b', {d:0}]], + op2: [['x', 'a', {i:{}}, 'b', {d:0}], ['y', {p:0}]], + conflict: { + type: BLACKHOLE, + op1: [['x', {p:0}], ['y', 'a', 'b', {d:0}]], + op2: [['x', 'a', 'b', {d:0}], ['y', {p:0}]] + }, + expect: ['x', {r:true}, 'a', {r:true}, 'b', {r:true}]}) ); + + it('blackholes items in lists correctly', () => xf({ + op1: [1, {p:0}, 'a', {d:0}], + op2: [[1, 'b', {d:0}], [2, {p:0}]], + conflict: { type: BLACKHOLE + }, + expect: [1, {r:true}, 'b', {r:true}]}) ); + + it('blackholes items despite scrambled pick and drop slots', () => xf({ + op1: [ [ 'a', { p: 1, d: 1 } ], [ 'x', { p: 0 } ], [ 'y', 'a', { d: 0 } ] ], + op2: [ [ 'x', 'a', { d: 0 } ], [ 'y', { p: 0 } ] ], + conflict: { + type: BLACKHOLE, + op1: [ [ 'x', { p: 0 } ], [ 'y', 'a', { d: 0 } ] ] + }, + expect: [['a', {p:0, d:0}], ['x', {r:true}, 'a', {r:true}]]}) ); + + it('handles chained blackholes', () => xf({ + op1: [ [ 'a', { p: 0 } ], // a->b.b, c->d.d + [ 'b', 'b', { d: 0 } ], + [ 'c', { p: 1 } ], + [ 'd', 'd', { d: 1 } ] + ], + op2: [ [ 'a', 'a', { d: 1 } ], // b->c.c, d->a.a + [ 'b', { p: 0 } ], + [ 'c', 'c', { d: 0 } ], + [ 'd', { p: 1 } ] + ], + conflict: { type: BLACKHOLE + }, + // c1: cMv(['a'], ['b', 'b']) + // c2: cMv(['b'], ['c', 'c']) + expect: [['a', {r:true}, 'a', {r:true}], ['c', {r:true}, 'c', {r:true}]]}) ); + + return it('creates conflict return values with valid slot ids', () => xf({ + op1: [['a', {p:0}], ['b', {d:0}], ['x', {p:1}], ['y', 'a', {d:1}]], + op2: [['x', 'a', {d:0}], ['y', {p:0}]], + conflict: { + type: BLACKHOLE, + op1: [['x', {p:0}], ['y', 'a', {d:0}]] + }, + expect: [['a', {p:0}], ['b', {d:0}], ['x', {r:true}, 'a', {r:true}]]}) ); + }); +}); + + + describe('transform-old', function() { + it('foo', () => + xf({ + op1: [ + ['x', ['a', {p:0}], ['b', {d:0}]], + ['y', ['a', {p:1}], ['b', {d:1}]] + ], + op2: ['x', {r:true}], + expect: ['y', ['a', {p:0}], ['b', {d:0}]]}) + ); + + // it 'hard', -> + // op1: ['x', [1, r:true], [2, r:true, es:['hi']]] # Edit at index 4 originally. + // # move the edited string to .y[4] which + // op2: [['x', 4, p:0], ['y', [2, r:true], [4, d:0]]] + // expect: + + describe('object edits', () => + it('can reparent with some extra junk', () => xf({ + op1: [['x', {p:0}], ['y', {d:0}]], + op2: [ + ['_a', {d:1}], + ['_x', {d:0}], + ['x', {p:0}, 'a', {p:1}] + ], + expectLeft: [['_x', {p:0}], ['y', {d:0}]], + expectRight: null + }) + ) + ); // the object was moved fair and square. + + describe('deletes', function() { + + it.skip('delete parent of a move', () => xf({ + // The current logic of transform actually just burns everything (in a + // consistant way of course). I'm not sure if this is better or worse - + // basically we'd be saying that if a move could end up in one of two places, + // put it in the place where it won't be killed forever. But that introduces new + // complexity, so I'm going to skip this for now. + + // x.a -> a, delete x + op1: [['x', {r:true}, 'a', {p:0}], ['z', {d:0}]], + // x.a -> x.b. + op2: ['x', ['a', {p:0}], ['b', {d:0}]], + expect: [['x', {r:true}, 'b', {p:0}], ['z', {d:0}]]}) ); // TODO: It would be better to do this in both cases. + //expectRight: ['x', r:true] + + return it('awful delete nonsense', function() { + xf({ + op1: [['x', {r:true}], ['y', {i:'hi'}]], // delete doc.x, insert doc.y + op2: [['x', 'a', {p:0}], ['y', {d:0}]], // move doc.x.a -> doc.y + expect: [['x', {r:true}], ['y', {r:true, i:'hi'}]]}); // del doc.x and doc.y, insert doc.y + + xf({ + op1: [['x', 'a', {p:0}], ['y', {d:0}]], // x.a -> y + op2: [['x', {r:true}], ['y', {i:'hi'}]], // delete x, ins y + expect: null + }); + + return xf({ + op1: [10, {r:true}], + op2: [[5, {d:0}], [10, 1, {p:0}]], + expect: [[5, {r:true}], [11, {r:true}]]}); + }); + }); + // And how do those indexes interact with pick / drop operations?? + + + describe('swap', function() { + const swap = [ + ['a', {p:0}, 'b', {p:1}], + ['b', {d:1}, 'a', {d:0}] + ]; + + it('noop vs swap', () => xf({ + op1: null, + op2: swap, + expect: null + }) + ); + + return it('can swap two edits', () => xf({ + op1: ['a', {es:['a edit']}, 'b', {es:['b edit']}], + op2: swap, + expect: ['b', {es:['b edit']}, 'a', {es:['a edit']}]}) ); + }); + + describe('lists', function() { + it('can rewrite simple list indexes', function() { + xf({ + op1: [10, {es:['edit']}], + op2: [0, {i:'oh hi'}], + expect: [11, {es:['edit']}]}); + + xf({ + op1: [10, {r:true}], + op2: [0, {i:'oh hi'}], + expect: [11, {r:true}]}); + + return xf({ + op1: [10, {i:{}}], + op2: [0, {i:'oh hi'}], + expect: [11, {i:{}}]}); + }); + + it('can change the root from an object to a list', () => xf({ + op1: ['a', {es:['hi']}], + op2: [{i:[], r:true}, [0, {d:0}], ['a', {p:0}]], + expect: [0, {es:['hi']}]}) ); + + it('can handle adjacent drops', () => xf({ + op1: [[11, {i:1}], [12, {i:2}], [13, {i:3}]], + op2: [0, {r:true}], + expect: [[10, {i:1}], [11, {i:2}], [12, {i:3}]]}) ); + + it('fixes drop indexes correctly 1', () => xf({ + op1: [[0, {r:true}], [1, {i:'hi'}]], + op2: [1, {r:true}], + expect: [0, {r:true, i:'hi'}]}) ); + + it('list drop vs delete uses the correct result index', function() { + xf({ + op1: [2, {i:'hi'}], + op2: [2, {r:true}], + expect: [2, {i:'hi'}]}); + + return xf({ + op1: [3, {i:'hi'}], + op2: [2, {r:true}], + expect: [2, {i:'hi'}]}); + }); + + it('list drop vs drop uses the correct result index', () => xf({ + op1: [2, {i:'hi'}], + op2: [2, {i:'other'}], + expectLeft: [2, {i:'hi'}], + expectRight: [3, {i:'hi'}]}) ); + + it('list drop vs delete and drop', function() { + xf({ + op1: [2, {i:'hi'}], + op2: [2, {r:true, i:'other'}], + expectLeft: [2, {i:'hi'}], + expectRight: [3, {i:'hi'}]}); + + xf({ + op1: [3, {i:'hi'}], + op2: [[2, {r:true}], [3, {i:'other'}]], + expect: [2, {i:'hi'}]}); + + return xf({ + op1: [4, {i:'hi'}], + op2: [[2, {r:true}], [3, {i:'other'}]], + expectLeft: [3, {i:'hi'}], + expectRight: [4, {i:'hi'}]}); + }); + + it('list delete vs drop', function() { + xf({ + op1: [1, {r:true}], + op2: [2, {i:'hi'}], + expect: [1, {r:true}]}); + + xf({ + op1: [2, {r:true}], + op2: [2, {i:'hi'}], + expect: [3, {r:true}]}); + + return xf({ + op1: [3, {r:true}], + op2: [2, {i:'hi'}], + expect: [4, {r:true}]}); + }); + + it('list delete vs delete', () => + xf({ + op1: [1, {r:true}], + op2: [1, {r:true}], + expect: null + }) + ); // It was already deleted. + + it('fixes drop indexes correctly 2', () => xf({ + op1: [[0, {r:true}], [1, {i:'hi'}]], + op2: [2, {r:true}], // Shouldn't affect the op. + expect: [[0, {r:true}], [1, {i:'hi'}]]}) ); + + it('insert vs delete parent', () => xf({ + op1: [2, 'x', {i:'hi'}], + op2: [2, {r:true}], + conflict: { type: RM_UNEXPECTED_CONTENT + }, + expect: null + }) + ); + + it('transforms against inserts in my own list', () => + xf({ //[0,1,2,3] -> [a,0,b,1,2,3...] + op1: [[0, {i:'a'}], [2, {i:'b'}]], + op2: [1, {r:true}], + expect: [[0, {i:'a'}], [2, {i:'b'}]]}) + ); + + it('vs cancelled op2 drop', () => xf({ + doc: {x:{a:'x.a'}, y:['a','b','c']}, + op1: [['x', {r:true}], ['y', 3, {i:5}]], + op2: [['x', 'a', {p:0}], ['y', 2, {d:0}]], + expect: [['x', {r:true}], ['y', [2, {r:true}], [3, {i:5}]]]}) ); + + it('vs cancelled op1 drop', () => xf({ + op1: [['x', {p:0}], ['y', [3, {d:0}], [4, {i:5}]]], + op2: ['x', {r:true}], + expect: ['y', 3, {i:5}]}) ); + + it('vs cancelled op1 pick', () => xf({ + doc: Array.from('abcdefg'), + op1: [[1, {p:0}], [4, {r:true, i:4}], [6, {d:0}]], + op2: [1, {r:true}], + expect: [[3, {r:true}], [4, {i:4}]]}) ); + + it('xxxxx 1', () => diamond({ // TODO Regression. + doc: Array.from('abcdef'), + op1: [[1, {p:0, i:'AAA'}], [3, {i:'BBB'}], [5, {d:0}]], + op2: [1, {r:true}]}) ); + + return it('xxxxx 2', () => diamond({ + doc: Array.from('abcdef'), + op1: [[1, {p:0, i:'AAA'}], [3, {d:0}], [5, {i:'CCC'}]], + op2: [1, {r:true}]}) ); + }); + + + return describe('edit', function() { + it('transforms edits by one another', () => xf({ + op1: [1, {es:[2, 'hi']}], + op2: [1, {es:['yo']}], + expect: [1, {es:[4, 'hi']}]}) ); + + it('copies in ops otherwise', () => xf({ + op1: ['x', {e:{position:2, text:'wai'}, et:'simple'}], + op2: ['y', {r:true}], + expect: ['x', {e:{position:2, text:'wai'}, et:'simple'}]}) ); + + it('allows edits at the root', () => xf({ + op1: [{e:{position:2, text:'wai'}, et:'simple'}], + op2: [{e:{position:0, text:'omg'}, et:'simple'}], + expect: [{e:{position:5, text:'wai'}, et:'simple'}]}) ); + + it('applies edits in the right order', () => xf({ + // Edits happen *after* the drop phase. + op1: [1, {es:[2, 'hi']}], + op2: [[1, {i:{}}], [2, {es:['yo']}]], + expect: [2, {es:[4, 'hi']}]}) ); + + return it('an edit on a deleted object goes away', () => xf({ + op1: [1, {es:[2, 'hi']}], + op2: [1, {r:"yo"}], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: [1, {r:true}] + }, // .... It'd be better if this copied the remove. + expect: null + }) + ); + }); + }); + + // TODO Numbers + + +// ***** Test cases found by the fuzzer which have caused issues + return describe('fuzzer tests', function() { + it('asdf', () => apply({ + doc: { the: '', Twas: 'the' }, + op: [ 'the', { es: [] } ], + expect: { the: '', Twas: 'the' }}) ); + + it('does not duplicate list items from edits', () => apply({ + doc: ['eyes'], + op: [ 0, { es: [] } ], + expect: ['eyes']}) ); + + it('will edit the root document', () => apply({ + doc: '', + op: [{es:[]}], + expect: '' + }) + ); + + // ------ These have nothing to do with apply. TODO: Move them out of this grouping. + + it('diamond', () => + // TODO: Do this for all combinations. + diamond({ + doc: Array.from('abcde'), + op1: [ [ 0, { p: 0 } ], [ 1, { d: 0 } ] ], + op2: [ [ 0, { p: 0 } ], [ 4, { d: 0 } ] ]}) + ); + + it('shuffles lists correctly', () => xf({ + op1: [ [ 0, { p: 0 } ], [ 1, { d: 0 } ] ], + op2: [ [ 0, { p: 0 } ], [ 10, { d: 0 } ] ], + expectLeft: [ [ 1, { d: 0 } ], [ 10, { p: 0 } ] ], + expectRight: null + }) + ); + + it('inserts before edits', function() { + xf({ + op1: [0, 'x', {i:5}], + op2: [0, {i:35}], + expect: [1, 'x', {i:5}]}); + + return xf({ + op1: [0, {es:[]}], + op2: [0, {i:35}], + expect: [1, {es:[]}]}); + }); + + it('duplicates become noops in a list', + () => xf({ + op1: [0,{"p":0,"d":0}], + op2: [0,{"p":0,"d":0}], + expectLeft: [0,{"p":0,"d":0}], // This is a bit weird. + expectRight: null + }) , + + () => xf({ + op1: [0, {r:true, i:'a'}], + op2: [0, {i:'b'}], + expectLeft: [[0, {i:'a'}], [1, {r:true}]], + expectRight: [1, {r:true, i:'a'}]}) , + + () => xf({ + op1: [0, {r:true, i:5}], + op2: [0, {r:true}], + expect: [0, {i:5}]}) ); + + it('p1 pick descends correctly', function() { + xf({ + op1: [2, {r:true}, 1, {es:['hi']}], + op2: [3, 1, {r:true}], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [2, 1, {es:['hi']}] + }, + expect: [2, {r:true}]}); + + return xf({ + op1: [[2, {r:true}, 1, {es:['hi']}], [3, 1, {r:true}]], + op2: [3, 2, {r:true}], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [2, 1, {es:['hi']}] + }, + expect: [[2, {r:true}], [3, 1, {r:true}]]}); + }); + + it('transforms picks correctly', () => xf({ + op1: [1, 1, {r:true}], + op2: [0, {p:0, d:0}], + expect: [1, 1, {r:true}]}) ); + + it('pick & drop vs insert after the picked item', () => xf({ + op1: [0, {p:0,d:0}], // Remove / insert works the same. + op2: [1, {i:"hi"}], + expectLeft: [0, {p:0,d:0}], + expectRight: [[0, {p:0}], [1, {d:0}]]}) ); + + it('pick same item vs shuffle list', () => xf({ + op1: [1, ['x', {p:0}], ['y', {d:0}]], + op2: [1, {d:0}, 'x', {p:0}], + expectLeft: [1, {p:0}, 'y', {d:0}], + expectRight: null + }) + ); + + it('remove the same item in a list', () => xf({ + op1: [ 0, { r: true } ], + op2: [ 0, { r: true } ], + expect: null + }) + ); + + it('rm vs hold item', () => xf({ + op1: [ 0, { r: true } ], + op2: [ 0, { p: 0, d: 0 } ], + expect: [ 0, { r: true } ]}) ); + + it('moves child elements correctly', () => xf({ + doc: ['a', [0,10,20], 'c'], + op1: [ 1, 0, { p: 0, d: 0 } ], + op2: [ [ 1, { d: 0 } ], [ 2, { p: 0 } ] ], + expect: [ 2, 0, { d:0, p:0 } ]}) ); + + it('moves list indexes', () => xf({ + doc: [[], 'b', 'c'], + op1: [ [ 0, 'hi', { d: 0 } ], [ 1, { p: 0 } ] ], + op2: [ [ 0, { p: 0 } ], [ 20, { d: 0 } ] ], + expect: [[0, {p:0}], [19, 'hi', {d:0}]]}) ); + + it('insert empty string vs insert null', () => xf({ + doc: undefined, + op1: [{i:'hi'}], + op2: [{i:null}], + conflict: { type: DROP_COLLISION + }, + expectLeft: [{r:true, i:'hi'}], + expectRight: null + }) + ); + + it('move vs emplace', () => xf({ + doc: ['a', 'b'], + op1: [[0, {p:0}], [1, {d:0}]], + op2: [1, {p:0, d:0}], + expectLeft: [0, {p:0, d:0}], + expectRight: [[0, {p:0}], [1, {d:0}]]}) ); + + it('rm chases a subdocument that was moved out', () => xf({ + doc: [ [ 'aaa' ] ], + op1: [ 0, { r: true } ], + op2: [ 0, { d: 0 }, 0, { p: 0 } ], // Valid because lists. + expect: [[0, {r:true}], [1, {r:true}]]}) ); + + it('colliding drops', () => xf({ + doc: [ 'a', 'b', {} ], + op1: [[0, {p:0}], [1, 'x', {d:0}]], // -> ['b', x:'a'] + op2: [1, {p:0}, 'x', {d:0}], // -> ['a', x:'b'] + conflict: { type: DROP_COLLISION + }, + expectLeft: [[0, {p:0}, 'x', {d:0}], [1, 'x', {r:true}]], + expectRight: [0, {r:true}]}) ); + + it('transform crash', () => xf({ + op1: [ [ 'the', { r: true, d: 0 } ], [ 'whiffling', { p: 0 } ] ], + op2: [ 'the', { p: 0, d: 0 } ], + expect: [ [ 'the', { d: 0, r: true } ], [ 'whiffling', { p: 0 } ] ]}) ); + + it('transforms drops when the parent is moved by a remove', () => xf({ + op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]], + op2: ['a', 0, {r:1}], + expect: [['a', {p:0}], ['b', {d:0}, 0, {i:2}]]}) ); + + it('transforms drops when the parent is moved by a drop', () => xf({ + op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]], + op2: ['a', 0, {i:1}], + expect: [['a', {p:0}], ['b', {d:0}, 2, {i:2}]]}) ); + + it('transforms conflicting drops obfuscated by a move', () => xf({ + op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]], + op2: ['a', 1, {i:1}], + expectLeft: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]], + expectRight: [['a', {p:0}], ['b', {d:0}, 2, {i:2}]]}) ); + + it('transforms edits when the parent is moved', () => xf({ + op1: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 1, 'xxx' ] } ] ], + op2: [ 'x', { es: [ {d: 1}, 'Z' ] } ], + expectLeft: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 'xxx' ] } ] ], + expectRight: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 1, 'xxx' ] } ] ]}) ); + + it('xf lots', () => xf({ + op1: [['a', {p:0}], ['b', {d:0, es:['hi']}]], + op2: [['a', {p:0}], ['c', {d:0}]], + expectLeft: [['b', {d:0, es:['hi']}], ['c', {p:0}]], + expectRight: ['c', {es:['hi']}]}) ); + + it('inserts are moved back by the other op', () => xf({ + op1: [['a', {p:0}], ['b', {d:0}, 'x', {i:'hi'}]], + op2: [['a', {p:0}], ['c', {d:0}]], + expectLeft: [['b', {d:0}, 'x', {i:'hi'}], ['c', {p:0}]], + expectRight: ['c', 'x', {i:'hi'}]}) ); + + it('more awful edit moves', () => xf({ + op1: [['a', {p:0}], ['c', {d:0}, 'x', {es:[]}]], + op2: ['a', ['b', {d:0}], ['x', {p:0}]], + expect: [['a', {p:0}], ['c', {d:0}, 'b', {es:[]}]]}) ); + + it('inserts null', () => xf({ + op1: [ 'x', 'a', { i: null } ], + op2: [ [ 'x', { p: 0 } ], [ 'y', { d: 0 } ] ], + expect: [ 'y', 'a', { i: null } ]}) ); + + it('preserves local insert if both sides delete', () => xf({ + op1: [ { i: {}, r: true }, 'x', { i: 'yo' } ], + op2: [ { r: true } ], + expect: [ { i: {} }, 'x', { i: 'yo' } ]}) ); + + it('handles insert/delete vs move', () => xf({ + op1: [ 'a', { i: {}, r: true }, 'x', { i: 'yo' } ], + op2: [ [ 'a', { p: 0 } ], [ 'b', { d: 0 } ] ], + expect: [ [ 'a', { i: {} }, 'x', { i: 'yo' } ], [ 'b', { r: true }, ] ]}) ); + + it('insert pushes edit target', () => xf({ + op1: [[ 0, { i: "yo" } ], [ 1, 'a', { es: [] }]], + op2: [0, [ 'a', { p: 0 } ], [ 'b', { d: 0 } ]], + expect: [[0, { i: 'yo' }], [1, 'b', { es: [] }]]}) ); + + it('composes simple regression', function() { + compose({ + op1: [ 0, { p: 0, d: 0 } ], + op2: [ { r: true } ], + expect: [ { r: true }, 0, { r: true } ]}); + + return compose({ + op1: [ 'a', 1, { r: true } ], + op2: [ 'a', { r: true } ], + expect: [ 'a', { r: true }, 1, { r: true } ]}); + }); + + it('ignores op2 inserts for index position after op1 insert', () => xf({ + op1: [ { r:true, i: [] }, 0, { i: '' } ], + op2: [ 0, { i: 0 } ], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [{r:true}] + }, + expect: [ { r: true, i: [] }, 0, { r:true, i: '' } ]}) ); + + it('edit moved inside a removed area should be removed', () => xf({ + op1: [[ 0, { r: true } ], [ 2, { es: [ ] } ]], + op2: [[ 0, 'x', { d: 0 } ], [ 3, { p: 0 } ]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [0, {r:true}] + }, + expect: [ 0, { r: true }, 'x', {r:true} ]}) ); + + it('advances indexes correctly with mixed numbers', () => xf({ + op1: [ [ 'x', [ 0, { p: 0 } ], [ 1, { d: 1 } ] ], [ 'y', { p: 1 } ], [ 'zzz', { d: 0 } ] ], + op2: [ [ 'x', 2, { i: 'hi' } ], [ 'y', { p: 0 } ], [ 'z', { d: 0 } ] ], + expectLeft: [ [ 'x', [ 0, { p: 1 } ], [ 1, { d: 0 } ] ], [ 'z', { p: 0 } ], [ 'zzz', { d: 1 } ] ], + expectRight: [ [ 'x', 0, { p: 0 } ], [ 'zzz', { d: 0 } ] ]}) ); + + it('handles index positions past cancelled drops 1', () => xf({ + op1: [ 0, { r: true, i: [ '' ] } ], + op2: [ [ 0, { p: 0, d: 0 } ], [ 1, { i: 23 } ] ], + expectLeft: [ 0, { r: true, i: [ '' ] } ], + expectRight: [ [ 0, { r: true } ], [ 1, { i: [ '' ] } ] ]}) ); + + it('handles index positions past cancelled drops 2', () => xf({ + // This looks more complicated, but its a simpler version of the above test. + op1: [ [ 'a', { r: true } ], [ 'b', 0, { i: 'hi' } ] ], + op2: [ [ 'a', { p: 0 } ], [ 'b', [ 0, { d: 0 } ], [ 1, { i: 'yo' } ] ] ], + expectLeft: [ 'b', 0, { i: 'hi', r: true } ], + expectRight: [ 'b', [ 0, { r: true } ], [ 1, { i: 'hi' } ] ]}) ); + + it('calculates removed drop indexes correctly', () => xf({ + op1: [ [ 0, { i: 'hi', p: 0 } ], [ 1, 1, { d: 0 } ], [ 2, { r: true } ] ], + op2: [ [ 0, { i: 'yo', p: 0 } ], [ 1, 1, { d: 0 } ] ], + expectLeft: [ [ 0, { i: 'hi' } ], [ 1, 1, { p: 0 } ], [ 2, { r: true }, 1, { d: 0 } ] ], + expectRight: [ [ 1, { i: 'hi' } ], [ 2, { r: true } ] ]}) ); + + it('removed drop indexes calc regression', () => xf({ + op1: [ [ 1, { p: 0 }, 'burbled', { d: 0 } ], [ 3, { r: true } ] ], + op2: [ [ 0, { i: 'to', r: true } ], [ 1, { p: 1 }, [ 'its', { d: 0 } ], [ 'thought', { d: 1 } ] ], [ 3, { p: 0 } ] ], + expectLeft: [ 1, [ 'burbled', { d: 0 } ], [ 'its', { r: true } ], [ 'thought', { p: 0 } ] ], + expectRight: [ 1, 'its', { r: true } ]}) ); + + it('removed drop indexes tele to op1 pick', () => xf({ + op1: [ 'a', 0, [ 0, { es: [] } ], [ 2, { r: true } ] ], + op2: [ [ 'a', { p: 0 }, 0, 0, { p: 1 } ], [ 'b', { d: 0 }, 0, 1, 0, { d: 1 } ] ], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', 0, 2, {r:true}], + op2: [ [ 'a', 0, 0, { p: 0 } ], [ 'b', 0, 1, 0, { d: 0 } ] ] + }, + expect: [ 'b', 0, 1, { r: true }, 0, { r: true } ]}) ); + + it('tracks removed drop index teleports', () => xf({ + // rm 0.a, move 0.b -> 0.c + doc: [{a:['a'], b:'b'}], + op1: [ 0, [ 'a', { r: true } ], [ 'b', { p: 0 } ], [ 'c', { d: 0 } ] ], // [{c:'b'}] + op2: [ 0, { d: 0, p: 1 }, [ 0, { d: 1 } ], [ 'a', { p: 0 } ] ], // [[{b:'b'}, 'a']] + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [0, 'a', {r:true}], + op2: [0, {p:0}, 0, {d:0}] + }, + expect: [ 0, { r: true }, 0, { r: true } ]}) ); + + it('handles transforming past cancelled move', () => xf({ + op1: [ [ 0, { r: true } ], [ 10, { i: [ '' ] } ] ], + op2: [ 0, { p: 0, d: 0 } ], + expect: [ [ 0, { r: true } ], [ 10, { i: [ '' ] } ] ]}) ); + + it('correctly adjusts indexes in another fuzzer great', () => xf({ + op1: [ [ 0, { d: 0, r: true } ], [ 3, { p: 0 } ] ], + op2: [ [ 0, { p: 0 } ], [ 3, { d: 0 } ] ], + expect: [[0, {d:0}], [2, {p:0}], [3, {r:true}]]}) ); + + it('op2 moves into something op1 removes and op1 moves into that', () => xf({ + op1: [ [ 'a', { r: true }, 'aa', { p: 0 } ], [ 'b', 'x', { d: 0 } ] ], + op2: [ [ 'a', 'bb', { d: 0 } ], [ 'b', { p: 0 } ] ], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', {r:true}] + }, + expect: [ 'a', { r: true }, ['aa', {r:true}], ['bb', {r:true}]]}) ); // Also ok if we miss the second rs. + + it('op2 moves into op1 remove edge cases', function() { + // Sorry not minified. + xf({ + op1: [ 'Came', 0, [ 0, { r: true }, 'he', { p: 0 } ], [ 1, { d: 0 }, 0, { i: 'time' } ] ], + op2: [ 'Came', 0, [ 0, 'he', [ 0, { d: 0 } ], [ 1, { es: [] } ] ], [ 1, { p: 0 } ] ], + expectLeft: [ 'Came', 0, 0, { r: true, d: 0 }, [ 0, { i: 'time' } ], [ 'he', { p: 0 } ] ], + expectRight: [ 'Came', 0, 0, { r: true, d: 0 }, [ 1, { i: 'time' } ], [ 'he', { p: 0 } ] ]}); + + return xf({ + op1: [ [ 0, [ 1, { p: 0 } ], [ 2, { r: true } ] ], [ 1, 'xxx', { d: 0 } ] ], + op2: [ 0, 1, { i: {}, p: 0 }, 'b', { d: 0 } ], + expectLeft: [ [ 0, [ 1, 'b', { p: 0 } ], [ 2, { r: true } ] ], [ 1, 'xxx', { d: 0 } ] ], + expectRight: [ 0, 2, { r: true } ]}); + }); + + it('translates indexes correctly in this fuzzer find', () => xf({ + op1: [ 0, { p: 0 }, 'x', { d: 0 } ], + op2: [ [ 0, { p: 0, d: 0 } ], [ 1, { i: 'y' } ] ], + expectLeft: [[0, { p: 0 }], [1, 'x', { d: 0 }]], + expectRight: null + }) + ); + + it('buries children of blackholed values', () => xf({ + op1: [ [ 0, [ 'a', { p: 0 } ], [ 'b', { d: 0 } ], [ 'c', { d: 1 } ] ], [ 1, { p: 1 } ] ], + op2: [ 0, { p: 0 }, 'x', { d: 0 } ], + // This is a bit interesting. The question is, which op2 picks and drops + // should we include in the output? For now the answer is that we include + // anything in both ops thats going to end up inside the blackholed + // content. + conflict: { type: BLACKHOLE + }, + + // op1: [[0, 'c', d:0], [1, p:0]] + expect: [ 0, {r: true}, 'x', {r:true} ]}) ); + + it('does not conflict when removed target gets moved inside removed container', function() { + // This edge case is interesting because we don't generate the same + // conflicts on left and right. We want our move of a.x to escape the + // object before removing it, but when we're right, the other operation's + // move holds the object and we get an unexpected rm conflict. + xf({ + op1: [ [ 'a', { r: true }, 'x', { p: 0 } ], [ 'b', { d: 0 } ] ], + op2: [ 'a', [ 'x', { p: 0 } ], [ 'y', { d: 0 } ] ], + conflictRight: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', {r:true}] + }, + expectLeft: [ [ 'a', { r: true }, 'y', { p: 0 } ], [ 'b', { d: 0 } ] ], + expectRight: [ 'a', { r: true }, 'y', {r:true}]}); + + xf({ + op1: [ [ 'a', { r: true }, 1, { p: 0 } ], [ 'b', { d: 0 } ] ], + op2: [ 'a', [ 0, { d: 0 } ], [ 1, { p: 0 } ] ], + expectLeft: [ [ 'a', { r: true }, 0, { p: 0 } ], [ 'b', { d: 0 } ] ], + conflictRight: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', {r:true}] + }, + expectRight: [ 'a', { r: true }, 0, {r:true}]}); + + return {expect: [ [ 'a', { r: true }, 0, { p: 0 } ], [ 'b', { d: 0 } ] ]}; + }); + + it('compose copies op2 edit data', () => compose({ + op1: [ 'a', { r: true } ], + op2: [ [ 'x', { p: 0 } ], [ 'y', { d: 0 }, 'b', { es: [] } ] ], + expect: [ + ['a', {r:true}], + ['x', {p:0}], + ['y', {d: 0}, 'b', {es: []}] + ]}) ); + + it('does not conflict when the dest is salvaged', () => xf({ + op1: [ [ 'a', { p: 0 } ], [ 'b', { i: 'hi' } ], [ 'c', { d: 0 } ] ], + op2: [ [ 'a', { p: 0 } ], [ 'b', { d: 0 } ] ], + expectLeft: [['b', {p:0, i:'hi'}], ['c', {d:0}]], + conflictRight: { + type: DROP_COLLISION, + op1: [ 'b', { i: 'hi' } ] + }, + expectRight: null + }) + ); + + it('does not conflict on identical r/i pairs', () => xf({ + op1: [{ i: [], r: true }], + op2: [{ i: [], r: true }], + expect: null + }) + ); + + it('allows embedded edits in identical r/i', () => xf({ + op1: [ { r: true, i: '', es: [] } ], + op2: [ { r: true, i: '' } ], + expect: [{es:[]}]}) ); + + it('does not conflict on identical r/i pairs with identical drops inside', () => xf({ + op1: [ { i: {}, r: true }, 'a', { i: 'a' } ], + op2: [ { i: {}, r: true }, 'a', { i: 'a' } ], + expect: null + }) + ); + + it('generates a DROP_COLLISION on children', () => xf({ + op1: [ { i: {}, r: true }, 'a', { i: 'a' } ], + op2: [ { i: {}, r: true }, 'a', { i: 'b' } ], + conflict: { + type: DROP_COLLISION, + op1: ['a', { i: 'a' } ], + op2: ['a', { i: 'b' } ] + }, + expectLeft: ['a', {r:true, i:'a'}], + expectRight: null + }) + ); + + it('Transforms edit moves into the right dest', () => xf({ + op1: [ 0, { p: 0, d: 0 }, + // These parts are all needed for some reason. + [ 0, { i: 1 } ], + [ 1, { r: true } ], + [ 3, { es: [] } ] + ], + op2: [ 0, [ 0, { d: 0 } ], [ 3, { p: 0 } ] ], + expectLeft: [ 0, {p:0, d:0}, + [0, {i:1}], + [1, {es:[]}], + [2, {r:true}] + ], + expectRight: [0, {p:0, d:0}, + [0, {es:[]}], + [1, {i:1}], + [2, {r:true}] + ]}) ); + + it('adjusts indexes of pick -> drop', () => xf({ + op1: [ 0, { p: 0, d: 0 } ], + op2: [ [ 0, { i: 'yo', p: 0 } ], [ 1, { d: 0 } ] ], + expectLeft: [ [ 0, { d: 0 } ], [ 1, { p: 0 } ] ], + expectRight: null + }) + ); + + it('clears output outDrop when theres no pick', () => xf({ + // Again, not minimized. We return the right data, we were just double- + // descending into outDrop. + op1: [ [ 'the', { d: 0, p: 0 } ], [ 'toves', { r: true } ] ], + op2: [ + [ 'bird', { d: 0 } ], + [ 'slain', { d: 1 } ], + [ 'the', { p: 1 } ], + [ 'toves', { p: 0 } ] + ], + expectLeft: [ + [ 'bird', { r: true } ], + [ 'slain', { p: 0 } ], + [ 'the', { d: 0 } ] + ], + expectRight: [ 'bird', { r: true } ]}) ); + + it('pushes drop indexes by other held items', () => xf({ + op1: [ [ 0, { p: 0 }], + [ 1, + [ 0, { i: 'hi' } ], + [ 1, { d: 0, es: [] } ] ] + ], + op2: [ + [ 0, { p: 1 }, 1, { d: 0 }, 2, { d: 1 } ], + [ 2, { p: 0 } ] + ], + expectLeft: [ 0, 1, + [ 0, { i: 'hi' } ], + [ 1, { d: 0, es: [] } ], + [ 2, { p: 0 } ] + ], + expectRight: [ 0, 1, [ 0, { i: 'hi' } ], [ 3, { es: [] } ] ]}) ); + + it('composes correctly with lots of removes', () => compose({ + op1: [ 3, 1, { r: true } ], + op2: [ + [ 0, { es: [] } ], + [ 1, { r: true, es: [] } ], + [ 2, { r: true } ] + ], + expect: [ + [ 0, { es: [] } ], + [ 1, { es: [], r: true } ], + [ 2, { r: true } ], + [ 3, 1, { r: true } ] + ]}) ); + + it('does not descend twice when p/r on an identical insert', () => xf({ + op1: [ [ 'a', { p: 0, i: '' } ], [ 'b', { d: 0 } ] ], + op2: [ 'a', { r: true, i: '' } ], + expect: null + }) + ); + + it('conflicts underneath a moved / inserted child', () => xf({ + op1: [ [ 'a', { p: 0, i: {} }, 'x', {i:5} ], [ 'b', { d: 0 } ] ], + op2: [ 'a', { r: true, i: {} }, 'x', {i:6} ], + conflict: { + type: DROP_COLLISION, + op1: ['a', 'x', {i:5}], + op2: ['a', 'x', {i:6}] + }, + expectLeft: ['a', 'x', {r:true, i:5}], + expectRight: null + }) + ); + + it('clears drop2 in transform moves', () => xf({ + doc: [{b: {a: 'hi'}}], + op1: [0, {d:0}, + [ 'a', { es: [] } ], + [ 'b', { p: 0 } ] + ], + op2: [ 0, 'b', + [ 'a', { p: 0 } ], + [ 'b', { d: 0 } ] + ], + expect: [0, {d:0}, 'b', { p: 0, es:[] }]}) ); + + it('descends correctly when op2 picks and drops', () => xf({ + op1: [ + [ 'b', { d: 0 }, [ 1, { es: [] } ], [ 2, { i: null } ] ], + [ 'e', { p: 0 } ] + ], + op2: [ { p: 0, d: 0 }, 'e', 1, { p: 1, d: 1 } ], + expectLeft: [ + [ 'b', { d: 0 }, [ 1, { i: null } ], [ 2, { es: [] } ]], + [ 'e', { p: 0 } ] + ], + expectRight: [ + [ 'b', { d: 0 }, [ 1, { es: [] } ], [ 2, { i: null } ] ], + [ 'e', { p: 0 } ] + ]}) ); + + it('composes a pick out of the insert', () => compose({ + op1: [ { i: [ 5, { x: 6 } ] } ], + op2: [ [ 0, { r: true }, 'c', { d: 0 } ], [ 1, 'x', { p: 0 } ] ], + // expect: [{i: [{c: 6}]}] + expect: [ { i: [ {} ] }, 0, 'c', { i: 6 } ]}) ); + + it('is not overeager to remove intermediate literal array items', () => compose({ + op1: [ [ 0, { i: [ 'a', 'b' ] }, 0, { p: 0 } ], [ 1, 0, { d: 0 } ] ], + op2: [ 0, { r: ['a'] }, 1, { r: 'b' } ], + expect: [ 0, 0, { d: 0, p: 0 } ]}) ); + + it('descends down insert indexes correctly', () => compose({ + op1: [ { i: [ {}, 'a' ] }, 1, { i: 'b' } ], + op2: [ [ 1, { r: 'b' } ], [ 2, { r: 'a' } ] ], + expect: [ { i: [ {} ] } ]}) ); + + it('handles composes with ena: 0', () => compose({ + op1: [{i:10}], + op2: [{ena:0}], + expect: [{i:10, ena:0}]}) ); // Also ok: just discarding the ena:0. + + it('handles rm parent with cross move', () => compose({ + op1: [ [ 'a', { p: 0 } ], [ 'b', 1, { d: 0 } ] ], + op2: [ [ 'b', { r: true }, 1, { p: 0 } ], [ 'c', { d: 0 } ]], + expect: [ [ 'a', { p: 0 } ], [ 'b', { r: true } ], [ 'c', { d: 0 } ] ]}) ); + + it('lets you remove children of an op at 2 levels', () => compose({ + op1: [ { i: [ 'a', { x: 'hi' } ] } ], + op2: [ { r: true }, 1, 'x', { r: true } ], + expect: null + }) + ); + + it('discards op1 inserts inside a removed chunk', () => compose({ + op1: [ 'y', [ 1, { i: 'x' } ], [ 2, { i: [ 'a', 'b' ] } ] ], + op2: [ { r: true }, 'y', 2, 0, { r: true } ], + expect: [ { r: true } ]}) ); + + it('handles deeply nested blackhole operations', () => xf({ + op1: [ + [ 'x', { p: 0 } ], + [ 'y', + [ 'a', + [ 'j', { p: 1 } ], + [ 'k', { d: 1 } ] + ], + [ 'b', { d: 0 }] + ] + ], + op2: [ + [ 'x', 'xx', { d: 0 }, 'j', 'jj', { d: 1 } ], + [ 'y', { p: 1 }, 'a', { p: 0 } ] + ], + conflict: { type: BLACKHOLE + }, + expect: ['x', {r:true}, 'xx', {r:true}, 'j', 'jj', {r:true}]}) ); + + it('does not list removed op1 moves in the blackhole info', () => xf({ + op1: [ + [ 'a', [ 'j', { d: 0 } ], [ 'k', { d: 1 } ] ], + [ 'b', { p: 0 }, 'z', 0, { p: 1 } ] + ], + op2: [ + [ 'a', { p: 0 } ], + [ 'b', [ 'y', { d: 0 } ], [ 'z', { r: true } ] ] + ], + conflict: { + type: BLACKHOLE, + op1: [ [ 'a', 'j', { d: 0 } ], [ 'b', { p: 0 } ] ], + op2: [ [ 'a', { p: 0 } ], [ 'b', 'y', { d: 0 } ] ] + }, + expect: ['b', {r:true}, 'y', {r:true}]}) ); + + return it('handles overlapping pick in blackholes', () => xf({ + // This looks complicated, but its really not so bad. Its: + // a->b.0, a.x -> z + // vs + // b -> a.x -> a.y + // + // Its a bit twisty because we're both picking up the same element and + // putting it in different places. This is why we have different left and + // right results. + op1: [ + [ 'a', { p: 1 }, 'x', { p: 0 } ], + [ 'b', 0, { d: 1 } ], + [ 'z', { d: 0 } ] + ], + op2: [ + [ 'a', [ 'x', { d: 0, p: 1 } ], [ 'y', { d: 1 } ] ], + [ 'b', { p: 0 } ] + ], + conflictLeft: { + type: BLACKHOLE, + op1: [['a', {p:0}], ['b', 0, {d:0}]], + op2: [['a', 'x', {d:0}], ['b', {p:0}]] + }, + expectLeft: [ + [ 'a', { r: true }, + [ 'x', { r: true } ], + [ 'y', { p: 0 } ] + ], + [ 'z', { d: 0 } ] + ], + conflictRight: { + type: BLACKHOLE, + op1: [['a', {p:0}], ['b', 0, {d:0}]] + }, + expectRight: [ 'a', { r: true }, + [ 'x', { r: true } ], + [ 'y', { r: true } ] + ]}) ); +}); +}); + \ No newline at end of file From ec49b689e727c1702ac4d81dfd53f1d01c5253d5 Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 07:06:58 +0530 Subject: [PATCH 02/11] Remove require('coffeescript/register') --- test/fuzzer.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/fuzzer.js b/test/fuzzer.js index b705856..9e5f24d 100644 --- a/test/fuzzer.js +++ b/test/fuzzer.js @@ -1,5 +1,3 @@ -require('coffeescript/register') - const assert = require('assert') // const {type} = require('../index') const {type} = require('../lib/json1') From edae23f211c6397ee3967bee346da05e789aa1ab Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 07:09:18 +0530 Subject: [PATCH 03/11] Run Prettier on decaffeinated tests (wow this helps a lot). --- test/cursor.js | 47 +- test/fuzzer.js | 18 +- test/genOp.js | 120 +- test/immutable.js | 38 +- test/test.js | 4526 +++++++++++++++++++++++++-------------------- 5 files changed, 2675 insertions(+), 2074 deletions(-) diff --git a/test/cursor.js b/test/cursor.js index 26764e7..42d2b01 100644 --- a/test/cursor.js +++ b/test/cursor.js @@ -5,10 +5,14 @@ * DS205: Consider reworking code to avoid use of IIFEs * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const {writeCursor, readCursor} = require('../lib/cursor'); +const { writeCursor, readCursor } = require('../lib/cursor'); const assert = require('assert'); -const data = require('fs').readFileSync(__dirname + '/ops.json', 'utf8').split('\n').filter(x => x !== '').map(JSON.parse); +const data = require('fs') + .readFileSync(__dirname + '/ops.json', 'utf8') + .split('\n') + .filter(x => x !== '') + .map(JSON.parse); describe('cursors', function() { describe('writeCursor duplicates', function() { @@ -32,15 +36,18 @@ describe('cursors', function() { } } - return __range__(0, depth, false).map((i) => w.ascend()); + return __range__(0, depth, false).map(i => w.ascend()); }; - if (op !== null) { f(op); } + if (op !== null) { + f(op); + } return assert.deepEqual(op, w.get()); }; - return Array.from(data).map((d) => - (d => it(`${JSON.stringify(d)}`, () => test(d)))(d)); + return Array.from(data).map(d => + (d => it(`${JSON.stringify(d)}`, () => test(d)))(d) + ); }); describe('copy using read cursors', function() { @@ -51,9 +58,12 @@ describe('cursors', function() { const path = []; (f = function() { let component, k; - if (component = r.getComponent()) { + if ((component = r.getComponent())) { // console.log 'component', component - for (k in component) { const v = component[k]; w.write(k, v); } + for (k in component) { + const v = component[k]; + w.write(k, v); + } } assert.deepStrictEqual(r.getPath(), path); @@ -74,33 +84,32 @@ describe('cursors', function() { return assert.deepEqual(op, w.get()); }; - // console.log op - // console.log w.get() - return Array.from(data).map((d) => - (d => it(`${JSON.stringify(d)}`, () => test(d)))(d)); + // console.log op + // console.log w.get() + return Array.from(data).map(d => + (d => it(`${JSON.stringify(d)}`, () => test(d)))(d) + ); }); return describe('fuzzer', () => - it('cleans up position after mergeTree', function() { - const a = [ 1, 'c', { d: 1 } ]; + const a = [1, 'c', { d: 1 }]; const w = writeCursor(a); w.descend(0); - + w.descend('a'); w.write('p', 1); w.ascend(); w.ascend(); w.descend(1); - w.mergeTree([{r:true}]); + w.mergeTree([{ r: true }]); w.descend('b'); w.write('p', 0); // Crash! w.ascend(); return w.ascend(); - }) - ); + })); }); function __range__(left, right, inclusive) { @@ -111,4 +120,4 @@ function __range__(left, right, inclusive) { range.push(i); } return range; -} \ No newline at end of file +} diff --git a/test/fuzzer.js b/test/fuzzer.js index 9e5f24d..5b82ea3 100644 --- a/test/fuzzer.js +++ b/test/fuzzer.js @@ -1,18 +1,18 @@ -const assert = require('assert') +const assert = require('assert'); // const {type} = require('../index') -const {type} = require('../lib/json1') +const { type } = require('../lib/json1'); -const run = module.exports = () => { +const run = (module.exports = () => { // require('./lib/log').quiet = true // type.debug = true - const fuzzer = require('ot-fuzzer') - const genOp = require('./genOp') + const fuzzer = require('ot-fuzzer'); + const genOp = require('./genOp'); - const _t = type.typeAllowingConflictsPred(() => true) + const _t = type.typeAllowingConflictsPred(() => true); // const tracer = require('./tracer')(_t, genOp) //fuzzer(tracer, tracer.genOp, 100000) - fuzzer(_t, genOp, 10000000) -} + fuzzer(_t, genOp, 10000000); +}); -if (require.main === module) run() +if (require.main === module) run(); diff --git a/test/genOp.js b/test/genOp.js index d716901..f240cbf 100644 --- a/test/genOp.js +++ b/test/genOp.js @@ -7,21 +7,27 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let genRandomOp; -const {randomInt, randomReal, randomWord} = require('ot-fuzzer'); +const { randomInt, randomReal, randomWord } = require('ot-fuzzer'); // require 'ot-fuzzer' const assert = require('assert'); -const {writeCursor} = require('../lib/cursor'); +const { writeCursor } = require('../lib/cursor'); const log = require('../lib/log'); -const {type} = require('../lib/json1'); - +const { type } = require('../lib/json1'); // This is an awful function to clone a document snapshot for use by the random // op generator. .. Since we don't want to corrupt the original object with // the changes the op generator will make. -const clone = function(o) { if (typeof o === 'object') { return JSON.parse(JSON.stringify(o)); } else { return o; } }; +const clone = function(o) { + if (typeof o === 'object') { + return JSON.parse(JSON.stringify(o)); + } else { + return o; + } +}; -const randomKey = function(obj) { // this works on arrays too! +const randomKey = function(obj) { + // this works on arrays too! if (Array.isArray(obj)) { if (obj.length === 0) { return undefined; @@ -48,8 +54,11 @@ const randomNewKey = function(obj) { let key; while (true) { // Mostly try just use a small letter - key = randomInt(20) === 0 ? randomWord() : letters[randomInt(letters.length)]; - if (obj[key] === undefined) { break; } + key = + randomInt(20) === 0 ? randomWord() : letters[randomInt(letters.length)]; + if (obj[key] === undefined) { + break; + } } return key; } @@ -58,27 +67,43 @@ const randomNewKey = function(obj) { // Generate a random object var randomThing = function() { switch (randomInt(7)) { - case 0: return null; - case 1: return ''; - case 2: return randomWord(); + case 0: + return null; + case 1: + return ''; + case 2: + return randomWord(); case 3: var obj = {}; - for (let i = 1, end = randomInt(2), asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { obj[randomNewKey(obj)] = randomThing(); } + for ( + let i = 1, end = randomInt(2), asc = 1 <= end; + asc ? i <= end : i >= end; + asc ? i++ : i-- + ) { + obj[randomNewKey(obj)] = randomThing(); + } return obj; - case 4: return (__range__(1, randomInt(2), true).map((j) => randomThing())); - case 5: return 0; // bias toward zeros to find accidental truthy checks - case 6: return randomInt(50); + case 4: + return __range__(1, randomInt(2), true).map(j => randomThing()); + case 5: + return 0; // bias toward zeros to find accidental truthy checks + case 6: + return randomInt(50); } }; // Pick a random path to something in the object. const randomPath = function(data) { - if ((data == null) || (typeof data !== 'object')) { return []; } + if (data == null || typeof data !== 'object') { + return []; + } const path = []; - while ((randomReal() < 0.9) && (data != null) && (typeof data === 'object')) { + while (randomReal() < 0.9 && data != null && typeof data === 'object') { const key = randomKey(data); - if (key == null) { break; } + if (key == null) { + break; + } path.push(key); data = data[key]; @@ -107,12 +132,12 @@ const randomWalkPick = function(w, container) { const randomWalkDrop = function(w, container) { if (container.data === undefined) { return [[], container, 'data']; - } else if ((typeof container.data !== 'object') || (container.data === null)) { + } else if (typeof container.data !== 'object' || container.data === null) { return null; // Can't insert into a document that is a string or number } let [path, parent, key, operand] = Array.from(randomWalkPick(w, container)); - if ((typeof operand === 'object') && (operand !== null)) { + if (typeof operand === 'object' && operand !== null) { parent = operand; } else { w.ascend(); @@ -135,7 +160,7 @@ const set = function(container, key, value) { if (Array.isArray(container)) { return container.splice(key, 0, value); } else { - return container[key] = value; + return (container[key] = value); } } }; @@ -144,7 +169,7 @@ const genRandomOpPart = function(data) { // log 'genRandomOp', data let key1, parent1, path1; - const container = {data}; + const container = { data }; const w = writeCursor(); // Remove something @@ -154,20 +179,23 @@ const genRandomOpPart = function(data) { // 1. move something // 2. insert something // 3. edit a string - const mode = (data === undefined) && (randomReal() < 0.9) ? 2 : randomInt(4); + const mode = data === undefined && randomReal() < 0.9 ? 2 : randomInt(4); //mode = 1 //log 'mode', mode switch (mode) { - case 0: case 1: - var [path, parent, key, operand] = Array.from(randomWalkPick(w, container)); + case 0: + case 1: + var [path, parent, key, operand] = Array.from( + randomWalkPick(w, container) + ); //log 'ppko', path, parent, key, operand - if ((mode === 1) && (typeof operand === 'string')) { + if (mode === 1 && typeof operand === 'string') { // Edit it! const genString = require('ot-text/test/genOp'); const [stringOp, result] = Array.from(genString(operand)); w.write('es', stringOp); parent[key] = result; - } else if ((mode === 1) && (typeof operand === 'number')) { + } else if (mode === 1 && typeof operand === 'number') { const increment = randomInt(10); w.write('ena', increment); parent[key] += increment; @@ -187,7 +215,9 @@ const genRandomOpPart = function(data) { [path, parent, key] = Array.from(walk); //log 'walk', walk const val = randomThing(); - if (parent !== container) { w.descend(key); } + if (parent !== container) { + w.descend(key); + } w.write('i', val); set(parent, key, clone(val)); } @@ -195,19 +225,30 @@ const genRandomOpPart = function(data) { case 3: // Move something. We'll pick up the current operand... - [path1, parent1, key1, operand] = Array.from(randomWalkPick(w, container)); + [path1, parent1, key1, operand] = Array.from( + randomWalkPick(w, container) + ); if (operand !== undefined) { set(parent1, key1, undefined); // remove it from the result... - if (parent1 === container) { // We're removing the whole thing. + if (parent1 === container) { + // We're removing the whole thing. w.write('r', true); } else { w.write('p', 0); // ... and find another place to insert it! - for (let i = 0, end = path1.length, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) { w.ascend(); } + for ( + let i = 0, end = path1.length, asc = 0 <= end; + asc ? i < end : i > end; + asc ? i++ : i-- + ) { + w.ascend(); + } w.reset(); - const [path2, parent2, key2] = Array.from(randomWalkDrop(w, container)); + const [path2, parent2, key2] = Array.from( + randomWalkDrop(w, container) + ); w.descend(key2); set(parent2, key2, operand); w.write('d', 0); @@ -226,7 +267,7 @@ const genRandomOpPart = function(data) { return [op, doc]; }; -module.exports = (genRandomOp = function(doc) { +module.exports = genRandomOp = function(doc) { doc = clone(doc); // 90% chance of adding an op the first time through, then 50% each successive time. @@ -239,7 +280,10 @@ module.exports = (genRandomOp = function(doc) { [opc, doc] = Array.from(genRandomOpPart(doc)); log(opc); // type.setDebug false - op = type.compose(op, opc); + op = type.compose( + op, + opc + ); chance = 0.5; } @@ -247,7 +291,7 @@ module.exports = (genRandomOp = function(doc) { // log.quiet = false log('-> generated op', op, 'doc', doc); return [op, doc]; -}); +}; if (require.main === module) { // log genRandomOp {} @@ -255,10 +299,10 @@ if (require.main === module) { for (let i = 1; i <= 10; i++) { type.debug = true; log.quiet = false; - log(genRandomOp({x:"hi", y:'omg', z:[1,'whoa',3]})); + log(genRandomOp({ x: 'hi', y: 'omg', z: [1, 'whoa', 3] })); } } - // log genRandomOp(undefined) +// log genRandomOp(undefined) function __range__(left, right, inclusive) { let range = []; @@ -268,4 +312,4 @@ function __range__(left, right, inclusive) { range.push(i); } return range; -} \ No newline at end of file +} diff --git a/test/immutable.js b/test/immutable.js index b262d2f..eb07298 100644 --- a/test/immutable.js +++ b/test/immutable.js @@ -7,7 +7,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const assert = require('assert'); -const {type} = require('../lib/json1'); +const { type } = require('../lib/json1'); const log = require('../lib/log'); const genOp = require('./genOp'); const deepClone = require('../lib/deepClone'); @@ -16,15 +16,19 @@ const deepClone = require('../lib/deepClone'); // input describe('immutable guarantees', function() { - const origDoc = {x:"hi", y:'omg', z:[1,'whoa',3]}; + const origDoc = { x: 'hi', y: 'omg', z: [1, 'whoa', 3] }; const expectDoc = deepClone(origDoc); const n = 1000; - this.slow(n*10); + this.slow(n * 10); it('apply does not mutate', () => (() => { const result = []; - for (let i = 1, end = n, asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { + for ( + let i = 1, end = n, asc = 1 <= end; + asc ? i <= end : i >= end; + asc ? i++ : i-- + ) { const [op, doc] = Array.from(genOp(origDoc)); assert.deepStrictEqual(origDoc, expectDoc); @@ -35,32 +39,41 @@ describe('immutable guarantees', function() { result.push(assert.deepStrictEqual(op, expectOp)); } return result; - })() - ); + })()); it('compose does not mutate', () => (() => { const result = []; - for (let i = 1, end = n, asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { + for ( + let i = 1, end = n, asc = 1 <= end; + asc ? i <= end : i >= end; + asc ? i++ : i-- + ) { let op2; let [op1, doc] = Array.from(genOp(origDoc)); [op2, doc] = Array.from(genOp(doc)); const expectOp1 = deepClone(op1); const expectOp2 = deepClone(op2); - type.compose(op1, op2); + type.compose( + op1, + op2 + ); assert.deepStrictEqual(op1, expectOp1); result.push(assert.deepStrictEqual(op2, expectOp2)); } return result; - })() - ); + })()); return it('transform does not mutate', () => (() => { const result = []; - for (let i = 1, end = n, asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { + for ( + let i = 1, end = n, asc = 1 <= end; + asc ? i <= end : i >= end; + asc ? i++ : i-- + ) { const [op1, doc1] = Array.from(genOp(origDoc)); const [op2, doc2] = Array.from(genOp(origDoc)); @@ -73,6 +86,5 @@ describe('immutable guarantees', function() { result.push(assert.deepStrictEqual(op2, expectOp2)); } return result; - })() - ); + })()); }); diff --git a/test/test.js b/test/test.js index 9d76edd..c0403ac 100644 --- a/test/test.js +++ b/test/test.js @@ -14,14 +14,14 @@ const assert = require('assert'); // {type} = require '../index' -const {type} = require('../lib/json1'); +const { type } = require('../lib/json1'); const log = require('../lib/log'); const deepClone = require('../lib/deepClone'); -const {transform} = type; -const {DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE} = type; +const { transform } = type; +const { DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE } = type; -const apply = function({doc:snapshot, op, expect}) { +const apply = function({ doc: snapshot, op, expect }) { type.setDebug(false); const orig = deepClone(snapshot); @@ -30,7 +30,11 @@ const apply = function({doc:snapshot, op, expect}) { assert.deepStrictEqual(snapshot, orig, 'Original snapshot was mutated'); return assert.deepStrictEqual(result, expect); } catch (e) { - console.log(`Apply failed! Repro apply( ${JSON.stringify(snapshot)}, ${JSON.stringify(op)} )`); + console.log( + `Apply failed! Repro apply( ${JSON.stringify(snapshot)}, ${JSON.stringify( + op + )} )` + ); console.log(`expected output: ${JSON.stringify(expect)}`); throw e; } @@ -42,44 +46,68 @@ const d = function(fn) { return type.setDebug(false); }; -const compose = function({op1, op2, expect}) { +const compose = function({ op1, op2, expect }) { try { - const result = type.compose(op1, op2); + const result = type.compose( + op1, + op2 + ); return assert.deepStrictEqual(result, expect); } catch (e) { d(function() { console.error('FAIL! Repro with:'); console.log(`compose( ${JSON.stringify(op1)}, ${JSON.stringify(op2)} )`); console.log(`expected output: ${JSON.stringify(expect)}`); - return type.compose(op1, op2); + return type.compose( + op1, + op2 + ); }); throw e; } }; -const invConflict = ({type, op1, op2}) => ({type, op1:op2, op2:op1}); - +const invConflict = ({ type, op1, op2 }) => ({ type, op1: op2, op2: op1 }); - -const otherSide = function(side) { if (side === 'left') { return 'right'; } else { return 'left'; } }; -const checkConflict = function({op1, op2, side, conflict: expectConflict, expect}) { +const otherSide = function(side) { + if (side === 'left') { + return 'right'; + } else { + return 'left'; + } +}; +const checkConflict = function({ + op1, + op2, + side, + conflict: expectConflict, + expect +}) { // We should get the same conflict with xf(op1, op2, left) and xf(op2, op1, right). if (expectConflict != null) { - if (!expectConflict.op1) { expectConflict.op1 = type.normalize(op1); } - if (!expectConflict.op2) { expectConflict.op2 = type.normalize(op2); } + if (!expectConflict.op1) { + expectConflict.op1 = type.normalize(op1); + } + if (!expectConflict.op2) { + expectConflict.op2 = type.normalize(op2); + } } return (() => { const result = []; for (var [side_, op1_, op2_, ec] of [ - [side, op1, op2, expectConflict], - [otherSide(side), op2, op1, expectConflict ? invConflict(expectConflict) : null] - ]) { + [side, op1, op2, expectConflict], + [ + otherSide(side), + op2, + op1, + expectConflict ? invConflict(expectConflict) : null + ] + ]) { try { - // d -> log('tryTransform', side_, op1_, op2_) - const {ok, conflict} = type.tryTransform(op1_, op2_, side_); - if ((ec == null)) { + const { ok, conflict } = type.tryTransform(op1_, op2_, side_); + if (ec == null) { // We don't care what the result is here; just that it doesn't conflict. result.push(assert(ok)); } else { @@ -92,7 +120,11 @@ const checkConflict = function({op1, op2, side, conflict: expectConflict, expect } catch (e) { d(function() { console.error('FAIL! Repro with:'); - console.log(`tryTransform(${JSON.stringify(op1_)}, ${JSON.stringify(op2_)}, '${side_}')`); + console.log( + `tryTransform(${JSON.stringify(op1_)}, ${JSON.stringify( + op2_ + )}, '${side_}')` + ); return type.tryTransform(op1_, op2_, side_); }); throw e; @@ -102,25 +134,48 @@ const checkConflict = function({op1, op2, side, conflict: expectConflict, expect })(); }; -const xf = function({op1, op2, conflict, conflictLeft, conflictRight, expect, expectLeft, expectRight}) { - if (expect !== undefined) { expectLeft = (expectRight = expect); } - if (conflict !== undefined) { conflictLeft = (conflictRight = conflict); } +const xf = function({ + op1, + op2, + conflict, + conflictLeft, + conflictRight, + expect, + expectLeft, + expectRight +}) { + if (expect !== undefined) { + expectLeft = expectRight = expect; + } + if (conflict !== undefined) { + conflictLeft = conflictRight = conflict; + } return (() => { const result1 = []; - for (var [side, e, c] of [['left', expectLeft, conflictLeft], ['right', expectRight, conflictRight]]) { - checkConflict({op1, op2, side, conflict: c, expect: e}); + for (var [side, e, c] of [ + ['left', expectLeft, conflictLeft], + ['right', expectRight, conflictRight] + ]) { + checkConflict({ op1, op2, side, conflict: c, expect: e }); try { - const result = (c != null) ? type.transformNoConflict(op1, op2, side) : transform(op1, op2, side); + const result = + c != null + ? type.transformNoConflict(op1, op2, side) + : transform(op1, op2, side); result1.push(assert.deepStrictEqual(result, e)); } catch (error) { e = error; d(function() { console.error('FAIL! Repro with:'); - return console.log(`transform(${JSON.stringify(op1)}, ${JSON.stringify(op2)}, '${side}')`); + return console.log( + `transform(${JSON.stringify(op1)}, ${JSON.stringify( + op2 + )}, '${side}')` + ); }); - // if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side + // if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side throw e; } } @@ -128,9 +183,7 @@ const xf = function({op1, op2, conflict, conflictLeft, conflictRight, expect, ex })(); }; - - -const diamond = function({doc, op1, op2}) { +const diamond = function({ doc, op1, op2 }) { let doc1, doc12, doc2, doc21, op1_, op2_; type.setDebug(false); @@ -163,22 +216,22 @@ const diamond = function({doc, op1, op2}) { } }; - -const path = function(path, {op, expect}) { - if (expect === undefined) { expect = path.slice(); } +const path = function(path, { op, expect }) { + if (expect === undefined) { + expect = path.slice(); + } const result = type.transformPosition(path, op); assert.deepStrictEqual(result, expect); // Also check that path+X = expect+X const path2 = path.concat('x'); - const expect2 = (expect != null) ? expect.concat('x') : null; - + const expect2 = expect != null ? expect.concat('x') : null; + const result2 = type.transformPosition(path2, op); return assert.deepStrictEqual(result2, expect2); }; - describe('json1', function() { before(function() { type.registerSubtype(require('ot-simple')); @@ -208,30 +261,35 @@ describe('json1', function() { it('allows some simple valid ops', function() { pass(null); - pass([{i:[1,2,3]}]); - pass([{r:{}}]); - pass([['x',{p:0}], ['y',{d:0}]]); - pass([[0,{p:0}], [10,{d:0}]]); - pass([['a',{p:0}],['b',{d:0}],['x',{p:1}],['y',{d:1}]]); - pass([{e:"hi", et:'simple'}]); - pass([{es:["hi"]}]); - return pass([{ena:5}]); - }); + pass([{ i: [1, 2, 3] }]); + pass([{ r: {} }]); + pass([['x', { p: 0 }], ['y', { d: 0 }]]); + pass([[0, { p: 0 }], [10, { d: 0 }]]); + pass([ + ['a', { p: 0 }], + ['b', { d: 0 }], + ['x', { p: 1 }], + ['y', { d: 1 }] + ]); + pass([{ e: 'hi', et: 'simple' }]); + pass([{ es: ['hi'] }]); + return pass([{ ena: 5 }]); + }); it('disallows invalid syntax', function() { fail(undefined); fail({}); - fail("hi"); + fail('hi'); fail(true); fail(false); fail(0); fail(10); fail([{}]); - fail([{invalid:true}]); + fail([{ invalid: true }]); fail([10, {}]); - fail([10, {invalid:true}]); + fail([10, { invalid: true }]); return fail([10, 'hi']); - }); + }); it('throws if there is any empty leaves', function() { fail([]); @@ -241,155 +299,170 @@ describe('json1', function() { fail([10]); fail([10, {}]); return fail([10, []]); - }); + }); it('ensures path components are non-zero integers or strings', function() { - fail([-1, {r:{}}]); - fail([0.5, {r:{}}]); - fail([true, {r:{}}]); - fail([false, {r:{}}]); - fail([null, {r:{}}]); - return fail([undefined, {r:{}}]); - }); + fail([-1, { r: {} }]); + fail([0.5, { r: {} }]); + fail([true, { r: {} }]); + fail([false, { r: {} }]); + fail([null, { r: {} }]); + return fail([undefined, { r: {} }]); + }); it('does not allow two pickups or two drops in a component', function() { - fail([{p:0, r:{}}]); - fail([{p:1, r:{}}]); - fail(['x', {p:0, r:{}}]); - fail(['x', {p:1, r:{}}]); - - fail([{d:0, i:'hi'}]); - fail([{d:1, i:'hi'}]); - fail([10, {d:0, i:'hi'}]); - return fail([10, {d:1, i:'hi'}]); - }); + fail([{ p: 0, r: {} }]); + fail([{ p: 1, r: {} }]); + fail(['x', { p: 0, r: {} }]); + fail(['x', { p: 1, r: {} }]); + + fail([{ d: 0, i: 'hi' }]); + fail([{ d: 1, i: 'hi' }]); + fail([10, { d: 0, i: 'hi' }]); + return fail([10, { d: 1, i: 'hi' }]); + }); it('throws if there are mismatched pickups / drops', function() { - fail([{p:0}]); - fail([{d:0}]); - fail(['x', {p:0}]); - fail([10, {p:0}]); - fail(['x', {d:0}]); - return fail([10, {d:0}]); - }); + fail([{ p: 0 }]); + fail([{ d: 0 }]); + fail(['x', { p: 0 }]); + fail([10, { p: 0 }]); + fail(['x', { d: 0 }]); + return fail([10, { d: 0 }]); + }); it('throws if pick/drop indexes dont start at 0', function() { - fail([['x', {p:1}], ['y', {d:1}]]); - return fail([[10, {p:1}], [20, {d:1}]]); - }); + fail([['x', { p: 1 }], ['y', { d: 1 }]]); + return fail([[10, { p: 1 }], [20, { d: 1 }]]); + }); - it('throws if a descent starts with an edit', () => fail([10, [{i:"hi"}]])); + it('throws if a descent starts with an edit', () => + fail([10, [{ i: 'hi' }]])); it('throws if descents are out of order', function() { - fail(['x', ['b', {r:{}}], ['a', {r:{}}]]); - fail(['x', [10, {r:{}}], [5, {r:{}}]]); - fail(['x', ['a', {r:{}}], [5, {r:{}}]]); - fail(['x', ['a', {r:{}}], ['a', {r:{}}]]); - return fail(['x', [10, {r:{}}], [10, {r:{}}]]); - }); + fail(['x', ['b', { r: {} }], ['a', { r: {} }]]); + fail(['x', [10, { r: {} }], [5, { r: {} }]]); + fail(['x', ['a', { r: {} }], [5, { r: {} }]]); + fail(['x', ['a', { r: {} }], ['a', { r: {} }]]); + return fail(['x', [10, { r: {} }], [10, { r: {} }]]); + }); - it('throws if descents start with the same scalar', () => fail(['x', ['a', {r:{}}], ['a', {e:{}}]])); + it('throws if descents start with the same scalar', () => + fail(['x', ['a', { r: {} }], ['a', { e: {} }]])); it('throws if descents have two adjacent edits', function() { - fail([{r:{}}, {p:0}]); - fail(['x', {r:{}}, {p:0}]); - return fail(['x', {r:{}}, {p:0}, 'y', {r:{}}]); - }); + fail([{ r: {} }, { p: 0 }]); + fail(['x', { r: {} }, { p: 0 }]); + return fail(['x', { r: {} }, { p: 0 }, 'y', { r: {} }]); + }); it.skip('does not allow ops to overwrite their own inserted data', function() { - fail([{i:{x:5}}, 'x', {i:6}]); - return fail([{i:['hi']}, 0, {i:'omg'}]); - }); + fail([{ i: { x: 5 } }, 'x', { i: 6 }]); + return fail([{ i: ['hi'] }, 0, { i: 'omg' }]); + }); it.skip('does not allow immediate data directly parented in other immediate data', function() { - fail([{i:{}}, 'x', {i:5}]); - fail([{i:{x:5}}, 'x', 'y', {i:6}]); - return fail([{i:[]}, 0, {i:5}]); - }); + fail([{ i: {} }, 'x', { i: 5 }]); + fail([{ i: { x: 5 } }, 'x', 'y', { i: 6 }]); + return fail([{ i: [] }, 0, { i: 5 }]); + }); - it('does not allow the final item to be a single descent', () => fail(['a', ['b', {r:{}}]])); // It should be ['a', 'b', r:{}] + it('does not allow the final item to be a single descent', () => + fail(['a', ['b', { r: {} }]])); // It should be ['a', 'b', r:{}] it('does not allow anything after the descents at the end', function() { - fail([[1, {r:{}}], [2, {r:{}}], 5]); - fail([[1, {r:{}}], [2, {r:{}}], 5, {r:{}}]); - return fail([[1, {r:{}}], [2, {r:{}}], {r:{}}]); - }); + fail([[1, { r: {} }], [2, { r: {} }], 5]); + fail([[1, { r: {} }], [2, { r: {} }], 5, { r: {} }]); + return fail([[1, { r: {} }], [2, { r: {} }], { r: {} }]); + }); it('allows removes inside removes', function() { - pass(['x', {r:true}, 'y', {r:true}]); - pass(['x', {r:{}}, 'y', {r:true}]); - pass([['x', {r:true}, 'y', {p:0}, 'z', {r:true}], ['y', {d:0}]]); - return pass([['x', {r:{}}, 'y', {p:0}, 'z', {r:true}], ['y', {d:0}]]); - }); + pass(['x', { r: true }, 'y', { r: true }]); + pass(['x', { r: {} }, 'y', { r: true }]); + pass([ + ['x', { r: true }, 'y', { p: 0 }, 'z', { r: true }], + ['y', { d: 0 }] + ]); + return pass([ + ['x', { r: {} }, 'y', { p: 0 }, 'z', { r: true }], + ['y', { d: 0 }] + ]); + }); it('allows inserts inside inserts', function() { - pass([1, {i:{}}, 'x', {i:10}]); - return pass([[0, 'x', {p:0}], [1, {i:{}}, 'x', {d:0}, 'y', {i:10}]]); - }); + pass([1, { i: {} }, 'x', { i: 10 }]); + return pass([ + [0, 'x', { p: 0 }], + [1, { i: {} }, 'x', { d: 0 }, 'y', { i: 10 }] + ]); + }); it.skip('fails if the operation drops items inside something it picked up', function() { - fail(['x', {r:true}, 1, {i:'hi'}]); - fail(['x', {d:0}, 1, {p:0}]); - return fail([{r:true}, 1, {p:0, d:0}]); - }); + fail(['x', { r: true }, 1, { i: 'hi' }]); + fail(['x', { d: 0 }, 1, { p: 0 }]); + return fail([{ r: true }, 1, { p: 0, d: 0 }]); + }); return describe('edit', function() { it('requires all edits to specify their type', function() { - fail([{e:{}}]); - fail([5, {e:{}}]); - return pass([{e:{}, et:'simple'}]); - }); + fail([{ e: {} }]); + fail([5, { e: {} }]); + return pass([{ e: {}, et: 'simple' }]); + }); it('allows edits to have null or false for the operation', function() { // These aren't valid operations according to the simple type, but the // type doesn't define a checkValidOp so we wouldn't be able to tell // anyway. - pass([{e:null, et:'simple'}]); - pass([5, {e:null, et:'simple'}]); - pass([{e:false, et:'simple'}]); - return pass([5, {e:false, et:'simple'}]); - }); + pass([{ e: null, et: 'simple' }]); + pass([5, { e: null, et: 'simple' }]); + pass([{ e: false, et: 'simple' }]); + return pass([5, { e: false, et: 'simple' }]); + }); it('does not allow an edit to use an unregistered type', function() { - fail([{e:{}, et:'an undefined type'}]); - return fail([{e:null, et:'an undefined type'}]); - }); + fail([{ e: {}, et: 'an undefined type' }]); + return fail([{ e: null, et: 'an undefined type' }]); + }); it('does not allow two edits in the same operation', function() { - fail([{e:{}, et:'simple', es:[1,2,3]}]); - fail([{es:[], ena:5}]); - return fail([{e:{}, et:'simple', ena:5}]); - }); + fail([{ e: {}, et: 'simple', es: [1, 2, 3] }]); + fail([{ es: [], ena: 5 }]); + return fail([{ e: {}, et: 'simple', ena: 5 }]); + }); - it('fails if the type is missing', () => fail([{et:'missing', e:{}}])); + it('fails if the type is missing', () => + fail([{ et: 'missing', e: {} }])); it('does not allow anything inside an edited subtree'); it.skip('does not allow an edit inside removed or picked up content', function() { - fail([{r:true}, 1, {es:['hi']}]); - pass([1, {r:true}, 1, {es:['hi']}]); - fail(['x', {r:true}, 1, {es:['hi']}]); - pass([[1, {p:0}, 1, {es:['hi']}], [2, {d:0}]]); - fail([['x', {p:0}, 1, {es:['hi']}], ['y', {d:0}]]); + fail([{ r: true }, 1, { es: ['hi'] }]); + pass([1, { r: true }, 1, { es: ['hi'] }]); + fail(['x', { r: true }, 1, { es: ['hi'] }]); + pass([[1, { p: 0 }, 1, { es: ['hi'] }], [2, { d: 0 }]]); + fail([['x', { p: 0 }, 1, { es: ['hi'] }], ['y', { d: 0 }]]); // This is actually ok. - return pass([ 0, { p: 0 }, [ 'a', { es: [], r: true } ], [ 'x', { d: 0 } ] ]); - }); + return pass([0, { p: 0 }, ['a', { es: [], r: true }], ['x', { d: 0 }]]); + }); return it.skip('does not allow you to drop inside something that was removed', function() { // These insert into the next list item - pass([[1, {r:true}, 1, {d:0}], [2, {p:0}]]); - pass([1, {p: 0}, 'x', {d: 0}]); + pass([[1, { r: true }, 1, { d: 0 }], [2, { p: 0 }]]); + pass([1, { p: 0 }, 'x', { d: 0 }]); // But this is not ok. - return fail(['x', {p:0}, 'a', {d:0}]); + return fail(['x', { p: 0 }, 'a', { d: 0 }]); + }); }); }); -}); describe('normalize', function() { const n = function(opIn, expect) { - if (expect === undefined) { expect = opIn; } + if (expect === undefined) { + expect = opIn; + } const op = type.normalize(opIn); return assert.deepStrictEqual(op, expect); }; @@ -400,974 +473,1230 @@ describe('json1', function() { }); it('normalizes some regular ops', function() { - n([{i:'hi'}]); - n([{i:'hi'}, 1,2,3], [{i:'hi'}]); - n([[1,2,3, {p:0}], [1,2,3, {d:0}]], [1,2,3, {p:0, d:0}]); - n([[1,2,3, {p:0}], [1,2,30, {d:0}]], [1,2, [3, {p:0}], [30, {d:0}]]); - return n([[1,2,30, {p:0}], [1,2,3, {d:0}]], [1,2, [3, {d:0}], [30, {p:0}]]); - }); + n([{ i: 'hi' }]); + n([{ i: 'hi' }, 1, 2, 3], [{ i: 'hi' }]); + n([[1, 2, 3, { p: 0 }], [1, 2, 3, { d: 0 }]], [1, 2, 3, { p: 0, d: 0 }]); + n( + [[1, 2, 3, { p: 0 }], [1, 2, 30, { d: 0 }]], + [1, 2, [3, { p: 0 }], [30, { d: 0 }]] + ); + return n( + [[1, 2, 30, { p: 0 }], [1, 2, 3, { d: 0 }]], + [1, 2, [3, { d: 0 }], [30, { p: 0 }]] + ); + }); - it('will let you insert null', () => n([{i:null}])); + it('will let you insert null', () => n([{ i: null }])); it('normalizes embedded ops when available', function() { - n([{es:[0, 'hi']}], [{es:['hi']}]); - n([{et:'text-unicode', e:['hi']}], [{es:['hi']}]); - n([{et:'text-unicode', e:[0, 'hi']}], [{es:['hi']}]); - n([{et:'simple', e:{}}]); - n([{et:'number', e:5}], [{ena:5}]); - return n([{ena:5}]); - }); + n([{ es: [0, 'hi'] }], [{ es: ['hi'] }]); + n([{ et: 'text-unicode', e: ['hi'] }], [{ es: ['hi'] }]); + n([{ et: 'text-unicode', e: [0, 'hi'] }], [{ es: ['hi'] }]); + n([{ et: 'simple', e: {} }]); + n([{ et: 'number', e: 5 }], [{ ena: 5 }]); + return n([{ ena: 5 }]); + }); it.skip('normalizes embedded removes', function() { - n([1, {r:true}, 2, {r:true}], [1, {r:true}]); - return n([{r:true}, 2, {r:true}], [{r:true}]); - }); + n([1, { r: true }, 2, { r: true }], [1, { r: true }]); + return n([{ r: true }, 2, { r: true }], [{ r: true }]); + }); it('throws if the type is missing', () => // Not sure if this is the best behaviour but ... eh. - assert.throws(() => n([{et:'missing', e:{}}])) - ); + assert.throws(() => n([{ et: 'missing', e: {} }]))); - return it('corrects weird pick and drop ids', () => n([['x', {p:1}], ['y', {d:1}]], [['x', {p:0}], ['y', {d:0}]])); -}); + return it('corrects weird pick and drop ids', () => + n( + [['x', { p: 1 }], ['y', { d: 1 }]], + [['x', { p: 0 }], ['y', { d: 0 }]] + )); + }); -// ****** Apply ****** + // ****** Apply ****** describe('apply', function() { it('Can set properties', function() { apply({ doc: [], - op: [0, {i:17}], - expect: [17]}); + op: [0, { i: 17 }], + expect: [17] + }); return apply({ doc: {}, - op: ['x', {i:5}], - expect: {x:5}}); - }); + op: ['x', { i: 5 }], + expect: { x: 5 } + }); + }); it('can edit the root', function() { apply({ - doc: {x:5}, - op: [{r:true}], + doc: { x: 5 }, + op: [{ r: true }], expect: undefined }); apply({ doc: '', - op: [{r:true}], + op: [{ r: true }], expect: undefined }); apply({ doc: 'hi', - op: [{r:true, i:null}], + op: [{ r: true, i: null }], expect: null }); apply({ doc: 'hi', - op: [{es:[2, ' there']}], + op: [{ es: [2, ' there'] }], expect: 'hi there' }); - assert.throws(() => type.apply(null, [{i:5}])); + assert.throws(() => type.apply(null, [{ i: 5 }])); apply({ doc: undefined, - op: [{i:5}], + op: [{ i: 5 }], expect: 5 }); return apply({ - doc: {x:5}, - op: [{r:{}, i:[1,2,3]}], - expect: [1,2,3]}); - }); + doc: { x: 5 }, + op: [{ r: {}, i: [1, 2, 3] }], + expect: [1, 2, 3] + }); + }); - // TODO: And an edit of the root. - - it('can move 1', () => apply({ - doc: {x:5}, - op: [['x', {p:0}], ['y', {d:0}]], - expect: {y:5}}) ); - - it('can move 2', () => apply({ - doc: [0,1,2], - op: [[1, {p:0}], [2, {d:0}]], - expect: [0,2,1]}) ); - - it('can handle complex list index stuff', () => apply({ - doc: [0,1,2,3,4,5], - op: [[1, {r:{}, i:11}], [2, {r:{}, i:12}]], - expect: [0,11,12,3,4,5]}) ); - - it('correctly handles interspersed descent and edits', () => apply({ - doc: {x: {y: {was:'y'}, was:'x'}}, - op: [['X',{d:0},'Y',{d:1}], ['x',{p:0},'y',{p:1}]], - expect: {X: {Y: {was:'y'}, was:'x'}}}) ); - - it('can edit strings', () => apply({ - doc: "errd", - op: [{es:[2,"maghe"]}], - expect: "ermagherd" - }) - ); - - it('can edit numbers', () => apply({ - doc: 5, - op: [{ena:10}], - expect: 15 - }) - ); - - it('can edit child numbers', () => apply({ - doc: [20], - op: [0, {ena:-100}], - expect: [-80]}) ); - - it('can edit subdocuments using an embedded type', () => apply({ - doc: {str:'hai'}, - op: [{e:{position:2, text:'wai'}, et:'simple'}], - expect: {str:'hawaii'}}) ); - - it('applies edits after drops', () => apply({ - doc: {x: "yooo"}, - op: [['x', {p:0}], ['y', {d:0, es:['sup']}]], - expect: {y: "supyooo"}}) ); + // TODO: And an edit of the root. - it('throws when the op traverses missing items', function() { - assert.throws(() => type.apply([0, 'hi'], [1, {p:0}, 'x', {d:0}])); - return assert.throws(() => type.apply({}, [{p:0}, 'a', {d:0}])); - }); + it('can move 1', () => + apply({ + doc: { x: 5 }, + op: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: { y: 5 } + })); - return it('throws if the type is missing', () => assert.throws(() => type.apply({}, [{et:'missing', e:{}}]))); -}); + it('can move 2', () => + apply({ + doc: [0, 1, 2], + op: [[1, { p: 0 }], [2, { d: 0 }]], + expect: [0, 2, 1] + })); + + it('can handle complex list index stuff', () => + apply({ + doc: [0, 1, 2, 3, 4, 5], + op: [[1, { r: {}, i: 11 }], [2, { r: {}, i: 12 }]], + expect: [0, 11, 12, 3, 4, 5] + })); + + it('correctly handles interspersed descent and edits', () => + apply({ + doc: { x: { y: { was: 'y' }, was: 'x' } }, + op: [['X', { d: 0 }, 'Y', { d: 1 }], ['x', { p: 0 }, 'y', { p: 1 }]], + expect: { X: { Y: { was: 'y' }, was: 'x' } } + })); + + it('can edit strings', () => + apply({ + doc: 'errd', + op: [{ es: [2, 'maghe'] }], + expect: 'ermagherd' + })); + + it('can edit numbers', () => + apply({ + doc: 5, + op: [{ ena: 10 }], + expect: 15 + })); + + it('can edit child numbers', () => + apply({ + doc: [20], + op: [0, { ena: -100 }], + expect: [-80] + })); + + it('can edit subdocuments using an embedded type', () => + apply({ + doc: { str: 'hai' }, + op: [{ e: { position: 2, text: 'wai' }, et: 'simple' }], + expect: { str: 'hawaii' } + })); + + it('applies edits after drops', () => + apply({ + doc: { x: 'yooo' }, + op: [['x', { p: 0 }], ['y', { d: 0, es: ['sup'] }]], + expect: { y: 'supyooo' } + })); + + it('throws when the op traverses missing items', function() { + assert.throws(() => type.apply([0, 'hi'], [1, { p: 0 }, 'x', { d: 0 }])); + return assert.throws(() => type.apply({}, [{ p: 0 }, 'a', { d: 0 }])); + }); + return it('throws if the type is missing', () => + assert.throws(() => type.apply({}, [{ et: 'missing', e: {} }]))); + }); describe('apply path', function() { it('does not modify path when op is unrelated', function() { - path(['a', 'b', 'c'], {op: null}); - path(['a', 'b', 'c'], {op: ['x', {i:5}]}); - path(['a', 'b', 'c'], {op: ['x', {r:true}]}); - path(['a', 'b', 'c'], {op: [['x', {p:0}], ['y', {d:0}]]}); - path([1,2,3], {op: [2, {i:5}]}); - path([1,2,3], {op: [1, 2, 4, {i:5}]}); - path([1], {op: [1, 2, {r:true}]}); - return path(['x'], {op: ['x', 'y', {r:true}]}); - }); + path(['a', 'b', 'c'], { op: null }); + path(['a', 'b', 'c'], { op: ['x', { i: 5 }] }); + path(['a', 'b', 'c'], { op: ['x', { r: true }] }); + path(['a', 'b', 'c'], { op: [['x', { p: 0 }], ['y', { d: 0 }]] }); + path([1, 2, 3], { op: [2, { i: 5 }] }); + path([1, 2, 3], { op: [1, 2, 4, { i: 5 }] }); + path([1], { op: [1, 2, { r: true }] }); + return path(['x'], { op: ['x', 'y', { r: true }] }); + }); it('adjusts list indicies', function() { - path([2], {op: [1, {i:5}], expect: [3]}); - path([2], {op: [2, {i:5}], expect: [3]}); - path([2], {op: [1, {r:true}], expect: [1]}); - path([2], {op: [[1, {p:0}], [3, {d:0}]], expect: [1]}); - path([2], {op: [[1, {d:0}], [3, {p:0}]], expect: [3]}); - return path([2], {op: [[2, {d:0}], [3, {p:0}]], expect: [3]}); - }); + path([2], { op: [1, { i: 5 }], expect: [3] }); + path([2], { op: [2, { i: 5 }], expect: [3] }); + path([2], { op: [1, { r: true }], expect: [1] }); + path([2], { op: [[1, { p: 0 }], [3, { d: 0 }]], expect: [1] }); + path([2], { op: [[1, { d: 0 }], [3, { p: 0 }]], expect: [3] }); + return path([2], { op: [[2, { d: 0 }], [3, { p: 0 }]], expect: [3] }); + }); it('returns null when the object at the path was removed', function() { - path(['x'], {op: [{r:true}], expect: null}); - path(['x'], {op: ['x', {r:true}], expect: null}); - path([1], {op: [{r:true}], expect: null}); - return path([1], {op: [1, {r:true}], expect: null}); + path(['x'], { op: [{ r: true }], expect: null }); + path(['x'], { op: ['x', { r: true }], expect: null }); + path([1], { op: [{ r: true }], expect: null }); + return path([1], { op: [1, { r: true }], expect: null }); }); it('moves the path', function() { - path(['a', 'z'], {op: [['a', {p:0}], ['y', {d:0}]], expect: ['y', 'z']}); - path(['a', 'b'], {op: [['a', 'b', {p:0}], ['z', {d:0}]], expect: ['z']}); - path(['a', 'b'], {op: [['a', 'b', 'c', {p:0}], ['z', {d:0}]]}); - path([1,2], {op: [[1, {p:0}], [10, {d:0}]], expect: [10, 2]}); - path([1,2], {op: [[1, 2, {p:0}], [10, {d:0}]], expect: [10]}); - path([1,2], {op: [1, [1, {d:0}], [2, {p:0}]], expect: [1, 1]}); - return path([1,2], {op: [[1, 2, 3, {p:0}], [10, {d:0}]]}); - }); + path(['a', 'z'], { + op: [['a', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', 'z'] + }); + path(['a', 'b'], { + op: [['a', 'b', { p: 0 }], ['z', { d: 0 }]], + expect: ['z'] + }); + path(['a', 'b'], { op: [['a', 'b', 'c', { p: 0 }], ['z', { d: 0 }]] }); + path([1, 2], { op: [[1, { p: 0 }], [10, { d: 0 }]], expect: [10, 2] }); + path([1, 2], { op: [[1, 2, { p: 0 }], [10, { d: 0 }]], expect: [10] }); + path([1, 2], { op: [1, [1, { d: 0 }], [2, { p: 0 }]], expect: [1, 1] }); + return path([1, 2], { op: [[1, 2, 3, { p: 0 }], [10, { d: 0 }]] }); + }); - it('handles pick parent and move', () => path(['a', 'b', 'c'], {op: [['a', {r:true}, 'b', {p:0}], ['x', {d:0}]], expect: ['x', 'c']})); + it('handles pick parent and move', () => + path(['a', 'b', 'c'], { + op: [['a', { r: true }, 'b', { p: 0 }], ['x', { d: 0 }]], + expect: ['x', 'c'] + })); - it('adjusts indicies under a pick', () => path(['a', 'b', 10], {op: [['a', {p:0}, 'b', 1, {r:true}], ['x', {d:0}]], expect: ['x', 'b', 9]})); + it('adjusts indicies under a pick', () => + path(['a', 'b', 10], { + op: [['a', { p: 0 }, 'b', 1, { r: true }], ['x', { d: 0 }]], + expect: ['x', 'b', 9] + })); it.skip('gen ops', function() {}); - // This should do something like: - // - Generate a document - // - Generate op, a random operation - // - Generate a path to somewhere in the document and an edit we can do there -> op2 - // - Check that transform(op2, op) == op2 at transformPosition(path) or something like that. + // This should do something like: + // - Generate a document + // - Generate op, a random operation + // - Generate a path to somewhere in the document and an edit we can do there -> op2 + // - Check that transform(op2, op) == op2 at transformPosition(path) or something like that. return it('calls transformPosition with embedded string edits if available', function() { // For embedded string operations (and other things that have // transformPosition or transformPosition or whatever) we should call that. - path(['x','y','z', 1], {op: ['x','y','z', {es:['abc']}], expect: ['x','y','z', 4]}); - path(['x','y','z', 1], {op: ['x','y','z', {es:['💃']}], expect: ['x','y','z', 2]}); - return path(['x','y','z'], {op: ['x','y','z', {es:['💃']}], expect: ['x','y','z']}); + path(['x', 'y', 'z', 1], { + op: ['x', 'y', 'z', { es: ['abc'] }], + expect: ['x', 'y', 'z', 4] + }); + path(['x', 'y', 'z', 1], { + op: ['x', 'y', 'z', { es: ['💃'] }], + expect: ['x', 'y', 'z', 2] + }); + return path(['x', 'y', 'z'], { + op: ['x', 'y', 'z', { es: ['💃'] }], + expect: ['x', 'y', 'z'] + }); + }); }); -}); - -// ******* Compose ******* + // ******* Compose ******* describe('compose', function() { - it('composes empty ops to nothing', () => compose({ - op1: null, - op2: null, - expect: null - }) - ); + it('composes empty ops to nothing', () => + compose({ + op1: null, + op2: null, + expect: null + })); describe('op1 drop', function() { - it('vs remove', () => compose({ - op1: [['x', {p:0}], ['y', {d:0}]], - op2: ['y', {r:true}], - expect: ['x', {r:true}]}) ); - - it('vs remove parent', () => compose({ - op1: [['x', {p:0}], ['y', 0, {d:0}]], - op2: ['y', {r:true}], - expect: [['x', {r:true}], ['y', {r:true}]]}) ); - - it('vs remove child', () => compose({ - op1: [['x', {p:0}], ['y', {d:0}]], - op2: ['y', 'a', {r:true}], - expect: [['x', {p:0}, 'a', {r:true}], ['y', {d:0}]]}) ); - - it('vs remove and pick child', () => compose({ - op1: [['x', {p:0}], ['y', {d:0}]], - op2: [['y', {r:true}, 'a', {p:0}], ['z', {d:0}]], - expect: [['x', {r:true}, 'a', {p:0}], ['z', {d:0}]]}) ); - - it('vs pick', () => compose({ - op1: [['x', {p:0}], ['y', {d:0}]], - op2: [['y', {p:0}], ['z', {d:0}]], - expect: [['x', {p:0}], ['z', {d:0}]]}) ); - - return it('is transformed by op2 picks', () => compose({ - op1: [['x', {p:0}], ['y', 10, {d:0}]], - op2: ['y', 0, {r:true}], - expect: [['x', {p:0}], ['y', [0, {r:true}], [9, {d:0}]]]}) ); - }); + it('vs remove', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: ['y', { r: true }], + expect: ['x', { r: true }] + })); + + it('vs remove parent', () => + compose({ + op1: [['x', { p: 0 }], ['y', 0, { d: 0 }]], + op2: ['y', { r: true }], + expect: [['x', { r: true }], ['y', { r: true }]] + })); + + it('vs remove child', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: ['y', 'a', { r: true }], + expect: [['x', { p: 0 }, 'a', { r: true }], ['y', { d: 0 }]] + })); + + it('vs remove and pick child', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [['y', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]], + expect: [['x', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]] + })); + + it('vs pick', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + expect: [['x', { p: 0 }], ['z', { d: 0 }]] + })); + + return it('is transformed by op2 picks', () => + compose({ + op1: [['x', { p: 0 }], ['y', 10, { d: 0 }]], + op2: ['y', 0, { r: true }], + expect: [['x', { p: 0 }], ['y', [0, { r: true }], [9, { d: 0 }]]] + })); + }); describe('op1 insert', function() { - it('vs remove', () => compose({ - op1: ['x', {i:{a:'hi'}}], - op2: ['x', {r:true}], - expect: null - }) - ); - - it('vs remove parent', () => compose({ - op1: ['x', 0, {i:{a:'hi'}}], - op2: ['x', {r:true}], - expect: ['x', {r:true}]}) ); - - it('vs remove child', () => compose({ - op1: ['x', {i:{a:'hi', b:'woo'}}], - op2: ['x', 'a', {r:true}], - expect: ['x', {i:{b:'woo'}}]}) ); - - it('vs remove and pick child', () => compose({ - op1: ['x', {i:{a:'hi', b:'woo'}}], - op2: [['x', {r:true}, 'a', {p:0}], ['y', {d:0}]], - expect: ['y', {i:'hi'}]}) ); - - it('vs remove an embedded insert', () => compose({ - op1: ['x', {i:{}}, 'y', {i:'hi'}], - op2: ['x', 'y', {r:true}], - expect: ['x', {i:{}}]}) ); - - it('vs remove from an embedded insert', () => compose({ - op1: ['x', {i:{}}, 'y', {i:[1,2,3]}], - op2: ['x', 'y', 1, {r:true}], - expect: ['x', {i:{}}, 'y', {i:[1, 3]}]}) ); - - it('picks the correct element of an embedded insert', () => compose({ - op1: ['x', {i:['a', 'b', 'c']}, 1, {i:'XX'}], - op2: [['x', 1, {p:0}], ['y', {d:0}]], - expect: [['x', {i:['a', 'b', 'c']}], ['y', {i:'XX'}]]}) ); - - it('picks the correct element of an embedded insert 2', () => compose({ - op1: ['x', {i:['a', 'b', 'c']}, 1, {i:'XX'}], - op2: [['x', 3, {p:0}], ['y', {d:0}]], // should grab 'c'. - expect: [['x', {i:['a', 'b']}, 1, {i:'XX'}], ['y', {i:'c'}]]}) ); - - - it('moves all children', () => compose({ - op1: ['x', {i:{}}, 'y', {i:[1,2,3]}], - op2: [['x', {p:0}], ['z', {d:0}]], - expect: ['z', {i:{}}, 'y', {i:[1,2,3]}]}) ); - - it('removes all children', () => compose({ - op1: ['x', {i:{}}, 'y', {i:[1,2,3]}], - op2: ['x', {r:true}], - expect: null - }) - ); - - it('removes all children when removed at the destination', () => compose({ - op1: [['x', {p:0}], ['y', {d:0}, 0, {i:'hi'}]], - op2: ['y', {r:true}], - expect: ['x', {r:true}]}) ); - - it('vs op2 insert', () => compose({ // Inserts aren't folded together. - op1: [{i:{}}], - op2: ['x', {i:'hi'}], - expect: [{i:{}}, 'x', {i:'hi'}]}) ); - - it('vs op2 string edit', () => compose({ - op1: [{i:'hi'}], - op2: [{es:[2, ' there']}], - expect: [{i:'hi', es:[2, ' there']}]}) ); - - return it('vs op2 number edit', () => compose({ - op1: [{i:10}], - op2: [{ena:20}], - expect: [{i:10, ena:20}]}) ); - }); + it('vs remove', () => + compose({ + op1: ['x', { i: { a: 'hi' } }], + op2: ['x', { r: true }], + expect: null + })); + + it('vs remove parent', () => + compose({ + op1: ['x', 0, { i: { a: 'hi' } }], + op2: ['x', { r: true }], + expect: ['x', { r: true }] + })); + + it('vs remove child', () => + compose({ + op1: ['x', { i: { a: 'hi', b: 'woo' } }], + op2: ['x', 'a', { r: true }], + expect: ['x', { i: { b: 'woo' } }] + })); + + it('vs remove and pick child', () => + compose({ + op1: ['x', { i: { a: 'hi', b: 'woo' } }], + op2: [['x', { r: true }, 'a', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', { i: 'hi' }] + })); + + it('vs remove an embedded insert', () => + compose({ + op1: ['x', { i: {} }, 'y', { i: 'hi' }], + op2: ['x', 'y', { r: true }], + expect: ['x', { i: {} }] + })); + + it('vs remove from an embedded insert', () => + compose({ + op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }], + op2: ['x', 'y', 1, { r: true }], + expect: ['x', { i: {} }, 'y', { i: [1, 3] }] + })); + + it('picks the correct element of an embedded insert', () => + compose({ + op1: ['x', { i: ['a', 'b', 'c'] }, 1, { i: 'XX' }], + op2: [['x', 1, { p: 0 }], ['y', { d: 0 }]], + expect: [['x', { i: ['a', 'b', 'c'] }], ['y', { i: 'XX' }]] + })); + + it('picks the correct element of an embedded insert 2', () => + compose({ + op1: ['x', { i: ['a', 'b', 'c'] }, 1, { i: 'XX' }], + op2: [['x', 3, { p: 0 }], ['y', { d: 0 }]], // should grab 'c'. + expect: [['x', { i: ['a', 'b'] }, 1, { i: 'XX' }], ['y', { i: 'c' }]] + })); + + it('moves all children', () => + compose({ + op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }], + op2: [['x', { p: 0 }], ['z', { d: 0 }]], + expect: ['z', { i: {} }, 'y', { i: [1, 2, 3] }] + })); + + it('removes all children', () => + compose({ + op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }], + op2: ['x', { r: true }], + expect: null + })); + + it('removes all children when removed at the destination', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }, 0, { i: 'hi' }]], + op2: ['y', { r: true }], + expect: ['x', { r: true }] + })); + + it('vs op2 insert', () => + compose({ + // Inserts aren't folded together. + op1: [{ i: {} }], + op2: ['x', { i: 'hi' }], + expect: [{ i: {} }, 'x', { i: 'hi' }] + })); + + it('vs op2 string edit', () => + compose({ + op1: [{ i: 'hi' }], + op2: [{ es: [2, ' there'] }], + expect: [{ i: 'hi', es: [2, ' there'] }] + })); + + return it('vs op2 number edit', () => + compose({ + op1: [{ i: 10 }], + op2: [{ ena: 20 }], + expect: [{ i: 10, ena: 20 }] + })); + }); describe('op1 edit', function() { - it('removes the edit if the edited object is deleted', () => compose({ - op1: ['x', {es:['hi']}], - op2: ['x', {r:true}], - expect: ['x', {r:true}]}) ); - - it('removes the edit in an embedded insert 1', () => compose({ - op1: ['x', {i:'', es:['hi']}], - op2: ['x', {r:true}], - expect: null - }) - ); - - it('removes the edit in an embedded insert 2', () => compose({ - op1: ['x', {i:['']}, 0, {es:['hi']}], - op2: ['x', 0, {r:true}], - expect: ['x', {i:[]}]}) ); - - it('composes string edits', () => compose({ - op1: [{es:['hi']}], - op2: [{es:[2, ' there']}], - expect: [{es:['hi there']}]}) ); - - it('composes number edits', () => compose({ - op1: [{ena:10}], - op2: [{ena:-8}], - expect: [{ena:2}]}) ); - - it('transforms and composes edits', () => compose({ - op1: ['x', {es:['hi']}], - op2: [['x', {p:0}], ['y', {d:0, es:[2, ' there']}]], - expect: [['x', {p:0}], ['y', {d:0, es:['hi there']}]]}) ); - - it('preserves inserts with edits', () => compose({ - op1: ['x', {i:'hi'}], - op2: [['x', {p:0}], ['y', {d:0, es:[' there']}]], - expect: ['y', {i:'hi', es:[' there']}]}) ); - - it('allows a different edit in the same location', () => compose({ - op1: ['x', {es:['hi']}], - op2: ['x', {r:true, i:'yo', es:[2, ' there']}], - expect: ['x', {r:true, i:'yo', es:[2, ' there']}]}) ); - - return it('throws if the type is missing', () => assert.throws(() => type.compose([{et:'missing', e:{}}], [{et:'missing', e:{}}]))); - }); + it('removes the edit if the edited object is deleted', () => + compose({ + op1: ['x', { es: ['hi'] }], + op2: ['x', { r: true }], + expect: ['x', { r: true }] + })); + + it('removes the edit in an embedded insert 1', () => + compose({ + op1: ['x', { i: '', es: ['hi'] }], + op2: ['x', { r: true }], + expect: null + })); + + it('removes the edit in an embedded insert 2', () => + compose({ + op1: ['x', { i: [''] }, 0, { es: ['hi'] }], + op2: ['x', 0, { r: true }], + expect: ['x', { i: [] }] + })); + + it('composes string edits', () => + compose({ + op1: [{ es: ['hi'] }], + op2: [{ es: [2, ' there'] }], + expect: [{ es: ['hi there'] }] + })); + + it('composes number edits', () => + compose({ + op1: [{ ena: 10 }], + op2: [{ ena: -8 }], + expect: [{ ena: 2 }] + })); + + it('transforms and composes edits', () => + compose({ + op1: ['x', { es: ['hi'] }], + op2: [['x', { p: 0 }], ['y', { d: 0, es: [2, ' there'] }]], + expect: [['x', { p: 0 }], ['y', { d: 0, es: ['hi there'] }]] + })); + + it('preserves inserts with edits', () => + compose({ + op1: ['x', { i: 'hi' }], + op2: [['x', { p: 0 }], ['y', { d: 0, es: [' there'] }]], + expect: ['y', { i: 'hi', es: [' there'] }] + })); + + it('allows a different edit in the same location', () => + compose({ + op1: ['x', { es: ['hi'] }], + op2: ['x', { r: true, i: 'yo', es: [2, ' there'] }], + expect: ['x', { r: true, i: 'yo', es: [2, ' there'] }] + })); + + return it('throws if the type is missing', () => + assert.throws(() => + type.compose( + [{ et: 'missing', e: {} }], + [{ et: 'missing', e: {} }] + ) + )); + }); describe('op2 pick', () => - it('gets untransformed by op1 drops', () => - ({ - op1: [5, {i:'hi'}], - op2: [6, {r:true}], - expect: [5, {r:true, i:'hi'}] - }) - ) - ); + it('gets untransformed by op1 drops', () => ({ + op1: [5, { i: 'hi' }], + op2: [6, { r: true }], + expect: [5, { r: true, i: 'hi' }] + }))); describe('op1 insert containing a drop', () => - it('vs pick at insert', () => compose({ - op1: [['x', {p:0}], ['y', {i:{}}, 'x', {d:0}]], - op2: [['y', {p:0}], ['z', {d:0}]], - expect: [['x', {p:0}], ['z', {i:{}}, 'x', {d:0}]]}) ) - ); + it('vs pick at insert', () => + compose({ + op1: [['x', { p: 0 }], ['y', { i: {} }, 'x', { d: 0 }]], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + expect: [['x', { p: 0 }], ['z', { i: {} }, 'x', { d: 0 }]] + }))); describe('fuzzer tests', () => - it('complicated transform of indicies', () => compose({ - op1: [ 0, { p: 0 }, 'x', 2, { d: 0 } ], - op2: [ 0, 'x', 0, { r: true } ], - expect: [ - [0, {p:0}, 'x', 1, {d:0}], - [1, 'x', 0, {r:true}] - ]}) ) - ); + it('complicated transform of indicies', () => + compose({ + op1: [0, { p: 0 }, 'x', 2, { d: 0 }], + op2: [0, 'x', 0, { r: true }], + expect: [[0, { p: 0 }, 'x', 1, { d: 0 }], [1, 'x', 0, { r: true }]] + }))); describe('setnull interaction', function() { // Currently failing. - it('reorders items inside a setnull region', () => compose({ - op1: [{i:[]}, [0, {i:'a'}], [1, {i:'b'}]], - op2: [[0, {p:0}], [1, {d:0}]], - expect: [{i:[]}, [0, {i:'b'}], [1, {i:'a'}]]}) ); - - it('lets a setnull child be moved', () => compose({ - op1: ['list', {i:[]}, 0, {i:'hi'}], - op2: [['list', 0, {p:0}], ['z', {d:0}]], - expect: [['list', {i:[]}], ['z', {i:'hi'}]]}) ); - - return it('lets a setnull child get modified', () => compose({ - op1: [{i:[]}, 0, {i:['a']}], - op2: [0, 0, {r:'a', i:'b'}], - expect: [{i:[]}, 0, {i: []}, 0, {i: 'b'}]}) ); - }); - //expect: [{i:[]}, 0, {i:['b']}] # Maybe better?? + it('reorders items inside a setnull region', () => + compose({ + op1: [{ i: [] }, [0, { i: 'a' }], [1, { i: 'b' }]], + op2: [[0, { p: 0 }], [1, { d: 0 }]], + expect: [{ i: [] }, [0, { i: 'b' }], [1, { i: 'a' }]] + })); + + it('lets a setnull child be moved', () => + compose({ + op1: ['list', { i: [] }, 0, { i: 'hi' }], + op2: [['list', 0, { p: 0 }], ['z', { d: 0 }]], + expect: [['list', { i: [] }], ['z', { i: 'hi' }]] + })); + + return it('lets a setnull child get modified', () => + compose({ + op1: [{ i: [] }, 0, { i: ['a'] }], + op2: [0, 0, { r: 'a', i: 'b' }], + expect: [{ i: [] }, 0, { i: [] }, 0, { i: 'b' }] + })); + }); + //expect: [{i:[]}, 0, {i:['b']}] # Maybe better?? return describe('regression', function() { - it('skips op2 drops when calculating op1 drop index simple', () => compose({ - op1: [[ 0, { p: 0 } ], [ 2, { d: 0 } ]], - op2: [[ 0, { p: 0 } ], [ 1, { d: 0 } ]], - expect: [ [ 0, { p: 1 } ], [ 1, { p: 0, d: 0 } ], [ 2, { d: 1 } ] ]}) ); - - it('skips op2 drops when calculating op1 drop index complex', () => compose({ - op1: [[0, {p:0, d:1}], [1, {p:1}], [2, {d:0}]], - op2: [[0, {p:0}], [1, {d:0}]], - // expect: [[0, {p:1}], [1, {d:0, p:0}], [2, d:1]] - expect: [[0, {p:1}], [1, {p:0, d:0}], [2, {d:1}]]}) ); - - it('3', () => compose({ - op1: [ { i: [ null, [] ] }, 0, { i: '' } ], - op2: [ 1, { p: 0 }, 0, { d: 0 } ], - // ... it'd be way more consistent to drop the null separately rather than merging it?? - expect: [ { i: [ [] ] }, [ 0, { i: '' } ], [ 1, 0, { i: null } ] ]}) ); - - return it('4', () => compose({ // This one triggered a bug in cursor! - op1: [ 0, - [ 0, [ 'a', { r: true } ], [ 'b', { d: 0 } ] ], - [ 2, { p: 0 } ] ], - op2: [ 0, 0, 'c', { i: 'd' } ], - expect: [ 0, - [ 0, [ 'a', { r: true } ], [ 'b', { d: 0 } ], [ 'c', { i: 'd' } ] ], - [ 2, { p: 0 } ] - ]}) ); + it('skips op2 drops when calculating op1 drop index simple', () => + compose({ + op1: [[0, { p: 0 }], [2, { d: 0 }]], + op2: [[0, { p: 0 }], [1, { d: 0 }]], + expect: [[0, { p: 1 }], [1, { p: 0, d: 0 }], [2, { d: 1 }]] + })); + + it('skips op2 drops when calculating op1 drop index complex', () => + compose({ + op1: [[0, { p: 0, d: 1 }], [1, { p: 1 }], [2, { d: 0 }]], + op2: [[0, { p: 0 }], [1, { d: 0 }]], + // expect: [[0, {p:1}], [1, {d:0, p:0}], [2, d:1]] + expect: [[0, { p: 1 }], [1, { p: 0, d: 0 }], [2, { d: 1 }]] + })); + + it('3', () => + compose({ + op1: [{ i: [null, []] }, 0, { i: '' }], + op2: [1, { p: 0 }, 0, { d: 0 }], + // ... it'd be way more consistent to drop the null separately rather than merging it?? + expect: [{ i: [[]] }, [0, { i: '' }], [1, 0, { i: null }]] + })); + + return it('4', () => + compose({ + // This one triggered a bug in cursor! + op1: [0, [0, ['a', { r: true }], ['b', { d: 0 }]], [2, { p: 0 }]], + op2: [0, 0, 'c', { i: 'd' }], + expect: [ + 0, + [0, ['a', { r: true }], ['b', { d: 0 }], ['c', { i: 'd' }]], + [2, { p: 0 }] + ] + })); + }); }); -}); // *** Old stuff describe('old compose', function() { it('gloms together unrelated edits', function() { compose({ - op1: [['a', {p:0}], ['b', {d:0}]], - op2: [['x', {p:0}], ['y', {d:0}]], - expect: [['a', {p:0}], ['b', {d:0}], ['x', {p:1}], ['y', {d:1}]]}); + op1: [['a', { p: 0 }], ['b', { d: 0 }]], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: [ + ['a', { p: 0 }], + ['b', { d: 0 }], + ['x', { p: 1 }], + ['y', { d: 1 }] + ] + }); return compose({ - op1: [2, {i:'hi'}], - op2: [0, 'x', {r:true}], - expect: [[0, 'x', {r:true}], [2, {i:"hi"}]]}); - }); + op1: [2, { i: 'hi' }], + op2: [0, 'x', { r: true }], + expect: [[0, 'x', { r: true }], [2, { i: 'hi' }]] + }); + }); - it('translates drops in objects', () => compose({ - op1: ['x', ['a', {p:0}], ['b', {d:0}]], // x.a -> x.b - op2: [['x', {p:0}], ['y', {d:0}]], // x -> y - expect: [['x', {p:0}, 'a', {p:1}], ['y', {d:0}, 'b', {d:1}]]}) ); // x.a -> y.b, x -> y - - it('untranslates picks in objects', () => compose({ - op1: [['x', {p:0}], ['y', {d:0}]], // x -> y - op2: [['y', 'a', {p:0}], ['z', {d:0}]], // y.a -> z - expect: [['x',{p:0},'a',{p:1}], ['y',{d:0}], ['z',{d:1}]]}) ); // x.a -> z, x -> y - - it('insert gets carried wholesale', () => compose({ - op1: ['x', {i:'hi there'}], - op2: [['x', {p:0}], ['y', {d:0}]], // x -> y - expect: ['y', {i:'hi there'}]}) ); - - it('insert gets edited by the op', () => compose({ - op1: ['x', {i:{a:1, b:2, c:3}}], - op2: [['x', 'a', {p:0}], ['y', {d:0}]], - expect: [['x', {i:{b:2, c:3}}], ['y', {i:1}]]}) ); - - return it('does not merge mutual inserts', () => compose({ - op1: [{i:{}}], - op2: ['x', {i:"hi"}], - expect: [{i:{}}, 'x', {i:'hi'}]}) ); -}); + it('translates drops in objects', () => + compose({ + op1: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], // x.a -> x.b + op2: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y + expect: [['x', { p: 0 }, 'a', { p: 1 }], ['y', { d: 0 }, 'b', { d: 1 }]] + })); // x.a -> y.b, x -> y - // TODO: List nonsense. + it('untranslates picks in objects', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y + op2: [['y', 'a', { p: 0 }], ['z', { d: 0 }]], // y.a -> z + expect: [ + ['x', { p: 0 }, 'a', { p: 1 }], + ['y', { d: 0 }], + ['z', { d: 1 }] + ] + })); // x.a -> z, x -> y + + it('insert gets carried wholesale', () => + compose({ + op1: ['x', { i: 'hi there' }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y + expect: ['y', { i: 'hi there' }] + })); + + it('insert gets edited by the op', () => + compose({ + op1: ['x', { i: { a: 1, b: 2, c: 3 } }], + op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], + expect: [['x', { i: { b: 2, c: 3 } }], ['y', { i: 1 }]] + })); + + return it('does not merge mutual inserts', () => + compose({ + op1: [{ i: {} }], + op2: ['x', { i: 'hi' }], + expect: [{ i: {} }, 'x', { i: 'hi' }] + })); + }); - // TODO: Edits. + // TODO: List nonsense. + // TODO: Edits. -// ****** Transform ****** + // ****** Transform ****** describe('transform', function() { describe('op1 pick', function() { - it('vs delete', () => xf({ - op1: [['x', {p:0}], ['y', {d:0}]], - op2: ['x', {r:true}], - expect: null - }) - ); - it('vs delete parent', () => xf({ - op1: [['x', 'a', {p:0}], ['y', {d:0}]], - op2: ['x', {r:true}], - expect: null - }) - ); - it('vs delete parent 2', () => xf({ - op1: ['x', ['a', {p:0}], ['b', {d:0}]], - op2: ['x', {r:true}], - expect: null - }) - ); - - it('vs pick', () => xf({ - op1: [['x', {p:0}], ['z', {d:0}]], - op2: [['x', {p:0}], ['y', {d:0}]], - // Consider adding a conflict for this case. - expectLeft: [['y', {p:0}], ['z', {d:0}]], - expectRight: null - }) - ); - it('vs pick parent', () => xf({ - op1: [['x', 'a', {p:0}], ['z', {d:0}]], - op2: [['x', {p:0}], ['y', {d:0}]], - expect: [['y', 'a', {p:0}], ['z', {d:0}]]}) ); - - it('vs pick and pick child', () => xf({ // regression - op1: [ // a -> xa, a.c -> xc - ['a', {p:0}, 'c', {p:1}], - ['xa', {d:0}], - ['xc', {d:1}] - ], - op2: [['a', {p:0}], ['b', {d:0}]], // a -> b - expectLeft: [ - ['b', {p:0}, 'c', {p:1}], - ['xa', {d:0}], - ['xc', {d:1}] - ], - expectRight: [ - ['b', 'c', {p:0}], - ['xc', {d:0}] - ]}) ); - - it('vs edit', () => xf({ - op1: [['x', {p:0}], ['z', {d:0}]], - op2: ['x', {es:['hi']}], - expect: [['x', {p:0}], ['z', {d:0}]]}) ); - - it('vs delete, drop', () => xf({ - op1: [['x', {p:0}], ['y', {d:0}]], - op2: [['a', {p:0}], ['x', {r:0, d:0}]], - expect: null - }) - ); + it('vs delete', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: ['x', { r: true }], + expect: null + })); + it('vs delete parent', () => + xf({ + op1: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], + op2: ['x', { r: true }], + expect: null + })); + it('vs delete parent 2', () => + xf({ + op1: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], + op2: ['x', { r: true }], + expect: null + })); - it('vs delete, insert', () => xf({ - op1: [['x', {p:0}], ['y', {d:0}]], - op2: ['x', {r:0, i:5}], - expect: null - }) - ); + it('vs pick', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + // Consider adding a conflict for this case. + expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], + expectRight: null + })); + it('vs pick parent', () => + xf({ + op1: [['x', 'a', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: [['y', 'a', { p: 0 }], ['z', { d: 0 }]] + })); + + it('vs pick and pick child', () => + xf({ + // regression + op1: [ + // a -> xa, a.c -> xc + ['a', { p: 0 }, 'c', { p: 1 }], + ['xa', { d: 0 }], + ['xc', { d: 1 }] + ], + op2: [['a', { p: 0 }], ['b', { d: 0 }]], // a -> b + expectLeft: [ + ['b', { p: 0 }, 'c', { p: 1 }], + ['xa', { d: 0 }], + ['xc', { d: 1 }] + ], + expectRight: [['b', 'c', { p: 0 }], ['xc', { d: 0 }]] + })); + + it('vs edit', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: ['x', { es: ['hi'] }], + expect: [['x', { p: 0 }], ['z', { d: 0 }]] + })); - it('vs pick, drop to self', - () => xf({ - op1: [['x', {p:0}], ['y', {d:0}]], - op2: [['x', {p:0}], ['y', {d:0}]], + it('vs delete, drop', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [['a', { p: 0 }], ['x', { r: 0, d: 0 }]], expect: null - }) , + })); - () => xf({ - op1: [['a', 1, {p:0}], ['y', {d:0}]], - op2: [['a', 1, {p:0}], ['y', {d:0}]], + it('vs delete, insert', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: ['x', { r: 0, i: 5 }], expect: null - }) - ); + })); + + it( + 'vs pick, drop to self', + () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: null + }), + + () => + xf({ + op1: [['a', 1, { p: 0 }], ['y', { d: 0 }]], + op2: [['a', 1, { p: 0 }], ['y', { d: 0 }]], + expect: null + }) + ); - it('vs pick, drop', () => xf({ - op1: [['x', {p:0}], ['z', {d:0}]], // x->z - op2: [['a', {p:0}], ['x', {p:1, d:0}], ['y', {d:1}]], // a->x, x->y - expectLeft: [['y', {p:0}], ['z', {d:0}]], - expectRight: null - }) - ); + it('vs pick, drop', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], // x->z + op2: [['a', { p: 0 }], ['x', { p: 1, d: 0 }], ['y', { d: 1 }]], // a->x, x->y + expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], + expectRight: null + })); - it('vs pick, insert', () => xf({ - op1: [['x', {p:0}], ['z', {d:0}]], - op2: [['x', {p:0, i:5}], ['y', {d:0}]], - expectLeft: [['y', {p:0}], ['z', {d:0}]], - expectRight: null - }) - ); - - return it('vs pick, edit', () => - ({ - op1: [['x', {p:0}], ['z', {d:0}]], - op2: [['x', {es:['hi'], p:0}], ['y', {d:0}]], - expectLeft: [['y', {p:0}], ['z', {d:0}]], + it('vs pick, insert', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', { p: 0, i: 5 }], ['y', { d: 0 }]], + expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], expectRight: null - }) - ); + })); + + return it('vs pick, edit', () => ({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', { es: ['hi'], p: 0 }], ['y', { d: 0 }]], + expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], + expectRight: null + })); }); describe('op1 delete', function() { - it('vs delete', () => xf({ - op1: ['x', {r:true}], - op2: ['x', {r:true}], - expect: null - }) - ); - it('vs delete parent', () => xf({ - op1: ['x', 'a', {r:true}], - op2: ['x', {r:true}], - expect: null - }) - ); - - it('vs pick', () => xf({ - op1: ['x', {r:true}], - op2: [['x', {p:0}], ['y', {d:0}]], - expect: ['y', {r:true}]}) ); - it('vs pick parent', () => xf({ - op1: ['x', 'a', {r:true}], - op2: [['x', {p:0}], ['y', {d:0}]], - expect: ['y', 'a', {r:true}]}) ); - - it('vs pick and drop', () => xf({ - op1: ['x', {r:true}], - op2: [['a', {p:0}], ['x', {d:0, p:1}], ['z', {d:1}]], - expect: ['z', {r:true}]}) ); - - it('vs edit', () => xf({ - op1: ['x', {r:true}], - op2: ['x', {es:['hi']}], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: ['x', {r:true}]}) ); - - it('vs move and insert', () => xf({ - op1: [ 'a', 1, { r: true } ], - op2: [ - [ 'a', { p: 0 } ], - [ 'b', { d: 0 }, [ 0, { i: 5 } ], [ 1, { i: 5 } ] ] - ], - expect: ['b', 3, {r:true}]}) ); + it('vs delete', () => + xf({ + op1: ['x', { r: true }], + op2: ['x', { r: true }], + expect: null + })); + it('vs delete parent', () => + xf({ + op1: ['x', 'a', { r: true }], + op2: ['x', { r: true }], + expect: null + })); + + it('vs pick', () => + xf({ + op1: ['x', { r: true }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', { r: true }] + })); + it('vs pick parent', () => + xf({ + op1: ['x', 'a', { r: true }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', 'a', { r: true }] + })); + + it('vs pick and drop', () => + xf({ + op1: ['x', { r: true }], + op2: [['a', { p: 0 }], ['x', { d: 0, p: 1 }], ['z', { d: 1 }]], + expect: ['z', { r: true }] + })); + + it('vs edit', () => + xf({ + op1: ['x', { r: true }], + op2: ['x', { es: ['hi'] }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['x', { r: true }] + })); + + it('vs move and insert', () => + xf({ + op1: ['a', 1, { r: true }], + op2: [['a', { p: 0 }], ['b', { d: 0 }, [0, { i: 5 }], [1, { i: 5 }]]], + expect: ['b', 3, { r: true }] + })); return describe('vs pick child', function() { - it('move in', () => xf({ - op1: ['x', {r:true}], - op2: [['a', {p:0}], ['x', 'y', {d:0}]], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: ['x', {r:true}, 'y', {r:true}]}) ); // Also ok if its just x, r:true - - it('move across', () => xf({ - op1: ['x', {r:true}], // delete doc.x - op2: ['x', ['y', {p:0}], ['z', {d:0}]], - expect: ['x', {r:true}]}) ); - - it('move out', () => xf({ - op1: ['x', {r:true}], - op2: [['x', 'y', {p:0}], ['y', {d:0}]], // move doc.x.y -> doc.y - expect: [['x', {r:true}], ['y', {r:true}]]}) ); // delete doc.x and doc.y - - it('multiple out', () => xf({ - op1: ['x', {r:true}], - op2: [['x', 'y', {p:0}, 'z', {p:1}], ['y', {d:0}], ['z', {d:1}]], - expect: [['x', {r:true}], ['y', {r:true}], ['z', {r:true}]]}) ); - - it('chain out', () => xf({ - op1: ['x', {r:true}], - op2: [['x', 'y', {p:0}], ['y', {p:1}], ['z', {d:0}, 'a', {d:1}]], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op2: [['y', {p:0}], ['z', 'a', {d:0}]] - }, // cMv(['y'], ['z', 'a']) - expect: [['x', {r:true}], ['z', {r:true}, 'a', {r:true}]]}) ); - - return it('mess', () => xf({ - // yeesh - op1: [['x', {r:true}, 'y', 'z', {p:0}], ['z', {d:0}]], - op2: [['x', 'y', {p:0}], ['y', {d:0}]], - expect: [['x', {r:true}], ['y', {r:true}, 'z', {p:0}], ['z', {d:0}]]}) ); + it('move in', () => + xf({ + op1: ['x', { r: true }], + op2: [['a', { p: 0 }], ['x', 'y', { d: 0 }]], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['x', { r: true }, 'y', { r: true }] + })); // Also ok if its just x, r:true + + it('move across', () => + xf({ + op1: ['x', { r: true }], // delete doc.x + op2: ['x', ['y', { p: 0 }], ['z', { d: 0 }]], + expect: ['x', { r: true }] + })); + + it('move out', () => + xf({ + op1: ['x', { r: true }], + op2: [['x', 'y', { p: 0 }], ['y', { d: 0 }]], // move doc.x.y -> doc.y + expect: [['x', { r: true }], ['y', { r: true }]] + })); // delete doc.x and doc.y + + it('multiple out', () => + xf({ + op1: ['x', { r: true }], + op2: [ + ['x', 'y', { p: 0 }, 'z', { p: 1 }], + ['y', { d: 0 }], + ['z', { d: 1 }] + ], + expect: [['x', { r: true }], ['y', { r: true }], ['z', { r: true }]] + })); + + it('chain out', () => + xf({ + op1: ['x', { r: true }], + op2: [ + ['x', 'y', { p: 0 }], + ['y', { p: 1 }], + ['z', { d: 0 }, 'a', { d: 1 }] + ], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: [['y', { p: 0 }], ['z', 'a', { d: 0 }]] + }, // cMv(['y'], ['z', 'a']) + expect: [['x', { r: true }], ['z', { r: true }, 'a', { r: true }]] + })); + + return it('mess', () => + xf({ + // yeesh + op1: [['x', { r: true }, 'y', 'z', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', 'y', { p: 0 }], ['y', { d: 0 }]], + expect: [ + ['x', { r: true }], + ['y', { r: true }, 'z', { p: 0 }], + ['z', { d: 0 }] + ] + })); + }); }); - }); describe('op1 drop', function() { - it('vs delete parent', () => xf({ - op1: [['x', {p:0}], ['y', 'a', {d:0}]], - op2: ['y', {r:true}], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: ['x', {r:true}]}) ); - - it('vs a cancelled parent', () => xf({ - // This is actually a really complicated case. - op1: [['x', 'y', {p:0}], ['y', {p:1}], ['z', {d:0}, 'a', {d:1}]], - op2: ['x', {r:true}], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op1: [['y', {p:0}], ['z', 'a', {d:0}]] - }, // c1: cMv(['y'], ['z', 'a']) - expect: ['y', {r:true}]}) ); - - it('vs pick parent', () => xf({ - op1: [['x', {p:0}], ['y', 'a', {d:0}]], - op2: [['y', {p:0}], ['z', {d:0}]], - expect: [['x', {p:0}], ['z', 'a', {d:0}]]}) ); - - it('vs drop', () => xf({ - op1: [['x', {p:0}], ['z', {d:0}]], - op2: [['y', {p:0}], ['z', {d:0}]], - conflict: { type: DROP_COLLISION - }, - expectLeft: [['x', {p:0}], ['z', {r:true, d:0}]], - expectRight: ['x', {r:true}]}) ); - - it('vs drop (list)', () => xf({ - op1: [[0, {p:0}], [4, {d:0}]], - op2: [[5, {d:0}], [10, {p:0}]], - expectLeft: [[0, {p:0}], [4, {d:0}]], - expectRight: [[0, {p:0}], [5, {d:0}]]}) ); - - it('vs drop (chained)', () => xf({ - op1: [['a', {p:1}], ['x', {p:0}], ['z', {d:0}, 'a', {d:1}]], - op2: [['y', {p:0}], ['z', {d:0}]], - conflict: { - type: DROP_COLLISION, - op1: [['x', {p:0}], ['z', {d:0}]] - }, //cMv(['x'], ['z']) - expectLeft: [['a', {p:0}], ['x', {p:1}], ['z', {r:true, d:1}, 'a', {d:0}]], - expectRight: [['a', {r:true}], ['x', {r:true}]]}) ); - - it('vs insert', () => xf({ - op1: [['x', {p:0}], ['z', {d:0}]], - op2: ['z', {i:5}], - conflict: { type: DROP_COLLISION - }, - expectLeft: [['x', {p:0}], ['z', {r:true, d:0}]], - expectRight: ['x', {r:true}]}) ); - - it('vs pick (a->b->c vs b->x)', () => xf({ - op1: [['a', {p:0}], ['b', {p:1, d:0}], ['c', {d:1}]], - op2: [['b', {p:0}], ['x', {d:0}]], - expectLeft: [['a', {p:0}], ['b', {d:0}], ['c', {d:1}], ['x', {p:1}]], - expectRight: [['a', {p:0}], ['b', {d:0}]]}) ); + it('vs delete parent', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: ['y', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['x', { r: true }] + })); + + it('vs a cancelled parent', () => + xf({ + // This is actually a really complicated case. + op1: [ + ['x', 'y', { p: 0 }], + ['y', { p: 1 }], + ['z', { d: 0 }, 'a', { d: 1 }] + ], + op2: ['x', { r: true }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [['y', { p: 0 }], ['z', 'a', { d: 0 }]] + }, // c1: cMv(['y'], ['z', 'a']) + expect: ['y', { r: true }] + })); + + it('vs pick parent', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + expect: [['x', { p: 0 }], ['z', 'a', { d: 0 }]] + })); + + it('vs drop', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + conflict: { type: DROP_COLLISION }, + expectLeft: [['x', { p: 0 }], ['z', { r: true, d: 0 }]], + expectRight: ['x', { r: true }] + })); + + it('vs drop (list)', () => + xf({ + op1: [[0, { p: 0 }], [4, { d: 0 }]], + op2: [[5, { d: 0 }], [10, { p: 0 }]], + expectLeft: [[0, { p: 0 }], [4, { d: 0 }]], + expectRight: [[0, { p: 0 }], [5, { d: 0 }]] + })); + + it('vs drop (chained)', () => + xf({ + op1: [ + ['a', { p: 1 }], + ['x', { p: 0 }], + ['z', { d: 0 }, 'a', { d: 1 }] + ], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + conflict: { + type: DROP_COLLISION, + op1: [['x', { p: 0 }], ['z', { d: 0 }]] + }, //cMv(['x'], ['z']) + expectLeft: [ + ['a', { p: 0 }], + ['x', { p: 1 }], + ['z', { r: true, d: 1 }, 'a', { d: 0 }] + ], + expectRight: [['a', { r: true }], ['x', { r: true }]] + })); + + it('vs insert', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: ['z', { i: 5 }], + conflict: { type: DROP_COLLISION }, + expectLeft: [['x', { p: 0 }], ['z', { r: true, d: 0 }]], + expectRight: ['x', { r: true }] + })); + + it('vs pick (a->b->c vs b->x)', () => + xf({ + op1: [['a', { p: 0 }], ['b', { p: 1, d: 0 }], ['c', { d: 1 }]], + op2: [['b', { p: 0 }], ['x', { d: 0 }]], + expectLeft: [ + ['a', { p: 0 }], + ['b', { d: 0 }], + ['c', { d: 1 }], + ['x', { p: 1 }] + ], + expectRight: [['a', { p: 0 }], ['b', { d: 0 }]] + })); return describe.skip('vs move inside me', function() { // Note: This is *not* blackholeing! The edits are totally fine; we // just need one edit to win. // The current behaviour just nukes both. - it('in objects', () => xf({ - op1: [['x', {p:0}], ['y', 'a', {d:0}]], - op2: [['x', 'a', {d:0}], ['y', {p:0}]], - expectLeft: [['x', {p:0}, 'a', {p:1}], ['y', {d:1}, 'x', {d:0}]], - expectRight: null - }) - ); - - it('in lists', () => xf({ - op1: [0, {p:0}, 'x', {d:0}], - op2: [[0, 'y', {d:0}], [1, {p:0}]], - expectLeft: [0, {p:0, d:1}, ['x', {d:0}], ['y', {p:1}]], - expectRight: null - }) - ); - - return it('multiple', () => xf({ - // a->x.a, b->x.b - op1: [['a', {p:0}], ['b', {p:1}], ['x', 'a', {d:0}, 'b', {d:1}]], - op2: [['a', 'x', {d:0}], ['x', {p:0}]], // x->a.x - expectLeft: [['a', {p:0}, 'x', {p:1}], ['b', {p:2}], - ['x', {d:1}, ['a', {d:0}], ['b', {d:2}]]], - expectRight: null - }) - ); + it('in objects', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]], + expectLeft: [ + ['x', { p: 0 }, 'a', { p: 1 }], + ['y', { d: 1 }, 'x', { d: 0 }] + ], + expectRight: null + })); + + it('in lists', () => + xf({ + op1: [0, { p: 0 }, 'x', { d: 0 }], + op2: [[0, 'y', { d: 0 }], [1, { p: 0 }]], + expectLeft: [0, { p: 0, d: 1 }, ['x', { d: 0 }], ['y', { p: 1 }]], + expectRight: null + })); + + return it('multiple', () => + xf({ + // a->x.a, b->x.b + op1: [ + ['a', { p: 0 }], + ['b', { p: 1 }], + ['x', 'a', { d: 0 }, 'b', { d: 1 }] + ], + op2: [['a', 'x', { d: 0 }], ['x', { p: 0 }]], // x->a.x + expectLeft: [ + ['a', { p: 0 }, 'x', { p: 1 }], + ['b', { p: 2 }], + ['x', { d: 1 }, ['a', { d: 0 }], ['b', { d: 2 }]] + ], + expectRight: null + })); }); }); describe('op1 insert', function() { - it('vs delete parent', () => xf({ - op1: ['y', 'a', {i:5}], - op2: ['y', {r:true}], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: null - }) - ); - - it('vs pick parent', () => xf({ - op1: ['y', 'a', {i:5}], - op2: [['y', {p:0}], ['z', {d:0}]], - expect: ['z', 'a', {i:5}]}) ); - - it('vs drop', () => xf({ - op1: ['z', {i:5}], - op2: [['y', {p:0}], ['z', {d:0}]], - conflict: { type: DROP_COLLISION - }, - expectLeft: ['z', {r:true, i:5}], - expectRight: null - }) - ); - - it('vs insert', () => xf({ - op1: ['z', {i:5}], - op2: ['z', {i:10}], - conflict: { type: DROP_COLLISION - }, - expectLeft: ['z', {r:true, i:5}], - expectRight: null - }) - ); - - it('vs insert at list position', () => xf({ - op1: [5, {i:'hi'}], - op2: [5, {i:'there'}], - expectLeft: [5, {i:'hi'}], - expectRight: [6, {i:'hi'}]}) ); - - it('vs identical insert', () => xf({ - op1: ['z', {i:5}], - op2: ['z', {i:5}], - expect: null - }) - ); - - // This is the new setNull for setting up schemas - it('vs embedded inserts', function() { + it('vs delete parent', () => xf({ - op1: ['x', {i:{}}], - op2: ['x', {i:{}}, 'y', {i:5}], + op1: ['y', 'a', { i: 5 }], + op2: ['y', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, expect: null - }); + })); + it('vs pick parent', () => xf({ - op1: ['x', {i:{}}, 'y', {i:5}], - op2: ['x', {i:{}}], - expect: ['x', 'y', {i:5}]}); + op1: ['y', 'a', { i: 5 }], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + expect: ['z', 'a', { i: 5 }] + })); + it('vs drop', () => xf({ - op1: ['x', {i:{}}, 'y', {i:5}], - op2: ['x', {i:{}}, 'y', {i:5}], - expect: null - }); + op1: ['z', { i: 5 }], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + conflict: { type: DROP_COLLISION }, + expectLeft: ['z', { r: true, i: 5 }], + expectRight: null + })); + + it('vs insert', () => + xf({ + op1: ['z', { i: 5 }], + op2: ['z', { i: 10 }], + conflict: { type: DROP_COLLISION }, + expectLeft: ['z', { r: true, i: 5 }], + expectRight: null + })); + + it('vs insert at list position', () => + xf({ + op1: [5, { i: 'hi' }], + op2: [5, { i: 'there' }], + expectLeft: [5, { i: 'hi' }], + expectRight: [6, { i: 'hi' }] + })); + + it('vs identical insert', () => + xf({ + op1: ['z', { i: 5 }], + op2: ['z', { i: 5 }], + expect: null + })); + + // This is the new setNull for setting up schemas + it('vs embedded inserts', function() { + xf({ + op1: ['x', { i: {} }], + op2: ['x', { i: {} }, 'y', { i: 5 }], + expect: null + }); + + xf({ + op1: ['x', { i: {} }, 'y', { i: 5 }], + op2: ['x', { i: {} }], + expect: ['x', 'y', { i: 5 }] + }); + + xf({ + op1: ['x', { i: {} }, 'y', { i: 5 }], + op2: ['x', { i: {} }, 'y', { i: 5 }], + expect: null + }); return xf({ - op1: ['x', {i:{}}, 'y', {i:5}], - op2: ['x', {i:{}}, 'y', {i:6}], + op1: ['x', { i: {} }, 'y', { i: 5 }], + op2: ['x', { i: {} }, 'y', { i: 6 }], conflict: { type: DROP_COLLISION, - op1: ['x', 'y', {i:5}], - op2: ['x', 'y', {i:6}] + op1: ['x', 'y', { i: 5 }], + op2: ['x', 'y', { i: 6 }] }, - expectLeft: ['x', 'y', {r:true, i:5}], + expectLeft: ['x', 'y', { r: true, i: 5 }], expectRight: null }); }); - return it('with embedded edits', () => xf({ - op1: [{i:'', es:['aaa']}], - op2: [{i:'', es:['bbb']}], - expectLeft: [{es:['aaa']}], - expectRight: [{es:[3, 'aaa']}]}) ); - }); + return it('with embedded edits', () => + xf({ + op1: [{ i: '', es: ['aaa'] }], + op2: [{ i: '', es: ['bbb'] }], + expectLeft: [{ es: ['aaa'] }], + expectRight: [{ es: [3, 'aaa'] }] + })); + }); describe('op1 edit', function() { - it('vs delete', () => xf({ - op1: ['x', {es:['hi']}], - op2: ['x', {r:true}], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: null - }) - ); - - it('vs delete parent', () => xf({ - op1: ['x', 'y', {es:['hi']}], - op2: ['x', {r:true}], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: null - }) - ); - - it('vs pick', () => xf({ - op1: ['x', {es:['hi']}], - op2: [['x', {p:0}], ['y', {d:0}]], - expect: ['y', {es:['hi']}]}) ); - - it('vs edit string', () => xf({ - op1: ['x', {es:['ab']}], - op2: ['x', {es:['cd']}], - expectLeft: ['x', {es:['ab']}], - expectRight: ['x', {es:[2, 'ab']}]}) ); - - it('vs edit number', () => xf({ - op1: [{ena:5}], - op2: [{ena:100}], - expect: [{ena:5}]}) ); - - it('throws if edit types arent compatible', () => assert.throws(() => type.transform([{es:[]}], [{ena:5}], 'left'))); - - it('vs move and edit', () => xf({ - op1: ['x', {es:[1, 'ab']}], - op2: [['x', {p:0}], ['y', {d:0, es:[{d:1}, 'cd']}]], - expectLeft: ['y', {es:['ab']}], - expectRight: ['y', {es:[2, 'ab']}]}) ); - - return it('throws if the type is missing', () => assert.throws(() => type.transform([{et:'missing', e:{}}], [{et:'missing', e:{}}], 'left'))); + it('vs delete', () => + xf({ + op1: ['x', { es: ['hi'] }], + op2: ['x', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })); + + it('vs delete parent', () => + xf({ + op1: ['x', 'y', { es: ['hi'] }], + op2: ['x', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })); + + it('vs pick', () => + xf({ + op1: ['x', { es: ['hi'] }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', { es: ['hi'] }] + })); + + it('vs edit string', () => + xf({ + op1: ['x', { es: ['ab'] }], + op2: ['x', { es: ['cd'] }], + expectLeft: ['x', { es: ['ab'] }], + expectRight: ['x', { es: [2, 'ab'] }] + })); + + it('vs edit number', () => + xf({ + op1: [{ ena: 5 }], + op2: [{ ena: 100 }], + expect: [{ ena: 5 }] + })); + + it('throws if edit types arent compatible', () => + assert.throws(() => + type.transform([{ es: [] }], [{ ena: 5 }], 'left') + )); + + it('vs move and edit', () => + xf({ + op1: ['x', { es: [1, 'ab'] }], + op2: [['x', { p: 0 }], ['y', { d: 0, es: [{ d: 1 }, 'cd'] }]], + expectLeft: ['y', { es: ['ab'] }], + expectRight: ['y', { es: [2, 'ab'] }] + })); + + return it('throws if the type is missing', () => + assert.throws(() => + type.transform( + [{ et: 'missing', e: {} }], + [{ et: 'missing', e: {} }], + 'left' + ) + )); }); describe('op2 cancel move', function() { - it('and insert', () => xf({ - op1: ['x', {r:true}], - op2: [['x', 'a', {p:0}], ['y', {d:0}, 'b', {i:5}]], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op2: ['y', 'b', {i:5}] - }, - expect: [['x', {r:true}], ['y', {r:true}, 'b', {r:true}]]}) ); + it('and insert', () => + xf({ + op1: ['x', { r: true }], + op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }, 'b', { i: 5 }]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: ['y', 'b', { i: 5 }] + }, + expect: [['x', { r: true }], ['y', { r: true }, 'b', { r: true }]] + })); - return it('and another move (rm x vs x.a -> y, q -> y.b)', () => xf({ - op1: ['x', {r:true}], - op2: [['q', {p:1}], ['x', 'a', {p:0}], ['y', {d:0}, 'b', {d:1}]], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op2: [['q', {p:0}], ['y', 'b', {d:0}]] - }, - expect: [['x', {r:true}], ['y', {r:true}, 'b', {r:true}]]}) ); - }); + return it('and another move (rm x vs x.a -> y, q -> y.b)', () => + xf({ + op1: ['x', { r: true }], + op2: [ + ['q', { p: 1 }], + ['x', 'a', { p: 0 }], + ['y', { d: 0 }, 'b', { d: 1 }] + ], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: [['q', { p: 0 }], ['y', 'b', { d: 0 }]] + }, + expect: [['x', { r: true }], ['y', { r: true }, 'b', { r: true }]] + })); + }); describe('op2 list move an op1 drop', function() { - it('vs op1 remove', () => xf({ - op1: [[0, {r:true}, 'a', {i:'hi'}], [5, {r:true}]], - op2: [[1, {p:0}], [4, {d:0}]], - expect: [[0, {r:true}], [3, 'a', {i:'hi'}], [5, {r:true}]]}) ); - - it('vs op1 remove 2', () => xf({ - op1: [[0, {r:true}, 'a', {i:'hi'}], [1, {r:true}], [2, {r:true}]], - op2: [[3, {p:0}], [4, {d:0}]], - expect: [[0, {r:true}], [1, {r:true}, 'a', {i:'hi'}], [2, {r:true}]]}) ); - - it('vs op1 insert before', () => xf({ - op1: [[0, {i:'a'}], [1, {i:'b'}], [2, 'a', {i:'hi'}]], - op2: [[0, {p:0}], [1, {d:0}]], - expect: [[0, {i:'a'}], [1, {i:'b'}], [3, 'a', {i:'hi'}]]}) ); - - - return it('vs op1 insert before and replace', () => xf({ - op1: [[0, {i:'xx'}, 'a', {r:true}], [1, 'a', {i:'hi'}]], - op2: [[0, {p:0}], [3, {d:0}]], - expect: [[0, {i:'xx'}], [3, 'a', {r:true}], [4, 'a', {i:'hi'}]]}) ); - }); + it('vs op1 remove', () => + xf({ + op1: [[0, { r: true }, 'a', { i: 'hi' }], [5, { r: true }]], + op2: [[1, { p: 0 }], [4, { d: 0 }]], + expect: [[0, { r: true }], [3, 'a', { i: 'hi' }], [5, { r: true }]] + })); + + it('vs op1 remove 2', () => + xf({ + op1: [ + [0, { r: true }, 'a', { i: 'hi' }], + [1, { r: true }], + [2, { r: true }] + ], + op2: [[3, { p: 0 }], [4, { d: 0 }]], + expect: [ + [0, { r: true }], + [1, { r: true }, 'a', { i: 'hi' }], + [2, { r: true }] + ] + })); + + it('vs op1 insert before', () => + xf({ + op1: [[0, { i: 'a' }], [1, { i: 'b' }], [2, 'a', { i: 'hi' }]], + op2: [[0, { p: 0 }], [1, { d: 0 }]], + expect: [[0, { i: 'a' }], [1, { i: 'b' }], [3, 'a', { i: 'hi' }]] + })); + return it('vs op1 insert before and replace', () => + xf({ + op1: [[0, { i: 'xx' }, 'a', { r: true }], [1, 'a', { i: 'hi' }]], + op2: [[0, { p: 0 }], [3, { d: 0 }]], + expect: [ + [0, { i: 'xx' }], + [3, 'a', { r: true }], + [4, 'a', { i: 'hi' }] + ] + })); + }); return describe('list', () => describe('drop', function() { - it('transforms by p1 drops', () => xf({ - op1: [[5, {i:5}], [10, {i:10}]], - op2: [9, {i:9}], - expectLeft: [[5, {i:5}], [10, {i:10}]], - expectRight: [[5, {i:5}], [11, {i:10}]]}) ); + it('transforms by p1 drops', () => + xf({ + op1: [[5, { i: 5 }], [10, { i: 10 }]], + op2: [9, { i: 9 }], + expectLeft: [[5, { i: 5 }], [10, { i: 10 }]], + expectRight: [[5, { i: 5 }], [11, { i: 10 }]] + })); it('transforms by p1 picks'); it('transforms by p2 picks'); return it('transforms by p2 drops'); - }) - ); + })); }); describe('conflicts', function() { @@ -1375,263 +1704,287 @@ describe('json1', function() { // xfConflict does both xf(op1, op2, left) and xf(op2, op1, right), and // uses invConflict. So this also tests RM_UNEXPECTED_CONTENT with each // test case. - it('errors if you insert', () => xf({ - op1: ['a', 'b', {i:5}], - op2: ['a', {r:true}], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: null - }) - ); - - it('errors if you drop', () => xf({ - op1: [['a', {p:0}], ['x', 'b', {d:0}]], - op2: ['x', {r:true}], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: ['a', {r:true}]}) ); - - it('errors if you rm then insert in a child', () => xf({ - op1: ['a', 'b', {r:true, i:5}], - op2: ['a', {r:true}], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op1: ['a', 'b', {i:5}] - }, - expect: null - }) - ); + it('errors if you insert', () => + xf({ + op1: ['a', 'b', { i: 5 }], + op2: ['a', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })); - it('errors if the object is replaced', () => xf({ - op1: ['a', 'b', {i:5}], - op2: ['a', {r:true, i:10}], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op2: ['a', {r:true}] - }, - expect: null - }) - ); + it('errors if you drop', () => + xf({ + op1: [['a', { p: 0 }], ['x', 'b', { d: 0 }]], + op2: ['x', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['a', { r: true }] + })); - it('handles a delete of the source parent by op2', () => xf({ - op1: [['a', {p:0}], ['b', 'b', {d:0}]], - op2: [['a', {p:0}], ['b', {r:true}, 'c', {d:0}]], - conflictLeft: { - type: RM_UNEXPECTED_CONTENT, - op2: ['b', {r:true}] - }, - expectLeft: ['b', 'c', {r:true}], - expectRight: null - }) - ); - - return it.skip('returns symmetric errors when both ops delete the other', () => xf({ - // The problem here is that there's two conflicts we want to return. - // Which one should be returned first? It'd be nice for the order of - // conflict returning to be symmetric - that is, if we know multiple - // conflicts happen, order them based on left/right. But I haven't done - // that, so we get different conflicts out of this in a first pass. - op1: [ [ 'x', { r: true } ], [ 'y', 'a', { i: {} } ] ], - op2: [ [ 'x', 'a', { i: {} } ], [ 'y', { r: true } ] ], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: ['x', {r:true}]}) ); - }); + it('errors if you rm then insert in a child', () => + xf({ + op1: ['a', 'b', { r: true, i: 5 }], + op2: ['a', { r: true }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', 'b', { i: 5 }] + }, + expect: null + })); - describe('overlapping drop', function() { - it('errors if two ops insert different content into the same place in an object', () => xf({ - op1: ['x', {i:'hi'}], - op2: ['x', {i:'yo'}], - conflict: { type: DROP_COLLISION - }, - expectLeft: ['x', {r:true, i:'hi'}], - expectRight: null - }) - ); + it('errors if the object is replaced', () => + xf({ + op1: ['a', 'b', { i: 5 }], + op2: ['a', { r: true, i: 10 }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: ['a', { r: true }] + }, + expect: null + })); - it('does not conflict if inserts are identical', () => xf({ - op1: ['x', {i:'hi'}], - op2: ['x', {i:'hi'}], - expectLeft: null, - expectRight: null - }) - ); + it('handles a delete of the source parent by op2', () => + xf({ + op1: [['a', { p: 0 }], ['b', 'b', { d: 0 }]], + op2: [['a', { p: 0 }], ['b', { r: true }, 'c', { d: 0 }]], + conflictLeft: { + type: RM_UNEXPECTED_CONTENT, + op2: ['b', { r: true }] + }, + expectLeft: ['b', 'c', { r: true }], + expectRight: null + })); - it('does not conflict if the two operations make identical moves', () => xf({ - op1: [['a', {p:0}], ['x', {d:0}]], - op2: [['a', {p:0}], ['x', {d:0}]], - expect: null - }) - ); // ??? Also ok for left: ['x', p:0, d:0] - - it('does not conflict if inserts are into a list', () => xf({ - op1: [1, {i:'hi'}], - op2: [1, {i:'yo'}], - expectLeft: [1, {i:'hi'}], - expectRight: [2, {i:'hi'}]}) ); - - it('errors if the inserts are at the root', () => xf({ - op1: [{i:1}], - op2: [{i:2}], - conflict: { type: DROP_COLLISION - }, - expectLeft: [{r:true, i:1}], - expectRight: null - }) - ); - - it('errors with insert vs drop', () => xf({ - op1: ['x', {i:'hi'}], - op2: [['a', {p:0}], ['x', {d:0}]], - // ???? - conflict: { type: DROP_COLLISION - }, - expectLeft: ['x', {r:true, i:'hi'}], - expectRight: null - }) - ); - - it('errors with drop vs insert', () => xf({ - op1: [['a', {p:0}], ['x', {d:0}]], - op2: ['x', {i:'hi'}], - conflict: { type: DROP_COLLISION - }, - expectLeft: [['a', {p:0}], ['x', {r:true, d:0}]], - expectRight: ['a', {r:true}]}) ); - - it('errors with drop vs drop', () => xf({ - op1: [['a', {p:0}], ['x', {d:0}]], - op2: [['b', {p:0}], ['x', {d:0}]], - conflict: { type: DROP_COLLISION - }, - expectLeft: [['a', {p:0}], ['x', {r:true, d:0}]], - expectRight: ['a', {r:true}]}) ); - - return it('errors if the two sides insert in the vacuum', () => xf({ - op1: [['a', {p:0}], ['b', {d:0}], ['c', {i:5}]], - op2: [['a', {p:0}], ['b', {i:6}], ['c', {d:0}]], - conflictLeft: { - type: DROP_COLLISION, - op1: [['a', {p:0}], ['b', {d:0}]], - op2: ['b', {i:6}] - }, - expectLeft: [['b', {r:true, d:0}], ['c', {p:0, i:5}]], - conflictRight: { - type: DROP_COLLISION, - op1: ['c', {i:5}], - op2: [['a', {p:0}], ['c', {d:0}]] - }, - expectRight: null - }) - ); + return it.skip('returns symmetric errors when both ops delete the other', () => + xf({ + // The problem here is that there's two conflicts we want to return. + // Which one should be returned first? It'd be nice for the order of + // conflict returning to be symmetric - that is, if we know multiple + // conflicts happen, order them based on left/right. But I haven't done + // that, so we get different conflicts out of this in a first pass. + op1: [['x', { r: true }], ['y', 'a', { i: {} }]], + op2: [['x', 'a', { i: {} }], ['y', { r: true }]], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['x', { r: true }] + })); }); + describe('overlapping drop', function() { + it('errors if two ops insert different content into the same place in an object', () => + xf({ + op1: ['x', { i: 'hi' }], + op2: ['x', { i: 'yo' }], + conflict: { type: DROP_COLLISION }, + expectLeft: ['x', { r: true, i: 'hi' }], + expectRight: null + })); + + it('does not conflict if inserts are identical', () => + xf({ + op1: ['x', { i: 'hi' }], + op2: ['x', { i: 'hi' }], + expectLeft: null, + expectRight: null + })); + + it('does not conflict if the two operations make identical moves', () => + xf({ + op1: [['a', { p: 0 }], ['x', { d: 0 }]], + op2: [['a', { p: 0 }], ['x', { d: 0 }]], + expect: null + })); // ??? Also ok for left: ['x', p:0, d:0] + + it('does not conflict if inserts are into a list', () => + xf({ + op1: [1, { i: 'hi' }], + op2: [1, { i: 'yo' }], + expectLeft: [1, { i: 'hi' }], + expectRight: [2, { i: 'hi' }] + })); + + it('errors if the inserts are at the root', () => + xf({ + op1: [{ i: 1 }], + op2: [{ i: 2 }], + conflict: { type: DROP_COLLISION }, + expectLeft: [{ r: true, i: 1 }], + expectRight: null + })); + + it('errors with insert vs drop', () => + xf({ + op1: ['x', { i: 'hi' }], + op2: [['a', { p: 0 }], ['x', { d: 0 }]], + // ???? + conflict: { type: DROP_COLLISION }, + expectLeft: ['x', { r: true, i: 'hi' }], + expectRight: null + })); + + it('errors with drop vs insert', () => + xf({ + op1: [['a', { p: 0 }], ['x', { d: 0 }]], + op2: ['x', { i: 'hi' }], + conflict: { type: DROP_COLLISION }, + expectLeft: [['a', { p: 0 }], ['x', { r: true, d: 0 }]], + expectRight: ['a', { r: true }] + })); + + it('errors with drop vs drop', () => + xf({ + op1: [['a', { p: 0 }], ['x', { d: 0 }]], + op2: [['b', { p: 0 }], ['x', { d: 0 }]], + conflict: { type: DROP_COLLISION }, + expectLeft: [['a', { p: 0 }], ['x', { r: true, d: 0 }]], + expectRight: ['a', { r: true }] + })); + + return it('errors if the two sides insert in the vacuum', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }], ['c', { i: 5 }]], + op2: [['a', { p: 0 }], ['b', { i: 6 }], ['c', { d: 0 }]], + conflictLeft: { + type: DROP_COLLISION, + op1: [['a', { p: 0 }], ['b', { d: 0 }]], + op2: ['b', { i: 6 }] + }, + expectLeft: [['b', { r: true, d: 0 }], ['c', { p: 0, i: 5 }]], + conflictRight: { + type: DROP_COLLISION, + op1: ['c', { i: 5 }], + op2: [['a', { p: 0 }], ['c', { d: 0 }]] + }, + expectRight: null + })); + }); describe('discarded edit', function() { - it('edit removed directly', () => xf({ - op1: ['a', {es:[]}], - op2: ['a', {r:true}], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: null - }) - ); + it('edit removed directly', () => + xf({ + op1: ['a', { es: [] }], + op2: ['a', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })); - return it('edit inside new content throws RM_UNEXPECTED_CONTENT', () => xf({ - op1: ['a', 'b', {i: 'hi', es:[]}], - op2: ['a', {r:true}], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op1: ['a', 'b', {i:'hi'}] - }, - expect: null - }) - ); + return it('edit inside new content throws RM_UNEXPECTED_CONTENT', () => + xf({ + op1: ['a', 'b', { i: 'hi', es: [] }], + op2: ['a', { r: true }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', 'b', { i: 'hi' }] + }, + expect: null + })); }); return describe('blackhole', function() { - it('detects and errors', () => xf({ - op1: [['x', {p:0}], ['y', 'a', {d:0}]], - op2: [['x', 'a', {d:0}], ['y', {p:0}]], - conflict: { type: BLACKHOLE - }, - expect: ['x', {r:true}, 'a', {r:true}]}) ); // Also equivalent: ['x', r:true] - - it('blackhole logic does not apply when op2 removes parent', () => xf({ - // TODO: Although you wouldn't know it, since this result is very similar. - op1: [['x', {p:0}], ['y', 'xx', 'a', {d:0}]], - op2: [['x', 'a', {d:0}], ['y', {p:0}, 'xx', {r:true}]], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op2: ['y', 'xx', {r:true}] - }, - expect: ['x', {r:true}, 'a', {r:true}]}) ); // Also ok: ['x', r:true] + it('detects and errors', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]], + conflict: { type: BLACKHOLE }, + expect: ['x', { r: true }, 'a', { r: true }] + })); // Also equivalent: ['x', r:true] - it('blackhole logic still applies when op2 inserts', () => xf({ - op1: [['x', {p:0}], ['y', 'a', {i:{}}, 'b', {d:0}]], - op2: [['x', 'a', {i:{}}, 'b', {d:0}], ['y', {p:0}]], - conflict: { - type: BLACKHOLE, - op1: [['x', {p:0}], ['y', 'a', 'b', {d:0}]], - op2: [['x', 'a', 'b', {d:0}], ['y', {p:0}]] - }, - expect: ['x', {r:true}, 'a', {r:true}, 'b', {r:true}]}) ); - - it('blackholes items in lists correctly', () => xf({ - op1: [1, {p:0}, 'a', {d:0}], - op2: [[1, 'b', {d:0}], [2, {p:0}]], - conflict: { type: BLACKHOLE - }, - expect: [1, {r:true}, 'b', {r:true}]}) ); - - it('blackholes items despite scrambled pick and drop slots', () => xf({ - op1: [ [ 'a', { p: 1, d: 1 } ], [ 'x', { p: 0 } ], [ 'y', 'a', { d: 0 } ] ], - op2: [ [ 'x', 'a', { d: 0 } ], [ 'y', { p: 0 } ] ], - conflict: { - type: BLACKHOLE, - op1: [ [ 'x', { p: 0 } ], [ 'y', 'a', { d: 0 } ] ] - }, - expect: [['a', {p:0, d:0}], ['x', {r:true}, 'a', {r:true}]]}) ); + it('blackhole logic does not apply when op2 removes parent', () => + xf({ + // TODO: Although you wouldn't know it, since this result is very similar. + op1: [['x', { p: 0 }], ['y', 'xx', 'a', { d: 0 }]], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }, 'xx', { r: true }]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: ['y', 'xx', { r: true }] + }, + expect: ['x', { r: true }, 'a', { r: true }] + })); // Also ok: ['x', r:true] - it('handles chained blackholes', () => xf({ - op1: [ [ 'a', { p: 0 } ], // a->b.b, c->d.d - [ 'b', 'b', { d: 0 } ], - [ 'c', { p: 1 } ], - [ 'd', 'd', { d: 1 } ] - ], - op2: [ [ 'a', 'a', { d: 1 } ], // b->c.c, d->a.a - [ 'b', { p: 0 } ], - [ 'c', 'c', { d: 0 } ], - [ 'd', { p: 1 } ] - ], - conflict: { type: BLACKHOLE - }, + it('blackhole logic still applies when op2 inserts', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { i: {} }, 'b', { d: 0 }]], + op2: [['x', 'a', { i: {} }, 'b', { d: 0 }], ['y', { p: 0 }]], + conflict: { + type: BLACKHOLE, + op1: [['x', { p: 0 }], ['y', 'a', 'b', { d: 0 }]], + op2: [['x', 'a', 'b', { d: 0 }], ['y', { p: 0 }]] + }, + expect: ['x', { r: true }, 'a', { r: true }, 'b', { r: true }] + })); + + it('blackholes items in lists correctly', () => + xf({ + op1: [1, { p: 0 }, 'a', { d: 0 }], + op2: [[1, 'b', { d: 0 }], [2, { p: 0 }]], + conflict: { type: BLACKHOLE }, + expect: [1, { r: true }, 'b', { r: true }] + })); + + it('blackholes items despite scrambled pick and drop slots', () => + xf({ + op1: [['a', { p: 1, d: 1 }], ['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]], + conflict: { + type: BLACKHOLE, + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]] + }, + expect: [['a', { p: 0, d: 0 }], ['x', { r: true }, 'a', { r: true }]] + })); + + it('handles chained blackholes', () => + xf({ + op1: [ + ['a', { p: 0 }], // a->b.b, c->d.d + ['b', 'b', { d: 0 }], + ['c', { p: 1 }], + ['d', 'd', { d: 1 }] + ], + op2: [ + ['a', 'a', { d: 1 }], // b->c.c, d->a.a + ['b', { p: 0 }], + ['c', 'c', { d: 0 }], + ['d', { p: 1 }] + ], + conflict: { type: BLACKHOLE }, // c1: cMv(['a'], ['b', 'b']) // c2: cMv(['b'], ['c', 'c']) - expect: [['a', {r:true}, 'a', {r:true}], ['c', {r:true}, 'c', {r:true}]]}) ); + expect: [ + ['a', { r: true }, 'a', { r: true }], + ['c', { r: true }, 'c', { r: true }] + ] + })); - return it('creates conflict return values with valid slot ids', () => xf({ - op1: [['a', {p:0}], ['b', {d:0}], ['x', {p:1}], ['y', 'a', {d:1}]], - op2: [['x', 'a', {d:0}], ['y', {p:0}]], - conflict: { - type: BLACKHOLE, - op1: [['x', {p:0}], ['y', 'a', {d:0}]] - }, - expect: [['a', {p:0}], ['b', {d:0}], ['x', {r:true}, 'a', {r:true}]]}) ); + return it('creates conflict return values with valid slot ids', () => + xf({ + op1: [ + ['a', { p: 0 }], + ['b', { d: 0 }], + ['x', { p: 1 }], + ['y', 'a', { d: 1 }] + ], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]], + conflict: { + type: BLACKHOLE, + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]] + }, + expect: [ + ['a', { p: 0 }], + ['b', { d: 0 }], + ['x', { r: true }, 'a', { r: true }] + ] + })); + }); }); -}); - describe('transform-old', function() { it('foo', () => xf({ op1: [ - ['x', ['a', {p:0}], ['b', {d:0}]], - ['y', ['a', {p:1}], ['b', {d:1}]] + ['x', ['a', { p: 0 }], ['b', { d: 0 }]], + ['y', ['a', { p: 1 }], ['b', { d: 1 }]] ], - op2: ['x', {r:true}], - expect: ['y', ['a', {p:0}], ['b', {d:0}]]}) - ); + op2: ['x', { r: true }], + expect: ['y', ['a', { p: 0 }], ['b', { d: 0 }]] + })); // it 'hard', -> // op1: ['x', [1, r:true], [2, r:true, es:['hi']]] # Edit at index 4 originally. @@ -1640,276 +1993,320 @@ describe('json1', function() { // expect: describe('object edits', () => - it('can reparent with some extra junk', () => xf({ - op1: [['x', {p:0}], ['y', {d:0}]], - op2: [ - ['_a', {d:1}], - ['_x', {d:0}], - ['x', {p:0}, 'a', {p:1}] - ], - expectLeft: [['_x', {p:0}], ['y', {d:0}]], - expectRight: null - }) - ) - ); // the object was moved fair and square. + it('can reparent with some extra junk', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [ + ['_a', { d: 1 }], + ['_x', { d: 0 }], + ['x', { p: 0 }, 'a', { p: 1 }] + ], + expectLeft: [['_x', { p: 0 }], ['y', { d: 0 }]], + expectRight: null + }))); // the object was moved fair and square. describe('deletes', function() { - - it.skip('delete parent of a move', () => xf({ - // The current logic of transform actually just burns everything (in a - // consistant way of course). I'm not sure if this is better or worse - - // basically we'd be saying that if a move could end up in one of two places, - // put it in the place where it won't be killed forever. But that introduces new - // complexity, so I'm going to skip this for now. - - // x.a -> a, delete x - op1: [['x', {r:true}, 'a', {p:0}], ['z', {d:0}]], - // x.a -> x.b. - op2: ['x', ['a', {p:0}], ['b', {d:0}]], - expect: [['x', {r:true}, 'b', {p:0}], ['z', {d:0}]]}) ); // TODO: It would be better to do this in both cases. - //expectRight: ['x', r:true] + it.skip('delete parent of a move', () => + xf({ + // The current logic of transform actually just burns everything (in a + // consistant way of course). I'm not sure if this is better or worse - + // basically we'd be saying that if a move could end up in one of two places, + // put it in the place where it won't be killed forever. But that introduces new + // complexity, so I'm going to skip this for now. + + // x.a -> a, delete x + op1: [['x', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]], + // x.a -> x.b. + op2: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], + expect: [['x', { r: true }, 'b', { p: 0 }], ['z', { d: 0 }]] + })); // TODO: It would be better to do this in both cases. + //expectRight: ['x', r:true] return it('awful delete nonsense', function() { xf({ - op1: [['x', {r:true}], ['y', {i:'hi'}]], // delete doc.x, insert doc.y - op2: [['x', 'a', {p:0}], ['y', {d:0}]], // move doc.x.a -> doc.y - expect: [['x', {r:true}], ['y', {r:true, i:'hi'}]]}); // del doc.x and doc.y, insert doc.y + op1: [['x', { r: true }], ['y', { i: 'hi' }]], // delete doc.x, insert doc.y + op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], // move doc.x.a -> doc.y + expect: [['x', { r: true }], ['y', { r: true, i: 'hi' }]] + }); // del doc.x and doc.y, insert doc.y xf({ - op1: [['x', 'a', {p:0}], ['y', {d:0}]], // x.a -> y - op2: [['x', {r:true}], ['y', {i:'hi'}]], // delete x, ins y + op1: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], // x.a -> y + op2: [['x', { r: true }], ['y', { i: 'hi' }]], // delete x, ins y expect: null }); return xf({ - op1: [10, {r:true}], - op2: [[5, {d:0}], [10, 1, {p:0}]], - expect: [[5, {r:true}], [11, {r:true}]]}); + op1: [10, { r: true }], + op2: [[5, { d: 0 }], [10, 1, { p: 0 }]], + expect: [[5, { r: true }], [11, { r: true }]] + }); + }); }); - }); - // And how do those indexes interact with pick / drop operations?? - + // And how do those indexes interact with pick / drop operations?? describe('swap', function() { const swap = [ - ['a', {p:0}, 'b', {p:1}], - ['b', {d:1}, 'a', {d:0}] + ['a', { p: 0 }, 'b', { p: 1 }], + ['b', { d: 1 }, 'a', { d: 0 }] ]; - it('noop vs swap', () => xf({ - op1: null, - op2: swap, - expect: null - }) - ); + it('noop vs swap', () => + xf({ + op1: null, + op2: swap, + expect: null + })); - return it('can swap two edits', () => xf({ - op1: ['a', {es:['a edit']}, 'b', {es:['b edit']}], - op2: swap, - expect: ['b', {es:['b edit']}, 'a', {es:['a edit']}]}) ); - }); + return it('can swap two edits', () => + xf({ + op1: ['a', { es: ['a edit'] }, 'b', { es: ['b edit'] }], + op2: swap, + expect: ['b', { es: ['b edit'] }, 'a', { es: ['a edit'] }] + })); + }); describe('lists', function() { it('can rewrite simple list indexes', function() { xf({ - op1: [10, {es:['edit']}], - op2: [0, {i:'oh hi'}], - expect: [11, {es:['edit']}]}); + op1: [10, { es: ['edit'] }], + op2: [0, { i: 'oh hi' }], + expect: [11, { es: ['edit'] }] + }); xf({ - op1: [10, {r:true}], - op2: [0, {i:'oh hi'}], - expect: [11, {r:true}]}); + op1: [10, { r: true }], + op2: [0, { i: 'oh hi' }], + expect: [11, { r: true }] + }); return xf({ - op1: [10, {i:{}}], - op2: [0, {i:'oh hi'}], - expect: [11, {i:{}}]}); - }); + op1: [10, { i: {} }], + op2: [0, { i: 'oh hi' }], + expect: [11, { i: {} }] + }); + }); - it('can change the root from an object to a list', () => xf({ - op1: ['a', {es:['hi']}], - op2: [{i:[], r:true}, [0, {d:0}], ['a', {p:0}]], - expect: [0, {es:['hi']}]}) ); + it('can change the root from an object to a list', () => + xf({ + op1: ['a', { es: ['hi'] }], + op2: [{ i: [], r: true }, [0, { d: 0 }], ['a', { p: 0 }]], + expect: [0, { es: ['hi'] }] + })); - it('can handle adjacent drops', () => xf({ - op1: [[11, {i:1}], [12, {i:2}], [13, {i:3}]], - op2: [0, {r:true}], - expect: [[10, {i:1}], [11, {i:2}], [12, {i:3}]]}) ); + it('can handle adjacent drops', () => + xf({ + op1: [[11, { i: 1 }], [12, { i: 2 }], [13, { i: 3 }]], + op2: [0, { r: true }], + expect: [[10, { i: 1 }], [11, { i: 2 }], [12, { i: 3 }]] + })); - it('fixes drop indexes correctly 1', () => xf({ - op1: [[0, {r:true}], [1, {i:'hi'}]], - op2: [1, {r:true}], - expect: [0, {r:true, i:'hi'}]}) ); + it('fixes drop indexes correctly 1', () => + xf({ + op1: [[0, { r: true }], [1, { i: 'hi' }]], + op2: [1, { r: true }], + expect: [0, { r: true, i: 'hi' }] + })); it('list drop vs delete uses the correct result index', function() { xf({ - op1: [2, {i:'hi'}], - op2: [2, {r:true}], - expect: [2, {i:'hi'}]}); + op1: [2, { i: 'hi' }], + op2: [2, { r: true }], + expect: [2, { i: 'hi' }] + }); return xf({ - op1: [3, {i:'hi'}], - op2: [2, {r:true}], - expect: [2, {i:'hi'}]}); - }); + op1: [3, { i: 'hi' }], + op2: [2, { r: true }], + expect: [2, { i: 'hi' }] + }); + }); - it('list drop vs drop uses the correct result index', () => xf({ - op1: [2, {i:'hi'}], - op2: [2, {i:'other'}], - expectLeft: [2, {i:'hi'}], - expectRight: [3, {i:'hi'}]}) ); + it('list drop vs drop uses the correct result index', () => + xf({ + op1: [2, { i: 'hi' }], + op2: [2, { i: 'other' }], + expectLeft: [2, { i: 'hi' }], + expectRight: [3, { i: 'hi' }] + })); it('list drop vs delete and drop', function() { xf({ - op1: [2, {i:'hi'}], - op2: [2, {r:true, i:'other'}], - expectLeft: [2, {i:'hi'}], - expectRight: [3, {i:'hi'}]}); + op1: [2, { i: 'hi' }], + op2: [2, { r: true, i: 'other' }], + expectLeft: [2, { i: 'hi' }], + expectRight: [3, { i: 'hi' }] + }); xf({ - op1: [3, {i:'hi'}], - op2: [[2, {r:true}], [3, {i:'other'}]], - expect: [2, {i:'hi'}]}); + op1: [3, { i: 'hi' }], + op2: [[2, { r: true }], [3, { i: 'other' }]], + expect: [2, { i: 'hi' }] + }); return xf({ - op1: [4, {i:'hi'}], - op2: [[2, {r:true}], [3, {i:'other'}]], - expectLeft: [3, {i:'hi'}], - expectRight: [4, {i:'hi'}]}); - }); + op1: [4, { i: 'hi' }], + op2: [[2, { r: true }], [3, { i: 'other' }]], + expectLeft: [3, { i: 'hi' }], + expectRight: [4, { i: 'hi' }] + }); + }); it('list delete vs drop', function() { xf({ - op1: [1, {r:true}], - op2: [2, {i:'hi'}], - expect: [1, {r:true}]}); + op1: [1, { r: true }], + op2: [2, { i: 'hi' }], + expect: [1, { r: true }] + }); xf({ - op1: [2, {r:true}], - op2: [2, {i:'hi'}], - expect: [3, {r:true}]}); + op1: [2, { r: true }], + op2: [2, { i: 'hi' }], + expect: [3, { r: true }] + }); return xf({ - op1: [3, {r:true}], - op2: [2, {i:'hi'}], - expect: [4, {r:true}]}); - }); + op1: [3, { r: true }], + op2: [2, { i: 'hi' }], + expect: [4, { r: true }] + }); + }); it('list delete vs delete', () => xf({ - op1: [1, {r:true}], - op2: [1, {r:true}], + op1: [1, { r: true }], + op2: [1, { r: true }], expect: null - }) - ); // It was already deleted. - - it('fixes drop indexes correctly 2', () => xf({ - op1: [[0, {r:true}], [1, {i:'hi'}]], - op2: [2, {r:true}], // Shouldn't affect the op. - expect: [[0, {r:true}], [1, {i:'hi'}]]}) ); - - it('insert vs delete parent', () => xf({ - op1: [2, 'x', {i:'hi'}], - op2: [2, {r:true}], - conflict: { type: RM_UNEXPECTED_CONTENT - }, - expect: null - }) - ); + })); // It was already deleted. + + it('fixes drop indexes correctly 2', () => + xf({ + op1: [[0, { r: true }], [1, { i: 'hi' }]], + op2: [2, { r: true }], // Shouldn't affect the op. + expect: [[0, { r: true }], [1, { i: 'hi' }]] + })); + + it('insert vs delete parent', () => + xf({ + op1: [2, 'x', { i: 'hi' }], + op2: [2, { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })); it('transforms against inserts in my own list', () => - xf({ //[0,1,2,3] -> [a,0,b,1,2,3...] - op1: [[0, {i:'a'}], [2, {i:'b'}]], - op2: [1, {r:true}], - expect: [[0, {i:'a'}], [2, {i:'b'}]]}) - ); + xf({ + //[0,1,2,3] -> [a,0,b,1,2,3...] + op1: [[0, { i: 'a' }], [2, { i: 'b' }]], + op2: [1, { r: true }], + expect: [[0, { i: 'a' }], [2, { i: 'b' }]] + })); - it('vs cancelled op2 drop', () => xf({ - doc: {x:{a:'x.a'}, y:['a','b','c']}, - op1: [['x', {r:true}], ['y', 3, {i:5}]], - op2: [['x', 'a', {p:0}], ['y', 2, {d:0}]], - expect: [['x', {r:true}], ['y', [2, {r:true}], [3, {i:5}]]]}) ); - - it('vs cancelled op1 drop', () => xf({ - op1: [['x', {p:0}], ['y', [3, {d:0}], [4, {i:5}]]], - op2: ['x', {r:true}], - expect: ['y', 3, {i:5}]}) ); - - it('vs cancelled op1 pick', () => xf({ - doc: Array.from('abcdefg'), - op1: [[1, {p:0}], [4, {r:true, i:4}], [6, {d:0}]], - op2: [1, {r:true}], - expect: [[3, {r:true}], [4, {i:4}]]}) ); - - it('xxxxx 1', () => diamond({ // TODO Regression. - doc: Array.from('abcdef'), - op1: [[1, {p:0, i:'AAA'}], [3, {i:'BBB'}], [5, {d:0}]], - op2: [1, {r:true}]}) ); - - return it('xxxxx 2', () => diamond({ - doc: Array.from('abcdef'), - op1: [[1, {p:0, i:'AAA'}], [3, {d:0}], [5, {i:'CCC'}]], - op2: [1, {r:true}]}) ); - }); + it('vs cancelled op2 drop', () => + xf({ + doc: { x: { a: 'x.a' }, y: ['a', 'b', 'c'] }, + op1: [['x', { r: true }], ['y', 3, { i: 5 }]], + op2: [['x', 'a', { p: 0 }], ['y', 2, { d: 0 }]], + expect: [['x', { r: true }], ['y', [2, { r: true }], [3, { i: 5 }]]] + })); + it('vs cancelled op1 drop', () => + xf({ + op1: [['x', { p: 0 }], ['y', [3, { d: 0 }], [4, { i: 5 }]]], + op2: ['x', { r: true }], + expect: ['y', 3, { i: 5 }] + })); - return describe('edit', function() { - it('transforms edits by one another', () => xf({ - op1: [1, {es:[2, 'hi']}], - op2: [1, {es:['yo']}], - expect: [1, {es:[4, 'hi']}]}) ); - - it('copies in ops otherwise', () => xf({ - op1: ['x', {e:{position:2, text:'wai'}, et:'simple'}], - op2: ['y', {r:true}], - expect: ['x', {e:{position:2, text:'wai'}, et:'simple'}]}) ); - - it('allows edits at the root', () => xf({ - op1: [{e:{position:2, text:'wai'}, et:'simple'}], - op2: [{e:{position:0, text:'omg'}, et:'simple'}], - expect: [{e:{position:5, text:'wai'}, et:'simple'}]}) ); - - it('applies edits in the right order', () => xf({ - // Edits happen *after* the drop phase. - op1: [1, {es:[2, 'hi']}], - op2: [[1, {i:{}}], [2, {es:['yo']}]], - expect: [2, {es:[4, 'hi']}]}) ); - - return it('an edit on a deleted object goes away', () => xf({ - op1: [1, {es:[2, 'hi']}], - op2: [1, {r:"yo"}], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op2: [1, {r:true}] - }, // .... It'd be better if this copied the remove. - expect: null - }) - ); + it('vs cancelled op1 pick', () => + xf({ + doc: Array.from('abcdefg'), + op1: [[1, { p: 0 }], [4, { r: true, i: 4 }], [6, { d: 0 }]], + op2: [1, { r: true }], + expect: [[3, { r: true }], [4, { i: 4 }]] + })); + + it('xxxxx 1', () => + diamond({ + // TODO Regression. + doc: Array.from('abcdef'), + op1: [[1, { p: 0, i: 'AAA' }], [3, { i: 'BBB' }], [5, { d: 0 }]], + op2: [1, { r: true }] + })); + + return it('xxxxx 2', () => + diamond({ + doc: Array.from('abcdef'), + op1: [[1, { p: 0, i: 'AAA' }], [3, { d: 0 }], [5, { i: 'CCC' }]], + op2: [1, { r: true }] + })); }); - }); - // TODO Numbers + return describe('edit', function() { + it('transforms edits by one another', () => + xf({ + op1: [1, { es: [2, 'hi'] }], + op2: [1, { es: ['yo'] }], + expect: [1, { es: [4, 'hi'] }] + })); + it('copies in ops otherwise', () => + xf({ + op1: ['x', { e: { position: 2, text: 'wai' }, et: 'simple' }], + op2: ['y', { r: true }], + expect: ['x', { e: { position: 2, text: 'wai' }, et: 'simple' }] + })); -// ***** Test cases found by the fuzzer which have caused issues - return describe('fuzzer tests', function() { - it('asdf', () => apply({ - doc: { the: '', Twas: 'the' }, - op: [ 'the', { es: [] } ], - expect: { the: '', Twas: 'the' }}) ); - - it('does not duplicate list items from edits', () => apply({ - doc: ['eyes'], - op: [ 0, { es: [] } ], - expect: ['eyes']}) ); - - it('will edit the root document', () => apply({ - doc: '', - op: [{es:[]}], - expect: '' - }) - ); + it('allows edits at the root', () => + xf({ + op1: [{ e: { position: 2, text: 'wai' }, et: 'simple' }], + op2: [{ e: { position: 0, text: 'omg' }, et: 'simple' }], + expect: [{ e: { position: 5, text: 'wai' }, et: 'simple' }] + })); + + it('applies edits in the right order', () => + xf({ + // Edits happen *after* the drop phase. + op1: [1, { es: [2, 'hi'] }], + op2: [[1, { i: {} }], [2, { es: ['yo'] }]], + expect: [2, { es: [4, 'hi'] }] + })); + + return it('an edit on a deleted object goes away', () => + xf({ + op1: [1, { es: [2, 'hi'] }], + op2: [1, { r: 'yo' }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: [1, { r: true }] + }, // .... It'd be better if this copied the remove. + expect: null + })); + }); + }); + + // TODO Numbers + + // ***** Test cases found by the fuzzer which have caused issues + return describe('fuzzer tests', function() { + it('asdf', () => + apply({ + doc: { the: '', Twas: 'the' }, + op: ['the', { es: [] }], + expect: { the: '', Twas: 'the' } + })); + + it('does not duplicate list items from edits', () => + apply({ + doc: ['eyes'], + op: [0, { es: [] }], + expect: ['eyes'] + })); + + it('will edit the root document', () => + apply({ + doc: '', + op: [{ es: [] }], + expect: '' + })); // ------ These have nothing to do with apply. TODO: Move them out of this grouping. @@ -1917,346 +2314,478 @@ describe('json1', function() { // TODO: Do this for all combinations. diamond({ doc: Array.from('abcde'), - op1: [ [ 0, { p: 0 } ], [ 1, { d: 0 } ] ], - op2: [ [ 0, { p: 0 } ], [ 4, { d: 0 } ] ]}) - ); - - it('shuffles lists correctly', () => xf({ - op1: [ [ 0, { p: 0 } ], [ 1, { d: 0 } ] ], - op2: [ [ 0, { p: 0 } ], [ 10, { d: 0 } ] ], - expectLeft: [ [ 1, { d: 0 } ], [ 10, { p: 0 } ] ], - expectRight: null - }) - ); + op1: [[0, { p: 0 }], [1, { d: 0 }]], + op2: [[0, { p: 0 }], [4, { d: 0 }]] + })); + + it('shuffles lists correctly', () => + xf({ + op1: [[0, { p: 0 }], [1, { d: 0 }]], + op2: [[0, { p: 0 }], [10, { d: 0 }]], + expectLeft: [[1, { d: 0 }], [10, { p: 0 }]], + expectRight: null + })); it('inserts before edits', function() { xf({ - op1: [0, 'x', {i:5}], - op2: [0, {i:35}], - expect: [1, 'x', {i:5}]}); + op1: [0, 'x', { i: 5 }], + op2: [0, { i: 35 }], + expect: [1, 'x', { i: 5 }] + }); return xf({ - op1: [0, {es:[]}], - op2: [0, {i:35}], - expect: [1, {es:[]}]}); - }); + op1: [0, { es: [] }], + op2: [0, { i: 35 }], + expect: [1, { es: [] }] + }); + }); - it('duplicates become noops in a list', - () => xf({ - op1: [0,{"p":0,"d":0}], - op2: [0,{"p":0,"d":0}], - expectLeft: [0,{"p":0,"d":0}], // This is a bit weird. - expectRight: null - }) , + it( + 'duplicates become noops in a list', + () => + xf({ + op1: [0, { p: 0, d: 0 }], + op2: [0, { p: 0, d: 0 }], + expectLeft: [0, { p: 0, d: 0 }], // This is a bit weird. + expectRight: null + }), - () => xf({ - op1: [0, {r:true, i:'a'}], - op2: [0, {i:'b'}], - expectLeft: [[0, {i:'a'}], [1, {r:true}]], - expectRight: [1, {r:true, i:'a'}]}) , + () => + xf({ + op1: [0, { r: true, i: 'a' }], + op2: [0, { i: 'b' }], + expectLeft: [[0, { i: 'a' }], [1, { r: true }]], + expectRight: [1, { r: true, i: 'a' }] + }), - () => xf({ - op1: [0, {r:true, i:5}], - op2: [0, {r:true}], - expect: [0, {i:5}]}) ); + () => + xf({ + op1: [0, { r: true, i: 5 }], + op2: [0, { r: true }], + expect: [0, { i: 5 }] + }) + ); it('p1 pick descends correctly', function() { xf({ - op1: [2, {r:true}, 1, {es:['hi']}], - op2: [3, 1, {r:true}], + op1: [2, { r: true }, 1, { es: ['hi'] }], + op2: [3, 1, { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT, - op1: [2, 1, {es:['hi']}] + op1: [2, 1, { es: ['hi'] }] }, - expect: [2, {r:true}]}); + expect: [2, { r: true }] + }); return xf({ - op1: [[2, {r:true}, 1, {es:['hi']}], [3, 1, {r:true}]], - op2: [3, 2, {r:true}], + op1: [[2, { r: true }, 1, { es: ['hi'] }], [3, 1, { r: true }]], + op2: [3, 2, { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT, - op1: [2, 1, {es:['hi']}] + op1: [2, 1, { es: ['hi'] }] }, - expect: [[2, {r:true}], [3, 1, {r:true}]]}); - }); + expect: [[2, { r: true }], [3, 1, { r: true }]] + }); + }); + + it('transforms picks correctly', () => + xf({ + op1: [1, 1, { r: true }], + op2: [0, { p: 0, d: 0 }], + expect: [1, 1, { r: true }] + })); + + it('pick & drop vs insert after the picked item', () => + xf({ + op1: [0, { p: 0, d: 0 }], // Remove / insert works the same. + op2: [1, { i: 'hi' }], + expectLeft: [0, { p: 0, d: 0 }], + expectRight: [[0, { p: 0 }], [1, { d: 0 }]] + })); + + it('pick same item vs shuffle list', () => + xf({ + op1: [1, ['x', { p: 0 }], ['y', { d: 0 }]], + op2: [1, { d: 0 }, 'x', { p: 0 }], + expectLeft: [1, { p: 0 }, 'y', { d: 0 }], + expectRight: null + })); + + it('remove the same item in a list', () => + xf({ + op1: [0, { r: true }], + op2: [0, { r: true }], + expect: null + })); + + it('rm vs hold item', () => + xf({ + op1: [0, { r: true }], + op2: [0, { p: 0, d: 0 }], + expect: [0, { r: true }] + })); + + it('moves child elements correctly', () => + xf({ + doc: ['a', [0, 10, 20], 'c'], + op1: [1, 0, { p: 0, d: 0 }], + op2: [[1, { d: 0 }], [2, { p: 0 }]], + expect: [2, 0, { d: 0, p: 0 }] + })); + + it('moves list indexes', () => + xf({ + doc: [[], 'b', 'c'], + op1: [[0, 'hi', { d: 0 }], [1, { p: 0 }]], + op2: [[0, { p: 0 }], [20, { d: 0 }]], + expect: [[0, { p: 0 }], [19, 'hi', { d: 0 }]] + })); + + it('insert empty string vs insert null', () => + xf({ + doc: undefined, + op1: [{ i: 'hi' }], + op2: [{ i: null }], + conflict: { type: DROP_COLLISION }, + expectLeft: [{ r: true, i: 'hi' }], + expectRight: null + })); + + it('move vs emplace', () => + xf({ + doc: ['a', 'b'], + op1: [[0, { p: 0 }], [1, { d: 0 }]], + op2: [1, { p: 0, d: 0 }], + expectLeft: [0, { p: 0, d: 0 }], + expectRight: [[0, { p: 0 }], [1, { d: 0 }]] + })); + + it('rm chases a subdocument that was moved out', () => + xf({ + doc: [['aaa']], + op1: [0, { r: true }], + op2: [0, { d: 0 }, 0, { p: 0 }], // Valid because lists. + expect: [[0, { r: true }], [1, { r: true }]] + })); + + it('colliding drops', () => + xf({ + doc: ['a', 'b', {}], + op1: [[0, { p: 0 }], [1, 'x', { d: 0 }]], // -> ['b', x:'a'] + op2: [1, { p: 0 }, 'x', { d: 0 }], // -> ['a', x:'b'] + conflict: { type: DROP_COLLISION }, + expectLeft: [[0, { p: 0 }, 'x', { d: 0 }], [1, 'x', { r: true }]], + expectRight: [0, { r: true }] + })); + + it('transform crash', () => + xf({ + op1: [['the', { r: true, d: 0 }], ['whiffling', { p: 0 }]], + op2: ['the', { p: 0, d: 0 }], + expect: [['the', { d: 0, r: true }], ['whiffling', { p: 0 }]] + })); + + it('transforms drops when the parent is moved by a remove', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], + op2: ['a', 0, { r: 1 }], + expect: [['a', { p: 0 }], ['b', { d: 0 }, 0, { i: 2 }]] + })); + + it('transforms drops when the parent is moved by a drop', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], + op2: ['a', 0, { i: 1 }], + expect: [['a', { p: 0 }], ['b', { d: 0 }, 2, { i: 2 }]] + })); + + it('transforms conflicting drops obfuscated by a move', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], + op2: ['a', 1, { i: 1 }], + expectLeft: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], + expectRight: [['a', { p: 0 }], ['b', { d: 0 }, 2, { i: 2 }]] + })); - it('transforms picks correctly', () => xf({ - op1: [1, 1, {r:true}], - op2: [0, {p:0, d:0}], - expect: [1, 1, {r:true}]}) ); - - it('pick & drop vs insert after the picked item', () => xf({ - op1: [0, {p:0,d:0}], // Remove / insert works the same. - op2: [1, {i:"hi"}], - expectLeft: [0, {p:0,d:0}], - expectRight: [[0, {p:0}], [1, {d:0}]]}) ); - - it('pick same item vs shuffle list', () => xf({ - op1: [1, ['x', {p:0}], ['y', {d:0}]], - op2: [1, {d:0}, 'x', {p:0}], - expectLeft: [1, {p:0}, 'y', {d:0}], - expectRight: null - }) - ); - - it('remove the same item in a list', () => xf({ - op1: [ 0, { r: true } ], - op2: [ 0, { r: true } ], - expect: null - }) - ); - - it('rm vs hold item', () => xf({ - op1: [ 0, { r: true } ], - op2: [ 0, { p: 0, d: 0 } ], - expect: [ 0, { r: true } ]}) ); - - it('moves child elements correctly', () => xf({ - doc: ['a', [0,10,20], 'c'], - op1: [ 1, 0, { p: 0, d: 0 } ], - op2: [ [ 1, { d: 0 } ], [ 2, { p: 0 } ] ], - expect: [ 2, 0, { d:0, p:0 } ]}) ); - - it('moves list indexes', () => xf({ - doc: [[], 'b', 'c'], - op1: [ [ 0, 'hi', { d: 0 } ], [ 1, { p: 0 } ] ], - op2: [ [ 0, { p: 0 } ], [ 20, { d: 0 } ] ], - expect: [[0, {p:0}], [19, 'hi', {d:0}]]}) ); - - it('insert empty string vs insert null', () => xf({ - doc: undefined, - op1: [{i:'hi'}], - op2: [{i:null}], - conflict: { type: DROP_COLLISION - }, - expectLeft: [{r:true, i:'hi'}], - expectRight: null - }) - ); - - it('move vs emplace', () => xf({ - doc: ['a', 'b'], - op1: [[0, {p:0}], [1, {d:0}]], - op2: [1, {p:0, d:0}], - expectLeft: [0, {p:0, d:0}], - expectRight: [[0, {p:0}], [1, {d:0}]]}) ); - - it('rm chases a subdocument that was moved out', () => xf({ - doc: [ [ 'aaa' ] ], - op1: [ 0, { r: true } ], - op2: [ 0, { d: 0 }, 0, { p: 0 } ], // Valid because lists. - expect: [[0, {r:true}], [1, {r:true}]]}) ); - - it('colliding drops', () => xf({ - doc: [ 'a', 'b', {} ], - op1: [[0, {p:0}], [1, 'x', {d:0}]], // -> ['b', x:'a'] - op2: [1, {p:0}, 'x', {d:0}], // -> ['a', x:'b'] - conflict: { type: DROP_COLLISION - }, - expectLeft: [[0, {p:0}, 'x', {d:0}], [1, 'x', {r:true}]], - expectRight: [0, {r:true}]}) ); - - it('transform crash', () => xf({ - op1: [ [ 'the', { r: true, d: 0 } ], [ 'whiffling', { p: 0 } ] ], - op2: [ 'the', { p: 0, d: 0 } ], - expect: [ [ 'the', { d: 0, r: true } ], [ 'whiffling', { p: 0 } ] ]}) ); - - it('transforms drops when the parent is moved by a remove', () => xf({ - op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]], - op2: ['a', 0, {r:1}], - expect: [['a', {p:0}], ['b', {d:0}, 0, {i:2}]]}) ); - - it('transforms drops when the parent is moved by a drop', () => xf({ - op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]], - op2: ['a', 0, {i:1}], - expect: [['a', {p:0}], ['b', {d:0}, 2, {i:2}]]}) ); - - it('transforms conflicting drops obfuscated by a move', () => xf({ - op1: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]], - op2: ['a', 1, {i:1}], - expectLeft: [['a', {p:0}], ['b', {d:0}, 1, {i:2}]], - expectRight: [['a', {p:0}], ['b', {d:0}, 2, {i:2}]]}) ); - - it('transforms edits when the parent is moved', () => xf({ - op1: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 1, 'xxx' ] } ] ], - op2: [ 'x', { es: [ {d: 1}, 'Z' ] } ], - expectLeft: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 'xxx' ] } ] ], - expectRight: [ [ 'x', { p: 0 } ], [ 'y', { d: 0, es: [ 1, 'xxx' ] } ] ]}) ); - - it('xf lots', () => xf({ - op1: [['a', {p:0}], ['b', {d:0, es:['hi']}]], - op2: [['a', {p:0}], ['c', {d:0}]], - expectLeft: [['b', {d:0, es:['hi']}], ['c', {p:0}]], - expectRight: ['c', {es:['hi']}]}) ); - - it('inserts are moved back by the other op', () => xf({ - op1: [['a', {p:0}], ['b', {d:0}, 'x', {i:'hi'}]], - op2: [['a', {p:0}], ['c', {d:0}]], - expectLeft: [['b', {d:0}, 'x', {i:'hi'}], ['c', {p:0}]], - expectRight: ['c', 'x', {i:'hi'}]}) ); - - it('more awful edit moves', () => xf({ - op1: [['a', {p:0}], ['c', {d:0}, 'x', {es:[]}]], - op2: ['a', ['b', {d:0}], ['x', {p:0}]], - expect: [['a', {p:0}], ['c', {d:0}, 'b', {es:[]}]]}) ); - - it('inserts null', () => xf({ - op1: [ 'x', 'a', { i: null } ], - op2: [ [ 'x', { p: 0 } ], [ 'y', { d: 0 } ] ], - expect: [ 'y', 'a', { i: null } ]}) ); - - it('preserves local insert if both sides delete', () => xf({ - op1: [ { i: {}, r: true }, 'x', { i: 'yo' } ], - op2: [ { r: true } ], - expect: [ { i: {} }, 'x', { i: 'yo' } ]}) ); - - it('handles insert/delete vs move', () => xf({ - op1: [ 'a', { i: {}, r: true }, 'x', { i: 'yo' } ], - op2: [ [ 'a', { p: 0 } ], [ 'b', { d: 0 } ] ], - expect: [ [ 'a', { i: {} }, 'x', { i: 'yo' } ], [ 'b', { r: true }, ] ]}) ); - - it('insert pushes edit target', () => xf({ - op1: [[ 0, { i: "yo" } ], [ 1, 'a', { es: [] }]], - op2: [0, [ 'a', { p: 0 } ], [ 'b', { d: 0 } ]], - expect: [[0, { i: 'yo' }], [1, 'b', { es: [] }]]}) ); + it('transforms edits when the parent is moved', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0, es: [1, 'xxx'] }]], + op2: ['x', { es: [{ d: 1 }, 'Z'] }], + expectLeft: [['x', { p: 0 }], ['y', { d: 0, es: ['xxx'] }]], + expectRight: [['x', { p: 0 }], ['y', { d: 0, es: [1, 'xxx'] }]] + })); + + it('xf lots', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0, es: ['hi'] }]], + op2: [['a', { p: 0 }], ['c', { d: 0 }]], + expectLeft: [['b', { d: 0, es: ['hi'] }], ['c', { p: 0 }]], + expectRight: ['c', { es: ['hi'] }] + })); + + it('inserts are moved back by the other op', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }, 'x', { i: 'hi' }]], + op2: [['a', { p: 0 }], ['c', { d: 0 }]], + expectLeft: [['b', { d: 0 }, 'x', { i: 'hi' }], ['c', { p: 0 }]], + expectRight: ['c', 'x', { i: 'hi' }] + })); + + it('more awful edit moves', () => + xf({ + op1: [['a', { p: 0 }], ['c', { d: 0 }, 'x', { es: [] }]], + op2: ['a', ['b', { d: 0 }], ['x', { p: 0 }]], + expect: [['a', { p: 0 }], ['c', { d: 0 }, 'b', { es: [] }]] + })); + + it('inserts null', () => + xf({ + op1: ['x', 'a', { i: null }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', 'a', { i: null }] + })); + + it('preserves local insert if both sides delete', () => + xf({ + op1: [{ i: {}, r: true }, 'x', { i: 'yo' }], + op2: [{ r: true }], + expect: [{ i: {} }, 'x', { i: 'yo' }] + })); + + it('handles insert/delete vs move', () => + xf({ + op1: ['a', { i: {}, r: true }, 'x', { i: 'yo' }], + op2: [['a', { p: 0 }], ['b', { d: 0 }]], + expect: [['a', { i: {} }, 'x', { i: 'yo' }], ['b', { r: true }]] + })); + + it('insert pushes edit target', () => + xf({ + op1: [[0, { i: 'yo' }], [1, 'a', { es: [] }]], + op2: [0, ['a', { p: 0 }], ['b', { d: 0 }]], + expect: [[0, { i: 'yo' }], [1, 'b', { es: [] }]] + })); it('composes simple regression', function() { compose({ - op1: [ 0, { p: 0, d: 0 } ], - op2: [ { r: true } ], - expect: [ { r: true }, 0, { r: true } ]}); + op1: [0, { p: 0, d: 0 }], + op2: [{ r: true }], + expect: [{ r: true }, 0, { r: true }] + }); return compose({ - op1: [ 'a', 1, { r: true } ], - op2: [ 'a', { r: true } ], - expect: [ 'a', { r: true }, 1, { r: true } ]}); - }); + op1: ['a', 1, { r: true }], + op2: ['a', { r: true }], + expect: ['a', { r: true }, 1, { r: true }] + }); + }); - it('ignores op2 inserts for index position after op1 insert', () => xf({ - op1: [ { r:true, i: [] }, 0, { i: '' } ], - op2: [ 0, { i: 0 } ], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op1: [{r:true}] - }, - expect: [ { r: true, i: [] }, 0, { r:true, i: '' } ]}) ); - - it('edit moved inside a removed area should be removed', () => xf({ - op1: [[ 0, { r: true } ], [ 2, { es: [ ] } ]], - op2: [[ 0, 'x', { d: 0 } ], [ 3, { p: 0 } ]], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op1: [0, {r:true}] - }, - expect: [ 0, { r: true }, 'x', {r:true} ]}) ); - - it('advances indexes correctly with mixed numbers', () => xf({ - op1: [ [ 'x', [ 0, { p: 0 } ], [ 1, { d: 1 } ] ], [ 'y', { p: 1 } ], [ 'zzz', { d: 0 } ] ], - op2: [ [ 'x', 2, { i: 'hi' } ], [ 'y', { p: 0 } ], [ 'z', { d: 0 } ] ], - expectLeft: [ [ 'x', [ 0, { p: 1 } ], [ 1, { d: 0 } ] ], [ 'z', { p: 0 } ], [ 'zzz', { d: 1 } ] ], - expectRight: [ [ 'x', 0, { p: 0 } ], [ 'zzz', { d: 0 } ] ]}) ); - - it('handles index positions past cancelled drops 1', () => xf({ - op1: [ 0, { r: true, i: [ '' ] } ], - op2: [ [ 0, { p: 0, d: 0 } ], [ 1, { i: 23 } ] ], - expectLeft: [ 0, { r: true, i: [ '' ] } ], - expectRight: [ [ 0, { r: true } ], [ 1, { i: [ '' ] } ] ]}) ); - - it('handles index positions past cancelled drops 2', () => xf({ - // This looks more complicated, but its a simpler version of the above test. - op1: [ [ 'a', { r: true } ], [ 'b', 0, { i: 'hi' } ] ], - op2: [ [ 'a', { p: 0 } ], [ 'b', [ 0, { d: 0 } ], [ 1, { i: 'yo' } ] ] ], - expectLeft: [ 'b', 0, { i: 'hi', r: true } ], - expectRight: [ 'b', [ 0, { r: true } ], [ 1, { i: 'hi' } ] ]}) ); - - it('calculates removed drop indexes correctly', () => xf({ - op1: [ [ 0, { i: 'hi', p: 0 } ], [ 1, 1, { d: 0 } ], [ 2, { r: true } ] ], - op2: [ [ 0, { i: 'yo', p: 0 } ], [ 1, 1, { d: 0 } ] ], - expectLeft: [ [ 0, { i: 'hi' } ], [ 1, 1, { p: 0 } ], [ 2, { r: true }, 1, { d: 0 } ] ], - expectRight: [ [ 1, { i: 'hi' } ], [ 2, { r: true } ] ]}) ); - - it('removed drop indexes calc regression', () => xf({ - op1: [ [ 1, { p: 0 }, 'burbled', { d: 0 } ], [ 3, { r: true } ] ], - op2: [ [ 0, { i: 'to', r: true } ], [ 1, { p: 1 }, [ 'its', { d: 0 } ], [ 'thought', { d: 1 } ] ], [ 3, { p: 0 } ] ], - expectLeft: [ 1, [ 'burbled', { d: 0 } ], [ 'its', { r: true } ], [ 'thought', { p: 0 } ] ], - expectRight: [ 1, 'its', { r: true } ]}) ); - - it('removed drop indexes tele to op1 pick', () => xf({ - op1: [ 'a', 0, [ 0, { es: [] } ], [ 2, { r: true } ] ], - op2: [ [ 'a', { p: 0 }, 0, 0, { p: 1 } ], [ 'b', { d: 0 }, 0, 1, 0, { d: 1 } ] ], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op1: ['a', 0, 2, {r:true}], - op2: [ [ 'a', 0, 0, { p: 0 } ], [ 'b', 0, 1, 0, { d: 0 } ] ] - }, - expect: [ 'b', 0, 1, { r: true }, 0, { r: true } ]}) ); - - it('tracks removed drop index teleports', () => xf({ - // rm 0.a, move 0.b -> 0.c - doc: [{a:['a'], b:'b'}], - op1: [ 0, [ 'a', { r: true } ], [ 'b', { p: 0 } ], [ 'c', { d: 0 } ] ], // [{c:'b'}] - op2: [ 0, { d: 0, p: 1 }, [ 0, { d: 1 } ], [ 'a', { p: 0 } ] ], // [[{b:'b'}, 'a']] - conflict: { - type: RM_UNEXPECTED_CONTENT, - op1: [0, 'a', {r:true}], - op2: [0, {p:0}, 0, {d:0}] - }, - expect: [ 0, { r: true }, 0, { r: true } ]}) ); - - it('handles transforming past cancelled move', () => xf({ - op1: [ [ 0, { r: true } ], [ 10, { i: [ '' ] } ] ], - op2: [ 0, { p: 0, d: 0 } ], - expect: [ [ 0, { r: true } ], [ 10, { i: [ '' ] } ] ]}) ); - - it('correctly adjusts indexes in another fuzzer great', () => xf({ - op1: [ [ 0, { d: 0, r: true } ], [ 3, { p: 0 } ] ], - op2: [ [ 0, { p: 0 } ], [ 3, { d: 0 } ] ], - expect: [[0, {d:0}], [2, {p:0}], [3, {r:true}]]}) ); - - it('op2 moves into something op1 removes and op1 moves into that', () => xf({ - op1: [ [ 'a', { r: true }, 'aa', { p: 0 } ], [ 'b', 'x', { d: 0 } ] ], - op2: [ [ 'a', 'bb', { d: 0 } ], [ 'b', { p: 0 } ] ], - conflict: { - type: RM_UNEXPECTED_CONTENT, - op1: ['a', {r:true}] - }, - expect: [ 'a', { r: true }, ['aa', {r:true}], ['bb', {r:true}]]}) ); // Also ok if we miss the second rs. + it('ignores op2 inserts for index position after op1 insert', () => + xf({ + op1: [{ r: true, i: [] }, 0, { i: '' }], + op2: [0, { i: 0 }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [{ r: true }] + }, + expect: [{ r: true, i: [] }, 0, { r: true, i: '' }] + })); + + it('edit moved inside a removed area should be removed', () => + xf({ + op1: [[0, { r: true }], [2, { es: [] }]], + op2: [[0, 'x', { d: 0 }], [3, { p: 0 }]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [0, { r: true }] + }, + expect: [0, { r: true }, 'x', { r: true }] + })); + + it('advances indexes correctly with mixed numbers', () => + xf({ + op1: [ + ['x', [0, { p: 0 }], [1, { d: 1 }]], + ['y', { p: 1 }], + ['zzz', { d: 0 }] + ], + op2: [['x', 2, { i: 'hi' }], ['y', { p: 0 }], ['z', { d: 0 }]], + expectLeft: [ + ['x', [0, { p: 1 }], [1, { d: 0 }]], + ['z', { p: 0 }], + ['zzz', { d: 1 }] + ], + expectRight: [['x', 0, { p: 0 }], ['zzz', { d: 0 }]] + })); + + it('handles index positions past cancelled drops 1', () => + xf({ + op1: [0, { r: true, i: [''] }], + op2: [[0, { p: 0, d: 0 }], [1, { i: 23 }]], + expectLeft: [0, { r: true, i: [''] }], + expectRight: [[0, { r: true }], [1, { i: [''] }]] + })); + + it('handles index positions past cancelled drops 2', () => + xf({ + // This looks more complicated, but its a simpler version of the above test. + op1: [['a', { r: true }], ['b', 0, { i: 'hi' }]], + op2: [['a', { p: 0 }], ['b', [0, { d: 0 }], [1, { i: 'yo' }]]], + expectLeft: ['b', 0, { i: 'hi', r: true }], + expectRight: ['b', [0, { r: true }], [1, { i: 'hi' }]] + })); + + it('calculates removed drop indexes correctly', () => + xf({ + op1: [[0, { i: 'hi', p: 0 }], [1, 1, { d: 0 }], [2, { r: true }]], + op2: [[0, { i: 'yo', p: 0 }], [1, 1, { d: 0 }]], + expectLeft: [ + [0, { i: 'hi' }], + [1, 1, { p: 0 }], + [2, { r: true }, 1, { d: 0 }] + ], + expectRight: [[1, { i: 'hi' }], [2, { r: true }]] + })); + + it('removed drop indexes calc regression', () => + xf({ + op1: [[1, { p: 0 }, 'burbled', { d: 0 }], [3, { r: true }]], + op2: [ + [0, { i: 'to', r: true }], + [1, { p: 1 }, ['its', { d: 0 }], ['thought', { d: 1 }]], + [3, { p: 0 }] + ], + expectLeft: [ + 1, + ['burbled', { d: 0 }], + ['its', { r: true }], + ['thought', { p: 0 }] + ], + expectRight: [1, 'its', { r: true }] + })); + + it('removed drop indexes tele to op1 pick', () => + xf({ + op1: ['a', 0, [0, { es: [] }], [2, { r: true }]], + op2: [ + ['a', { p: 0 }, 0, 0, { p: 1 }], + ['b', { d: 0 }, 0, 1, 0, { d: 1 }] + ], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', 0, 2, { r: true }], + op2: [['a', 0, 0, { p: 0 }], ['b', 0, 1, 0, { d: 0 }]] + }, + expect: ['b', 0, 1, { r: true }, 0, { r: true }] + })); + + it('tracks removed drop index teleports', () => + xf({ + // rm 0.a, move 0.b -> 0.c + doc: [{ a: ['a'], b: 'b' }], + op1: [0, ['a', { r: true }], ['b', { p: 0 }], ['c', { d: 0 }]], // [{c:'b'}] + op2: [0, { d: 0, p: 1 }, [0, { d: 1 }], ['a', { p: 0 }]], // [[{b:'b'}, 'a']] + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [0, 'a', { r: true }], + op2: [0, { p: 0 }, 0, { d: 0 }] + }, + expect: [0, { r: true }, 0, { r: true }] + })); + + it('handles transforming past cancelled move', () => + xf({ + op1: [[0, { r: true }], [10, { i: [''] }]], + op2: [0, { p: 0, d: 0 }], + expect: [[0, { r: true }], [10, { i: [''] }]] + })); + + it('correctly adjusts indexes in another fuzzer great', () => + xf({ + op1: [[0, { d: 0, r: true }], [3, { p: 0 }]], + op2: [[0, { p: 0 }], [3, { d: 0 }]], + expect: [[0, { d: 0 }], [2, { p: 0 }], [3, { r: true }]] + })); + + it('op2 moves into something op1 removes and op1 moves into that', () => + xf({ + op1: [['a', { r: true }, 'aa', { p: 0 }], ['b', 'x', { d: 0 }]], + op2: [['a', 'bb', { d: 0 }], ['b', { p: 0 }]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', { r: true }] + }, + expect: ['a', { r: true }, ['aa', { r: true }], ['bb', { r: true }]] + })); // Also ok if we miss the second rs. it('op2 moves into op1 remove edge cases', function() { // Sorry not minified. xf({ - op1: [ 'Came', 0, [ 0, { r: true }, 'he', { p: 0 } ], [ 1, { d: 0 }, 0, { i: 'time' } ] ], - op2: [ 'Came', 0, [ 0, 'he', [ 0, { d: 0 } ], [ 1, { es: [] } ] ], [ 1, { p: 0 } ] ], - expectLeft: [ 'Came', 0, 0, { r: true, d: 0 }, [ 0, { i: 'time' } ], [ 'he', { p: 0 } ] ], - expectRight: [ 'Came', 0, 0, { r: true, d: 0 }, [ 1, { i: 'time' } ], [ 'he', { p: 0 } ] ]}); + op1: [ + 'Came', + 0, + [0, { r: true }, 'he', { p: 0 }], + [1, { d: 0 }, 0, { i: 'time' }] + ], + op2: [ + 'Came', + 0, + [0, 'he', [0, { d: 0 }], [1, { es: [] }]], + [1, { p: 0 }] + ], + expectLeft: [ + 'Came', + 0, + 0, + { r: true, d: 0 }, + [0, { i: 'time' }], + ['he', { p: 0 }] + ], + expectRight: [ + 'Came', + 0, + 0, + { r: true, d: 0 }, + [1, { i: 'time' }], + ['he', { p: 0 }] + ] + }); return xf({ - op1: [ [ 0, [ 1, { p: 0 } ], [ 2, { r: true } ] ], [ 1, 'xxx', { d: 0 } ] ], - op2: [ 0, 1, { i: {}, p: 0 }, 'b', { d: 0 } ], - expectLeft: [ [ 0, [ 1, 'b', { p: 0 } ], [ 2, { r: true } ] ], [ 1, 'xxx', { d: 0 } ] ], - expectRight: [ 0, 2, { r: true } ]}); - }); + op1: [[0, [1, { p: 0 }], [2, { r: true }]], [1, 'xxx', { d: 0 }]], + op2: [0, 1, { i: {}, p: 0 }, 'b', { d: 0 }], + expectLeft: [ + [0, [1, 'b', { p: 0 }], [2, { r: true }]], + [1, 'xxx', { d: 0 }] + ], + expectRight: [0, 2, { r: true }] + }); + }); + + it('translates indexes correctly in this fuzzer find', () => + xf({ + op1: [0, { p: 0 }, 'x', { d: 0 }], + op2: [[0, { p: 0, d: 0 }], [1, { i: 'y' }]], + expectLeft: [[0, { p: 0 }], [1, 'x', { d: 0 }]], + expectRight: null + })); - it('translates indexes correctly in this fuzzer find', () => xf({ - op1: [ 0, { p: 0 }, 'x', { d: 0 } ], - op2: [ [ 0, { p: 0, d: 0 } ], [ 1, { i: 'y' } ] ], - expectLeft: [[0, { p: 0 }], [1, 'x', { d: 0 }]], - expectRight: null - }) - ); - - it('buries children of blackholed values', () => xf({ - op1: [ [ 0, [ 'a', { p: 0 } ], [ 'b', { d: 0 } ], [ 'c', { d: 1 } ] ], [ 1, { p: 1 } ] ], - op2: [ 0, { p: 0 }, 'x', { d: 0 } ], - // This is a bit interesting. The question is, which op2 picks and drops - // should we include in the output? For now the answer is that we include - // anything in both ops thats going to end up inside the blackholed - // content. - conflict: { type: BLACKHOLE - }, + it('buries children of blackholed values', () => + xf({ + op1: [ + [0, ['a', { p: 0 }], ['b', { d: 0 }], ['c', { d: 1 }]], + [1, { p: 1 }] + ], + op2: [0, { p: 0 }, 'x', { d: 0 }], + // This is a bit interesting. The question is, which op2 picks and drops + // should we include in the output? For now the answer is that we include + // anything in both ops thats going to end up inside the blackholed + // content. + conflict: { type: BLACKHOLE }, // op1: [[0, 'c', d:0], [1, p:0]] - expect: [ 0, {r: true}, 'x', {r:true} ]}) ); + expect: [0, { r: true }, 'x', { r: true }] + })); it('does not conflict when removed target gets moved inside removed container', function() { // This edge case is interesting because we don't generate the same @@ -2264,314 +2793,321 @@ describe('json1', function() { // object before removing it, but when we're right, the other operation's // move holds the object and we get an unexpected rm conflict. xf({ - op1: [ [ 'a', { r: true }, 'x', { p: 0 } ], [ 'b', { d: 0 } ] ], - op2: [ 'a', [ 'x', { p: 0 } ], [ 'y', { d: 0 } ] ], + op1: [['a', { r: true }, 'x', { p: 0 }], ['b', { d: 0 }]], + op2: ['a', ['x', { p: 0 }], ['y', { d: 0 }]], conflictRight: { type: RM_UNEXPECTED_CONTENT, - op1: ['a', {r:true}] + op1: ['a', { r: true }] }, - expectLeft: [ [ 'a', { r: true }, 'y', { p: 0 } ], [ 'b', { d: 0 } ] ], - expectRight: [ 'a', { r: true }, 'y', {r:true}]}); + expectLeft: [['a', { r: true }, 'y', { p: 0 }], ['b', { d: 0 }]], + expectRight: ['a', { r: true }, 'y', { r: true }] + }); xf({ - op1: [ [ 'a', { r: true }, 1, { p: 0 } ], [ 'b', { d: 0 } ] ], - op2: [ 'a', [ 0, { d: 0 } ], [ 1, { p: 0 } ] ], - expectLeft: [ [ 'a', { r: true }, 0, { p: 0 } ], [ 'b', { d: 0 } ] ], + op1: [['a', { r: true }, 1, { p: 0 }], ['b', { d: 0 }]], + op2: ['a', [0, { d: 0 }], [1, { p: 0 }]], + expectLeft: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]], conflictRight: { type: RM_UNEXPECTED_CONTENT, - op1: ['a', {r:true}] + op1: ['a', { r: true }] }, - expectRight: [ 'a', { r: true }, 0, {r:true}]}); + expectRight: ['a', { r: true }, 0, { r: true }] + }); - return {expect: [ [ 'a', { r: true }, 0, { p: 0 } ], [ 'b', { d: 0 } ] ]}; - }); + return { expect: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]] }; + }); - it('compose copies op2 edit data', () => compose({ - op1: [ 'a', { r: true } ], - op2: [ [ 'x', { p: 0 } ], [ 'y', { d: 0 }, 'b', { es: [] } ] ], - expect: [ - ['a', {r:true}], - ['x', {p:0}], - ['y', {d: 0}, 'b', {es: []}] - ]}) ); - - it('does not conflict when the dest is salvaged', () => xf({ - op1: [ [ 'a', { p: 0 } ], [ 'b', { i: 'hi' } ], [ 'c', { d: 0 } ] ], - op2: [ [ 'a', { p: 0 } ], [ 'b', { d: 0 } ] ], - expectLeft: [['b', {p:0, i:'hi'}], ['c', {d:0}]], - conflictRight: { - type: DROP_COLLISION, - op1: [ 'b', { i: 'hi' } ] - }, - expectRight: null - }) - ); - - it('does not conflict on identical r/i pairs', () => xf({ - op1: [{ i: [], r: true }], - op2: [{ i: [], r: true }], - expect: null - }) - ); - - it('allows embedded edits in identical r/i', () => xf({ - op1: [ { r: true, i: '', es: [] } ], - op2: [ { r: true, i: '' } ], - expect: [{es:[]}]}) ); - - it('does not conflict on identical r/i pairs with identical drops inside', () => xf({ - op1: [ { i: {}, r: true }, 'a', { i: 'a' } ], - op2: [ { i: {}, r: true }, 'a', { i: 'a' } ], - expect: null - }) - ); - - it('generates a DROP_COLLISION on children', () => xf({ - op1: [ { i: {}, r: true }, 'a', { i: 'a' } ], - op2: [ { i: {}, r: true }, 'a', { i: 'b' } ], - conflict: { - type: DROP_COLLISION, - op1: ['a', { i: 'a' } ], - op2: ['a', { i: 'b' } ] - }, - expectLeft: ['a', {r:true, i:'a'}], - expectRight: null - }) - ); - - it('Transforms edit moves into the right dest', () => xf({ - op1: [ 0, { p: 0, d: 0 }, - // These parts are all needed for some reason. - [ 0, { i: 1 } ], - [ 1, { r: true } ], - [ 3, { es: [] } ] - ], - op2: [ 0, [ 0, { d: 0 } ], [ 3, { p: 0 } ] ], - expectLeft: [ 0, {p:0, d:0}, - [0, {i:1}], - [1, {es:[]}], - [2, {r:true}] - ], - expectRight: [0, {p:0, d:0}, - [0, {es:[]}], - [1, {i:1}], - [2, {r:true}] - ]}) ); - - it('adjusts indexes of pick -> drop', () => xf({ - op1: [ 0, { p: 0, d: 0 } ], - op2: [ [ 0, { i: 'yo', p: 0 } ], [ 1, { d: 0 } ] ], - expectLeft: [ [ 0, { d: 0 } ], [ 1, { p: 0 } ] ], - expectRight: null - }) - ); - - it('clears output outDrop when theres no pick', () => xf({ - // Again, not minimized. We return the right data, we were just double- - // descending into outDrop. - op1: [ [ 'the', { d: 0, p: 0 } ], [ 'toves', { r: true } ] ], - op2: [ - [ 'bird', { d: 0 } ], - [ 'slain', { d: 1 } ], - [ 'the', { p: 1 } ], - [ 'toves', { p: 0 } ] - ], - expectLeft: [ - [ 'bird', { r: true } ], - [ 'slain', { p: 0 } ], - [ 'the', { d: 0 } ] - ], - expectRight: [ 'bird', { r: true } ]}) ); - - it('pushes drop indexes by other held items', () => xf({ - op1: [ [ 0, { p: 0 }], - [ 1, - [ 0, { i: 'hi' } ], - [ 1, { d: 0, es: [] } ] ] - ], - op2: [ - [ 0, { p: 1 }, 1, { d: 0 }, 2, { d: 1 } ], - [ 2, { p: 0 } ] - ], - expectLeft: [ 0, 1, - [ 0, { i: 'hi' } ], - [ 1, { d: 0, es: [] } ], - [ 2, { p: 0 } ] - ], - expectRight: [ 0, 1, [ 0, { i: 'hi' } ], [ 3, { es: [] } ] ]}) ); - - it('composes correctly with lots of removes', () => compose({ - op1: [ 3, 1, { r: true } ], - op2: [ - [ 0, { es: [] } ], - [ 1, { r: true, es: [] } ], - [ 2, { r: true } ] - ], - expect: [ - [ 0, { es: [] } ], - [ 1, { es: [], r: true } ], - [ 2, { r: true } ], - [ 3, 1, { r: true } ] - ]}) ); - - it('does not descend twice when p/r on an identical insert', () => xf({ - op1: [ [ 'a', { p: 0, i: '' } ], [ 'b', { d: 0 } ] ], - op2: [ 'a', { r: true, i: '' } ], - expect: null - }) - ); - - it('conflicts underneath a moved / inserted child', () => xf({ - op1: [ [ 'a', { p: 0, i: {} }, 'x', {i:5} ], [ 'b', { d: 0 } ] ], - op2: [ 'a', { r: true, i: {} }, 'x', {i:6} ], - conflict: { - type: DROP_COLLISION, - op1: ['a', 'x', {i:5}], - op2: ['a', 'x', {i:6}] - }, - expectLeft: ['a', 'x', {r:true, i:5}], - expectRight: null - }) - ); - - it('clears drop2 in transform moves', () => xf({ - doc: [{b: {a: 'hi'}}], - op1: [0, {d:0}, - [ 'a', { es: [] } ], - [ 'b', { p: 0 } ] - ], - op2: [ 0, 'b', - [ 'a', { p: 0 } ], - [ 'b', { d: 0 } ] - ], - expect: [0, {d:0}, 'b', { p: 0, es:[] }]}) ); - - it('descends correctly when op2 picks and drops', () => xf({ - op1: [ - [ 'b', { d: 0 }, [ 1, { es: [] } ], [ 2, { i: null } ] ], - [ 'e', { p: 0 } ] - ], - op2: [ { p: 0, d: 0 }, 'e', 1, { p: 1, d: 1 } ], - expectLeft: [ - [ 'b', { d: 0 }, [ 1, { i: null } ], [ 2, { es: [] } ]], - [ 'e', { p: 0 } ] - ], - expectRight: [ - [ 'b', { d: 0 }, [ 1, { es: [] } ], [ 2, { i: null } ] ], - [ 'e', { p: 0 } ] - ]}) ); - - it('composes a pick out of the insert', () => compose({ - op1: [ { i: [ 5, { x: 6 } ] } ], - op2: [ [ 0, { r: true }, 'c', { d: 0 } ], [ 1, 'x', { p: 0 } ] ], - // expect: [{i: [{c: 6}]}] - expect: [ { i: [ {} ] }, 0, 'c', { i: 6 } ]}) ); - - it('is not overeager to remove intermediate literal array items', () => compose({ - op1: [ [ 0, { i: [ 'a', 'b' ] }, 0, { p: 0 } ], [ 1, 0, { d: 0 } ] ], - op2: [ 0, { r: ['a'] }, 1, { r: 'b' } ], - expect: [ 0, 0, { d: 0, p: 0 } ]}) ); - - it('descends down insert indexes correctly', () => compose({ - op1: [ { i: [ {}, 'a' ] }, 1, { i: 'b' } ], - op2: [ [ 1, { r: 'b' } ], [ 2, { r: 'a' } ] ], - expect: [ { i: [ {} ] } ]}) ); - - it('handles composes with ena: 0', () => compose({ - op1: [{i:10}], - op2: [{ena:0}], - expect: [{i:10, ena:0}]}) ); // Also ok: just discarding the ena:0. - - it('handles rm parent with cross move', () => compose({ - op1: [ [ 'a', { p: 0 } ], [ 'b', 1, { d: 0 } ] ], - op2: [ [ 'b', { r: true }, 1, { p: 0 } ], [ 'c', { d: 0 } ]], - expect: [ [ 'a', { p: 0 } ], [ 'b', { r: true } ], [ 'c', { d: 0 } ] ]}) ); - - it('lets you remove children of an op at 2 levels', () => compose({ - op1: [ { i: [ 'a', { x: 'hi' } ] } ], - op2: [ { r: true }, 1, 'x', { r: true } ], - expect: null - }) - ); - - it('discards op1 inserts inside a removed chunk', () => compose({ - op1: [ 'y', [ 1, { i: 'x' } ], [ 2, { i: [ 'a', 'b' ] } ] ], - op2: [ { r: true }, 'y', 2, 0, { r: true } ], - expect: [ { r: true } ]}) ); - - it('handles deeply nested blackhole operations', () => xf({ - op1: [ - [ 'x', { p: 0 } ], - [ 'y', - [ 'a', - [ 'j', { p: 1 } ], - [ 'k', { d: 1 } ] - ], - [ 'b', { d: 0 }] + it('compose copies op2 edit data', () => + compose({ + op1: ['a', { r: true }], + op2: [['x', { p: 0 }], ['y', { d: 0 }, 'b', { es: [] }]], + expect: [ + ['a', { r: true }], + ['x', { p: 0 }], + ['y', { d: 0 }, 'b', { es: [] }] ] - ], - op2: [ - [ 'x', 'xx', { d: 0 }, 'j', 'jj', { d: 1 } ], - [ 'y', { p: 1 }, 'a', { p: 0 } ] - ], - conflict: { type: BLACKHOLE - }, - expect: ['x', {r:true}, 'xx', {r:true}, 'j', 'jj', {r:true}]}) ); - - it('does not list removed op1 moves in the blackhole info', () => xf({ - op1: [ - [ 'a', [ 'j', { d: 0 } ], [ 'k', { d: 1 } ] ], - [ 'b', { p: 0 }, 'z', 0, { p: 1 } ] - ], - op2: [ - [ 'a', { p: 0 } ], - [ 'b', [ 'y', { d: 0 } ], [ 'z', { r: true } ] ] - ], - conflict: { - type: BLACKHOLE, - op1: [ [ 'a', 'j', { d: 0 } ], [ 'b', { p: 0 } ] ], - op2: [ [ 'a', { p: 0 } ], [ 'b', 'y', { d: 0 } ] ] - }, - expect: ['b', {r:true}, 'y', {r:true}]}) ); - - return it('handles overlapping pick in blackholes', () => xf({ - // This looks complicated, but its really not so bad. Its: - // a->b.0, a.x -> z - // vs - // b -> a.x -> a.y - // - // Its a bit twisty because we're both picking up the same element and - // putting it in different places. This is why we have different left and - // right results. - op1: [ - [ 'a', { p: 1 }, 'x', { p: 0 } ], - [ 'b', 0, { d: 1 } ], - [ 'z', { d: 0 } ] - ], - op2: [ - [ 'a', [ 'x', { d: 0, p: 1 } ], [ 'y', { d: 1 } ] ], - [ 'b', { p: 0 } ] - ], - conflictLeft: { - type: BLACKHOLE, - op1: [['a', {p:0}], ['b', 0, {d:0}]], - op2: [['a', 'x', {d:0}], ['b', {p:0}]] - }, - expectLeft: [ - [ 'a', { r: true }, - [ 'x', { r: true } ], - [ 'y', { p: 0 } ] + })); + + it('does not conflict when the dest is salvaged', () => + xf({ + op1: [['a', { p: 0 }], ['b', { i: 'hi' }], ['c', { d: 0 }]], + op2: [['a', { p: 0 }], ['b', { d: 0 }]], + expectLeft: [['b', { p: 0, i: 'hi' }], ['c', { d: 0 }]], + conflictRight: { + type: DROP_COLLISION, + op1: ['b', { i: 'hi' }] + }, + expectRight: null + })); + + it('does not conflict on identical r/i pairs', () => + xf({ + op1: [{ i: [], r: true }], + op2: [{ i: [], r: true }], + expect: null + })); + + it('allows embedded edits in identical r/i', () => + xf({ + op1: [{ r: true, i: '', es: [] }], + op2: [{ r: true, i: '' }], + expect: [{ es: [] }] + })); + + it('does not conflict on identical r/i pairs with identical drops inside', () => + xf({ + op1: [{ i: {}, r: true }, 'a', { i: 'a' }], + op2: [{ i: {}, r: true }, 'a', { i: 'a' }], + expect: null + })); + + it('generates a DROP_COLLISION on children', () => + xf({ + op1: [{ i: {}, r: true }, 'a', { i: 'a' }], + op2: [{ i: {}, r: true }, 'a', { i: 'b' }], + conflict: { + type: DROP_COLLISION, + op1: ['a', { i: 'a' }], + op2: ['a', { i: 'b' }] + }, + expectLeft: ['a', { r: true, i: 'a' }], + expectRight: null + })); + + it('Transforms edit moves into the right dest', () => + xf({ + op1: [ + 0, + { p: 0, d: 0 }, + // These parts are all needed for some reason. + [0, { i: 1 }], + [1, { r: true }], + [3, { es: [] }] ], - [ 'z', { d: 0 } ] - ], - conflictRight: { - type: BLACKHOLE, - op1: [['a', {p:0}], ['b', 0, {d:0}]] - }, - expectRight: [ 'a', { r: true }, - [ 'x', { r: true } ], - [ 'y', { r: true } ] - ]}) ); -}); + op2: [0, [0, { d: 0 }], [3, { p: 0 }]], + expectLeft: [ + 0, + { p: 0, d: 0 }, + [0, { i: 1 }], + [1, { es: [] }], + [2, { r: true }] + ], + expectRight: [ + 0, + { p: 0, d: 0 }, + [0, { es: [] }], + [1, { i: 1 }], + [2, { r: true }] + ] + })); + + it('adjusts indexes of pick -> drop', () => + xf({ + op1: [0, { p: 0, d: 0 }], + op2: [[0, { i: 'yo', p: 0 }], [1, { d: 0 }]], + expectLeft: [[0, { d: 0 }], [1, { p: 0 }]], + expectRight: null + })); + + it('clears output outDrop when theres no pick', () => + xf({ + // Again, not minimized. We return the right data, we were just double- + // descending into outDrop. + op1: [['the', { d: 0, p: 0 }], ['toves', { r: true }]], + op2: [ + ['bird', { d: 0 }], + ['slain', { d: 1 }], + ['the', { p: 1 }], + ['toves', { p: 0 }] + ], + expectLeft: [ + ['bird', { r: true }], + ['slain', { p: 0 }], + ['the', { d: 0 }] + ], + expectRight: ['bird', { r: true }] + })); + + it('pushes drop indexes by other held items', () => + xf({ + op1: [[0, { p: 0 }], [1, [0, { i: 'hi' }], [1, { d: 0, es: [] }]]], + op2: [[0, { p: 1 }, 1, { d: 0 }, 2, { d: 1 }], [2, { p: 0 }]], + expectLeft: [ + 0, + 1, + [0, { i: 'hi' }], + [1, { d: 0, es: [] }], + [2, { p: 0 }] + ], + expectRight: [0, 1, [0, { i: 'hi' }], [3, { es: [] }]] + })); + + it('composes correctly with lots of removes', () => + compose({ + op1: [3, 1, { r: true }], + op2: [[0, { es: [] }], [1, { r: true, es: [] }], [2, { r: true }]], + expect: [ + [0, { es: [] }], + [1, { es: [], r: true }], + [2, { r: true }], + [3, 1, { r: true }] + ] + })); + + it('does not descend twice when p/r on an identical insert', () => + xf({ + op1: [['a', { p: 0, i: '' }], ['b', { d: 0 }]], + op2: ['a', { r: true, i: '' }], + expect: null + })); + + it('conflicts underneath a moved / inserted child', () => + xf({ + op1: [['a', { p: 0, i: {} }, 'x', { i: 5 }], ['b', { d: 0 }]], + op2: ['a', { r: true, i: {} }, 'x', { i: 6 }], + conflict: { + type: DROP_COLLISION, + op1: ['a', 'x', { i: 5 }], + op2: ['a', 'x', { i: 6 }] + }, + expectLeft: ['a', 'x', { r: true, i: 5 }], + expectRight: null + })); + + it('clears drop2 in transform moves', () => + xf({ + doc: [{ b: { a: 'hi' } }], + op1: [0, { d: 0 }, ['a', { es: [] }], ['b', { p: 0 }]], + op2: [0, 'b', ['a', { p: 0 }], ['b', { d: 0 }]], + expect: [0, { d: 0 }, 'b', { p: 0, es: [] }] + })); + + it('descends correctly when op2 picks and drops', () => + xf({ + op1: [ + ['b', { d: 0 }, [1, { es: [] }], [2, { i: null }]], + ['e', { p: 0 }] + ], + op2: [{ p: 0, d: 0 }, 'e', 1, { p: 1, d: 1 }], + expectLeft: [ + ['b', { d: 0 }, [1, { i: null }], [2, { es: [] }]], + ['e', { p: 0 }] + ], + expectRight: [ + ['b', { d: 0 }, [1, { es: [] }], [2, { i: null }]], + ['e', { p: 0 }] + ] + })); + + it('composes a pick out of the insert', () => + compose({ + op1: [{ i: [5, { x: 6 }] }], + op2: [[0, { r: true }, 'c', { d: 0 }], [1, 'x', { p: 0 }]], + // expect: [{i: [{c: 6}]}] + expect: [{ i: [{}] }, 0, 'c', { i: 6 }] + })); + + it('is not overeager to remove intermediate literal array items', () => + compose({ + op1: [[0, { i: ['a', 'b'] }, 0, { p: 0 }], [1, 0, { d: 0 }]], + op2: [0, { r: ['a'] }, 1, { r: 'b' }], + expect: [0, 0, { d: 0, p: 0 }] + })); + + it('descends down insert indexes correctly', () => + compose({ + op1: [{ i: [{}, 'a'] }, 1, { i: 'b' }], + op2: [[1, { r: 'b' }], [2, { r: 'a' }]], + expect: [{ i: [{}] }] + })); + + it('handles composes with ena: 0', () => + compose({ + op1: [{ i: 10 }], + op2: [{ ena: 0 }], + expect: [{ i: 10, ena: 0 }] + })); // Also ok: just discarding the ena:0. + + it('handles rm parent with cross move', () => + compose({ + op1: [['a', { p: 0 }], ['b', 1, { d: 0 }]], + op2: [['b', { r: true }, 1, { p: 0 }], ['c', { d: 0 }]], + expect: [['a', { p: 0 }], ['b', { r: true }], ['c', { d: 0 }]] + })); + + it('lets you remove children of an op at 2 levels', () => + compose({ + op1: [{ i: ['a', { x: 'hi' }] }], + op2: [{ r: true }, 1, 'x', { r: true }], + expect: null + })); + + it('discards op1 inserts inside a removed chunk', () => + compose({ + op1: ['y', [1, { i: 'x' }], [2, { i: ['a', 'b'] }]], + op2: [{ r: true }, 'y', 2, 0, { r: true }], + expect: [{ r: true }] + })); + + it('handles deeply nested blackhole operations', () => + xf({ + op1: [ + ['x', { p: 0 }], + ['y', ['a', ['j', { p: 1 }], ['k', { d: 1 }]], ['b', { d: 0 }]] + ], + op2: [ + ['x', 'xx', { d: 0 }, 'j', 'jj', { d: 1 }], + ['y', { p: 1 }, 'a', { p: 0 }] + ], + conflict: { type: BLACKHOLE }, + expect: ['x', { r: true }, 'xx', { r: true }, 'j', 'jj', { r: true }] + })); + + it('does not list removed op1 moves in the blackhole info', () => + xf({ + op1: [ + ['a', ['j', { d: 0 }], ['k', { d: 1 }]], + ['b', { p: 0 }, 'z', 0, { p: 1 }] + ], + op2: [['a', { p: 0 }], ['b', ['y', { d: 0 }], ['z', { r: true }]]], + conflict: { + type: BLACKHOLE, + op1: [['a', 'j', { d: 0 }], ['b', { p: 0 }]], + op2: [['a', { p: 0 }], ['b', 'y', { d: 0 }]] + }, + expect: ['b', { r: true }, 'y', { r: true }] + })); + + return it('handles overlapping pick in blackholes', () => + xf({ + // This looks complicated, but its really not so bad. Its: + // a->b.0, a.x -> z + // vs + // b -> a.x -> a.y + // + // Its a bit twisty because we're both picking up the same element and + // putting it in different places. This is why we have different left and + // right results. + op1: [ + ['a', { p: 1 }, 'x', { p: 0 }], + ['b', 0, { d: 1 }], + ['z', { d: 0 }] + ], + op2: [['a', ['x', { d: 0, p: 1 }], ['y', { d: 1 }]], ['b', { p: 0 }]], + conflictLeft: { + type: BLACKHOLE, + op1: [['a', { p: 0 }], ['b', 0, { d: 0 }]], + op2: [['a', 'x', { d: 0 }], ['b', { p: 0 }]] + }, + expectLeft: [ + ['a', { r: true }, ['x', { r: true }], ['y', { p: 0 }]], + ['z', { d: 0 }] + ], + conflictRight: { + type: BLACKHOLE, + op1: [['a', { p: 0 }], ['b', 0, { d: 0 }]] + }, + expectRight: ['a', { r: true }, ['x', { r: true }], ['y', { r: true }]] + })); + }); }); - \ No newline at end of file From e693a84572619a4f380840134d2028a1a0597ee6 Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 07:12:20 +0530 Subject: [PATCH 04/11] Remove coffeescript/register in mocha config --- test/mocha.opts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/mocha.opts b/test/mocha.opts index 988738e..4d7bcbf 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,3 +1,2 @@ --r coffeescript/register --reporter spec --check-leaks From 42b84d89bd8d5f163454bc2d932f16e3ebacf8cc Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 09:34:21 +0530 Subject: [PATCH 05/11] Remove semicolons from tests using Prettier. --- test/cursor.js | 122 ++--- test/fuzzer.js | 16 +- test/genOp.js | 244 ++++----- test/immutable.js | 82 +-- test/test.js | 1243 ++++++++++++++++++++++----------------------- 5 files changed, 848 insertions(+), 859 deletions(-) diff --git a/test/cursor.js b/test/cursor.js index 42d2b01..2aa3b97 100644 --- a/test/cursor.js +++ b/test/cursor.js @@ -5,119 +5,119 @@ * DS205: Consider reworking code to avoid use of IIFEs * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const { writeCursor, readCursor } = require('../lib/cursor'); -const assert = require('assert'); +const { writeCursor, readCursor } = require('../lib/cursor') +const assert = require('assert') const data = require('fs') .readFileSync(__dirname + '/ops.json', 'utf8') .split('\n') .filter(x => x !== '') - .map(JSON.parse); + .map(JSON.parse) describe('cursors', function() { describe('writeCursor duplicates', function() { const test = function(op) { - const w = writeCursor(); + const w = writeCursor() var f = function(l) { - assert(Array.isArray(l)); - let depth = 0; + assert(Array.isArray(l)) + let depth = 0 for (let c of Array.from(l)) { if (['string', 'number'].includes(typeof c)) { - depth++; - w.descend(c); + depth++ + w.descend(c) } else if (Array.isArray(c)) { - f(c); + f(c) } else if (typeof c === 'object') { for (let k in c) { - const v = c[k]; - w.write(k, v); + const v = c[k] + w.write(k, v) } } } - return __range__(0, depth, false).map(i => w.ascend()); - }; + return __range__(0, depth, false).map(i => w.ascend()) + } if (op !== null) { - f(op); + f(op) } - return assert.deepEqual(op, w.get()); - }; + return assert.deepEqual(op, w.get()) + } return Array.from(data).map(d => (d => it(`${JSON.stringify(d)}`, () => test(d)))(d) - ); - }); + ) + }) describe('copy using read cursors', function() { const test = function(op) { - let f; - const r = readCursor(op); - const w = writeCursor(); - const path = []; - (f = function() { - let component, k; + let f + const r = readCursor(op) + const w = writeCursor() + const path = [] + ;(f = function() { + let component, k if ((component = r.getComponent())) { // console.log 'component', component for (k in component) { - const v = component[k]; - w.write(k, v); + const v = component[k] + w.write(k, v) } } - assert.deepStrictEqual(r.getPath(), path); - assert.deepStrictEqual(w.getPath(), path); + assert.deepStrictEqual(r.getPath(), path) + assert.deepStrictEqual(w.getPath(), path) return (() => { - const result = []; + const result = [] for (k of r) { - path.push(k); - w.descend(k); - f(); - w.ascend(); - result.push(path.pop()); + path.push(k) + w.descend(k) + f() + w.ascend() + result.push(path.pop()) } - return result; - })(); - })(); + return result + })() + })() - return assert.deepEqual(op, w.get()); - }; + return assert.deepEqual(op, w.get()) + } // console.log op // console.log w.get() return Array.from(data).map(d => (d => it(`${JSON.stringify(d)}`, () => test(d)))(d) - ); - }); + ) + }) return describe('fuzzer', () => it('cleans up position after mergeTree', function() { - const a = [1, 'c', { d: 1 }]; - const w = writeCursor(a); + const a = [1, 'c', { d: 1 }] + const w = writeCursor(a) - w.descend(0); + w.descend(0) - w.descend('a'); - w.write('p', 1); - w.ascend(); - w.ascend(); + w.descend('a') + w.write('p', 1) + w.ascend() + w.ascend() - w.descend(1); - w.mergeTree([{ r: true }]); - w.descend('b'); - w.write('p', 0); // Crash! - w.ascend(); - return w.ascend(); - })); -}); + w.descend(1) + w.mergeTree([{ r: true }]) + w.descend('b') + w.write('p', 0) // Crash! + w.ascend() + return w.ascend() + })) +}) function __range__(left, right, inclusive) { - let range = []; - let ascending = left < right; - let end = !inclusive ? right : ascending ? right + 1 : right - 1; + let range = [] + let ascending = left < right + let end = !inclusive ? right : ascending ? right + 1 : right - 1 for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { - range.push(i); + range.push(i) } - return range; + return range } diff --git a/test/fuzzer.js b/test/fuzzer.js index 5b82ea3..9bfa43a 100644 --- a/test/fuzzer.js +++ b/test/fuzzer.js @@ -1,18 +1,18 @@ -const assert = require('assert'); +const assert = require('assert') // const {type} = require('../index') -const { type } = require('../lib/json1'); +const { type } = require('../lib/json1') const run = (module.exports = () => { // require('./lib/log').quiet = true // type.debug = true - const fuzzer = require('ot-fuzzer'); - const genOp = require('./genOp'); + const fuzzer = require('ot-fuzzer') + const genOp = require('./genOp') - const _t = type.typeAllowingConflictsPred(() => true); + const _t = type.typeAllowingConflictsPred(() => true) // const tracer = require('./tracer')(_t, genOp) //fuzzer(tracer, tracer.genOp, 100000) - fuzzer(_t, genOp, 10000000); -}); + fuzzer(_t, genOp, 10000000) +}) -if (require.main === module) run(); +if (require.main === module) run() diff --git a/test/genOp.js b/test/genOp.js index f240cbf..c5333dd 100644 --- a/test/genOp.js +++ b/test/genOp.js @@ -6,171 +6,171 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let genRandomOp; -const { randomInt, randomReal, randomWord } = require('ot-fuzzer'); +let genRandomOp +const { randomInt, randomReal, randomWord } = require('ot-fuzzer') // require 'ot-fuzzer' -const assert = require('assert'); -const { writeCursor } = require('../lib/cursor'); -const log = require('../lib/log'); -const { type } = require('../lib/json1'); +const assert = require('assert') +const { writeCursor } = require('../lib/cursor') +const log = require('../lib/log') +const { type } = require('../lib/json1') // This is an awful function to clone a document snapshot for use by the random // op generator. .. Since we don't want to corrupt the original object with // the changes the op generator will make. const clone = function(o) { if (typeof o === 'object') { - return JSON.parse(JSON.stringify(o)); + return JSON.parse(JSON.stringify(o)) } else { - return o; + return o } -}; +} const randomKey = function(obj) { // this works on arrays too! if (Array.isArray(obj)) { if (obj.length === 0) { - return undefined; + return undefined } else { - return randomInt(obj.length); + return randomInt(obj.length) } } else { - const keys = Object.keys(obj); + const keys = Object.keys(obj) if (keys.length === 0) { - return undefined; + return undefined } else { - return keys[randomInt(keys.length)]; + return keys[randomInt(keys.length)] } } -}; +} -const letters = 'abxyz'; +const letters = 'abxyz' // Generate a random new key for a value in obj. const randomNewKey = function(obj) { if (Array.isArray(obj)) { - return randomInt(obj.length + 1); + return randomInt(obj.length + 1) } else { - let key; + let key while (true) { // Mostly try just use a small letter key = - randomInt(20) === 0 ? randomWord() : letters[randomInt(letters.length)]; + randomInt(20) === 0 ? randomWord() : letters[randomInt(letters.length)] if (obj[key] === undefined) { - break; + break } } - return key; + return key } -}; +} // Generate a random object var randomThing = function() { switch (randomInt(7)) { case 0: - return null; + return null case 1: - return ''; + return '' case 2: - return randomWord(); + return randomWord() case 3: - var obj = {}; + var obj = {} for ( let i = 1, end = randomInt(2), asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i-- ) { - obj[randomNewKey(obj)] = randomThing(); + obj[randomNewKey(obj)] = randomThing() } - return obj; + return obj case 4: - return __range__(1, randomInt(2), true).map(j => randomThing()); + return __range__(1, randomInt(2), true).map(j => randomThing()) case 5: - return 0; // bias toward zeros to find accidental truthy checks + return 0 // bias toward zeros to find accidental truthy checks case 6: - return randomInt(50); + return randomInt(50) } -}; +} // Pick a random path to something in the object. const randomPath = function(data) { if (data == null || typeof data !== 'object') { - return []; + return [] } - const path = []; + const path = [] while (randomReal() < 0.9 && data != null && typeof data === 'object') { - const key = randomKey(data); + const key = randomKey(data) if (key == null) { - break; + break } - path.push(key); - data = data[key]; + path.push(key) + data = data[key] } - return path; -}; + return path +} const randomWalkPick = function(w, container) { - const path = randomPath(container.data); - let parent = container; - let key = 'data'; + const path = randomPath(container.data) + let parent = container + let key = 'data' //log 'rwp', container, path, parent for (let p of Array.from(path)) { - parent = parent[key]; - key = p; - w.descend(key); + parent = parent[key] + key = p + w.descend(key) } - const operand = parent[key]; - return [path, parent, key, operand]; -}; + const operand = parent[key] + return [path, parent, key, operand] +} // Returns [path, parent, key] if we can drop here, or null if no drop is // possible const randomWalkDrop = function(w, container) { if (container.data === undefined) { - return [[], container, 'data']; + return [[], container, 'data'] } else if (typeof container.data !== 'object' || container.data === null) { - return null; // Can't insert into a document that is a string or number + return null // Can't insert into a document that is a string or number } - let [path, parent, key, operand] = Array.from(randomWalkPick(w, container)); + let [path, parent, key, operand] = Array.from(randomWalkPick(w, container)) if (typeof operand === 'object' && operand !== null) { - parent = operand; + parent = operand } else { - w.ascend(); - w.reset(); - path.pop(); + w.ascend() + w.reset() + path.pop() } - key = randomNewKey(parent); + key = randomNewKey(parent) // log 'key', key - return [path, parent, key]; -}; + return [path, parent, key] +} const set = function(container, key, value) { if (value === undefined) { if (Array.isArray(container)) { - return container.splice(key, 1); + return container.splice(key, 1) } else { - return delete container[key]; + return delete container[key] } } else { if (Array.isArray(container)) { - return container.splice(key, 0, value); + return container.splice(key, 0, value) } else { - return (container[key] = value); + return (container[key] = value) } } -}; +} const genRandomOpPart = function(data) { // log 'genRandomOp', data - let key1, parent1, path1; - const container = { data }; - const w = writeCursor(); + let key1, parent1, path1 + const container = { data } + const w = writeCursor() // Remove something @@ -179,7 +179,7 @@ const genRandomOpPart = function(data) { // 1. move something // 2. insert something // 3. edit a string - const mode = data === undefined && randomReal() < 0.9 ? 2 : randomInt(4); + const mode = data === undefined && randomReal() < 0.9 ? 2 : randomInt(4) //mode = 1 //log 'mode', mode switch (mode) { @@ -187,55 +187,55 @@ const genRandomOpPart = function(data) { case 1: var [path, parent, key, operand] = Array.from( randomWalkPick(w, container) - ); + ) //log 'ppko', path, parent, key, operand if (mode === 1 && typeof operand === 'string') { // Edit it! - const genString = require('ot-text/test/genOp'); - const [stringOp, result] = Array.from(genString(operand)); - w.write('es', stringOp); - parent[key] = result; + const genString = require('ot-text/test/genOp') + const [stringOp, result] = Array.from(genString(operand)) + w.write('es', stringOp) + parent[key] = result } else if (mode === 1 && typeof operand === 'number') { - const increment = randomInt(10); - w.write('ena', increment); - parent[key] += increment; + const increment = randomInt(10) + w.write('ena', increment) + parent[key] += increment } else { // Remove it if (operand !== undefined) { - w.write('r', true); //operand - set(parent, key, undefined); + w.write('r', true) //operand + set(parent, key, undefined) } } - break; + break case 2: // insert something - var walk = randomWalkDrop(w, container); + var walk = randomWalkDrop(w, container) if (walk !== null) { - [path, parent, key] = Array.from(walk); + ;[path, parent, key] = Array.from(walk) //log 'walk', walk - const val = randomThing(); + const val = randomThing() if (parent !== container) { - w.descend(key); + w.descend(key) } - w.write('i', val); - set(parent, key, clone(val)); + w.write('i', val) + set(parent, key, clone(val)) } - break; + break case 3: // Move something. We'll pick up the current operand... - [path1, parent1, key1, operand] = Array.from( + ;[path1, parent1, key1, operand] = Array.from( randomWalkPick(w, container) - ); + ) if (operand !== undefined) { - set(parent1, key1, undefined); // remove it from the result... + set(parent1, key1, undefined) // remove it from the result... if (parent1 === container) { // We're removing the whole thing. - w.write('r', true); + w.write('r', true) } else { - w.write('p', 0); + w.write('p', 0) // ... and find another place to insert it! for ( @@ -243,73 +243,73 @@ const genRandomOpPart = function(data) { asc ? i < end : i > end; asc ? i++ : i-- ) { - w.ascend(); + w.ascend() } - w.reset(); + w.reset() const [path2, parent2, key2] = Array.from( randomWalkDrop(w, container) - ); - w.descend(key2); - set(parent2, key2, operand); - w.write('d', 0); + ) + w.descend(key2) + set(parent2, key2, operand) + w.write('d', 0) } } - break; + break } - const doc = container.data; - const op = w.get(); + const doc = container.data + const op = w.get() - type.checkValidOp(op); + type.checkValidOp(op) // assert.deepEqual doc, type.apply clone(data), op - return [op, doc]; -}; + return [op, doc] +} module.exports = genRandomOp = function(doc) { - doc = clone(doc); + doc = clone(doc) // 90% chance of adding an op the first time through, then 50% each successive time. - let chance = 0.99; + let chance = 0.99 - let op = null; // Aggregate op + let op = null // Aggregate op while (randomReal() < chance) { - let opc; - [opc, doc] = Array.from(genRandomOpPart(doc)); - log(opc); + let opc + ;[opc, doc] = Array.from(genRandomOpPart(doc)) + log(opc) // type.setDebug false op = type.compose( op, opc - ); + ) - chance = 0.5; + chance = 0.5 } // log.quiet = false - log('-> generated op', op, 'doc', doc); - return [op, doc]; -}; + log('-> generated op', op, 'doc', doc) + return [op, doc] +} if (require.main === module) { // log genRandomOp {} // log genRandomOp({x:5, y:7, z:[1,2,3]}) for [1..10] for (let i = 1; i <= 10; i++) { - type.debug = true; - log.quiet = false; - log(genRandomOp({ x: 'hi', y: 'omg', z: [1, 'whoa', 3] })); + type.debug = true + log.quiet = false + log(genRandomOp({ x: 'hi', y: 'omg', z: [1, 'whoa', 3] })) } } // log genRandomOp(undefined) function __range__(left, right, inclusive) { - let range = []; - let ascending = left < right; - let end = !inclusive ? right : ascending ? right + 1 : right - 1; + let range = [] + let ascending = left < right + let end = !inclusive ? right : ascending ? right + 1 : right - 1 for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { - range.push(i); + range.push(i) } - return range; + return range } diff --git a/test/immutable.js b/test/immutable.js index eb07298..bd6f93f 100644 --- a/test/immutable.js +++ b/test/immutable.js @@ -6,85 +6,85 @@ * DS205: Consider reworking code to avoid use of IIFEs * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const assert = require('assert'); -const { type } = require('../lib/json1'); -const log = require('../lib/log'); -const genOp = require('./genOp'); -const deepClone = require('../lib/deepClone'); +const assert = require('assert') +const { type } = require('../lib/json1') +const log = require('../lib/log') +const genOp = require('./genOp') +const deepClone = require('../lib/deepClone') // This tests that none of apply / compose / transform / genOp mutate their // input describe('immutable guarantees', function() { - const origDoc = { x: 'hi', y: 'omg', z: [1, 'whoa', 3] }; - const expectDoc = deepClone(origDoc); - const n = 1000; - this.slow(n * 10); + const origDoc = { x: 'hi', y: 'omg', z: [1, 'whoa', 3] } + const expectDoc = deepClone(origDoc) + const n = 1000 + this.slow(n * 10) it('apply does not mutate', () => (() => { - const result = []; + const result = [] for ( let i = 1, end = n, asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i-- ) { - const [op, doc] = Array.from(genOp(origDoc)); - assert.deepStrictEqual(origDoc, expectDoc); + const [op, doc] = Array.from(genOp(origDoc)) + assert.deepStrictEqual(origDoc, expectDoc) - const expectOp = deepClone(op); - type.apply(origDoc, op); + const expectOp = deepClone(op) + type.apply(origDoc, op) - assert.deepStrictEqual(origDoc, expectDoc); - result.push(assert.deepStrictEqual(op, expectOp)); + assert.deepStrictEqual(origDoc, expectDoc) + result.push(assert.deepStrictEqual(op, expectOp)) } - return result; - })()); + return result + })()) it('compose does not mutate', () => (() => { - const result = []; + const result = [] for ( let i = 1, end = n, asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i-- ) { - let op2; - let [op1, doc] = Array.from(genOp(origDoc)); - [op2, doc] = Array.from(genOp(doc)); + let op2 + let [op1, doc] = Array.from(genOp(origDoc)) + ;[op2, doc] = Array.from(genOp(doc)) - const expectOp1 = deepClone(op1); - const expectOp2 = deepClone(op2); + const expectOp1 = deepClone(op1) + const expectOp2 = deepClone(op2) type.compose( op1, op2 - ); + ) - assert.deepStrictEqual(op1, expectOp1); - result.push(assert.deepStrictEqual(op2, expectOp2)); + assert.deepStrictEqual(op1, expectOp1) + result.push(assert.deepStrictEqual(op2, expectOp2)) } - return result; - })()); + return result + })()) return it('transform does not mutate', () => (() => { - const result = []; + const result = [] for ( let i = 1, end = n, asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i-- ) { - const [op1, doc1] = Array.from(genOp(origDoc)); - const [op2, doc2] = Array.from(genOp(origDoc)); + const [op1, doc1] = Array.from(genOp(origDoc)) + const [op2, doc2] = Array.from(genOp(origDoc)) - const expectOp1 = deepClone(op1); - const expectOp2 = deepClone(op2); + const expectOp1 = deepClone(op1) + const expectOp2 = deepClone(op2) - type.transformNoConflict(op1, op2, 'left'); - type.transformNoConflict(op2, op1, 'right'); - assert.deepStrictEqual(op1, expectOp1); - result.push(assert.deepStrictEqual(op2, expectOp2)); + type.transformNoConflict(op1, op2, 'left') + type.transformNoConflict(op2, op1, 'right') + assert.deepStrictEqual(op1, expectOp1) + result.push(assert.deepStrictEqual(op2, expectOp2)) } - return result; - })()); -}); + return result + })()) +}) diff --git a/test/test.js b/test/test.js index c0403ac..f924b67 100644 --- a/test/test.js +++ b/test/test.js @@ -12,70 +12,70 @@ // // Cleanups welcome, so long as you don't remove any tests. -const assert = require('assert'); +const assert = require('assert') // {type} = require '../index' -const { type } = require('../lib/json1'); -const log = require('../lib/log'); -const deepClone = require('../lib/deepClone'); +const { type } = require('../lib/json1') +const log = require('../lib/log') +const deepClone = require('../lib/deepClone') -const { transform } = type; -const { DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE } = type; +const { transform } = type +const { DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE } = type const apply = function({ doc: snapshot, op, expect }) { - type.setDebug(false); + type.setDebug(false) - const orig = deepClone(snapshot); + const orig = deepClone(snapshot) try { - const result = type.apply(snapshot, op); - assert.deepStrictEqual(snapshot, orig, 'Original snapshot was mutated'); - return assert.deepStrictEqual(result, expect); + const result = type.apply(snapshot, op) + assert.deepStrictEqual(snapshot, orig, 'Original snapshot was mutated') + return assert.deepStrictEqual(result, expect) } catch (e) { console.log( `Apply failed! Repro apply( ${JSON.stringify(snapshot)}, ${JSON.stringify( op )} )` - ); - console.log(`expected output: ${JSON.stringify(expect)}`); - throw e; + ) + console.log(`expected output: ${JSON.stringify(expect)}`) + throw e } -}; +} const d = function(fn) { - type.setDebug(true); - fn(); - return type.setDebug(false); -}; + type.setDebug(true) + fn() + return type.setDebug(false) +} const compose = function({ op1, op2, expect }) { try { const result = type.compose( op1, op2 - ); - return assert.deepStrictEqual(result, expect); + ) + return assert.deepStrictEqual(result, expect) } catch (e) { d(function() { - console.error('FAIL! Repro with:'); - console.log(`compose( ${JSON.stringify(op1)}, ${JSON.stringify(op2)} )`); - console.log(`expected output: ${JSON.stringify(expect)}`); + console.error('FAIL! Repro with:') + console.log(`compose( ${JSON.stringify(op1)}, ${JSON.stringify(op2)} )`) + console.log(`expected output: ${JSON.stringify(expect)}`) return type.compose( op1, op2 - ); - }); - throw e; + ) + }) + throw e } -}; +} -const invConflict = ({ type, op1, op2 }) => ({ type, op1: op2, op2: op1 }); +const invConflict = ({ type, op1, op2 }) => ({ type, op1: op2, op2: op1 }) const otherSide = function(side) { if (side === 'left') { - return 'right'; + return 'right' } else { - return 'left'; + return 'left' } -}; +} const checkConflict = function({ op1, op2, @@ -86,15 +86,15 @@ const checkConflict = function({ // We should get the same conflict with xf(op1, op2, left) and xf(op2, op1, right). if (expectConflict != null) { if (!expectConflict.op1) { - expectConflict.op1 = type.normalize(op1); + expectConflict.op1 = type.normalize(op1) } if (!expectConflict.op2) { - expectConflict.op2 = type.normalize(op2); + expectConflict.op2 = type.normalize(op2) } } return (() => { - const result = []; + const result = [] for (var [side_, op1_, op2_, ec] of [ [side, op1, op2, expectConflict], [ @@ -106,33 +106,33 @@ const checkConflict = function({ ]) { try { // d -> log('tryTransform', side_, op1_, op2_) - const { ok, conflict } = type.tryTransform(op1_, op2_, side_); + const { ok, conflict } = type.tryTransform(op1_, op2_, side_) if (ec == null) { // We don't care what the result is here; just that it doesn't conflict. - result.push(assert(ok)); + result.push(assert(ok)) } else { - assert(!ok, `Conflict erroneously succeeded (${side_})`); + assert(!ok, `Conflict erroneously succeeded (${side_})`) // d -> log('conflict', conflict) - conflict.op1 = type.normalize(conflict.op1); - conflict.op2 = type.normalize(conflict.op2); - result.push(assert.deepStrictEqual(conflict, ec)); + conflict.op1 = type.normalize(conflict.op1) + conflict.op2 = type.normalize(conflict.op2) + result.push(assert.deepStrictEqual(conflict, ec)) } } catch (e) { d(function() { - console.error('FAIL! Repro with:'); + console.error('FAIL! Repro with:') console.log( `tryTransform(${JSON.stringify(op1_)}, ${JSON.stringify( op2_ )}, '${side_}')` - ); - return type.tryTransform(op1_, op2_, side_); - }); - throw e; + ) + return type.tryTransform(op1_, op2_, side_) + }) + throw e } } - return result; - })(); -}; + return result + })() +} const xf = function({ op1, @@ -145,373 +145,364 @@ const xf = function({ expectRight }) { if (expect !== undefined) { - expectLeft = expectRight = expect; + expectLeft = expectRight = expect } if (conflict !== undefined) { - conflictLeft = conflictRight = conflict; + conflictLeft = conflictRight = conflict } return (() => { - const result1 = []; + const result1 = [] for (var [side, e, c] of [ ['left', expectLeft, conflictLeft], ['right', expectRight, conflictRight] ]) { - checkConflict({ op1, op2, side, conflict: c, expect: e }); + checkConflict({ op1, op2, side, conflict: c, expect: e }) try { const result = c != null ? type.transformNoConflict(op1, op2, side) - : transform(op1, op2, side); - result1.push(assert.deepStrictEqual(result, e)); + : transform(op1, op2, side) + result1.push(assert.deepStrictEqual(result, e)) } catch (error) { - e = error; + e = error d(function() { - console.error('FAIL! Repro with:'); + console.error('FAIL! Repro with:') return console.log( `transform(${JSON.stringify(op1)}, ${JSON.stringify( op2 )}, '${side}')` - ); - }); + ) + }) // if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side - throw e; + throw e } } - return result1; - })(); -}; + return result1 + })() +} const diamond = function({ doc, op1, op2 }) { - let doc1, doc12, doc2, doc21, op1_, op2_; - type.setDebug(false); + let doc1, doc12, doc2, doc21, op1_, op2_ + type.setDebug(false) try { // Test that the diamond property holds - op1_ = transform(op1, op2, 'left'); - op2_ = transform(op2, op1, 'right'); + op1_ = transform(op1, op2, 'left') + op2_ = transform(op2, op1, 'right') - doc1 = type.apply(doc, op1); - doc2 = type.apply(doc, op2); + doc1 = type.apply(doc, op1) + doc2 = type.apply(doc, op2) - doc12 = type.apply(doc1, op2_); - doc21 = type.apply(doc2, op1_); + doc12 = type.apply(doc1, op2_) + doc21 = type.apply(doc2, op1_) - return assert.deepStrictEqual(doc12, doc21); + return assert.deepStrictEqual(doc12, doc21) } catch (e) { - log.quiet = false; - log('\nOops! Diamond property does not hold. Given document', doc); - log('op1 ', op1, ' / op2', op2); - log('op1_', op1_, ' / op2_', op2_); - log('---- 1'); - log('op1', op1, '->', doc1); - log('op2', op2_, '->', doc12); - log('---- 2'); - log('op2', op2, '->', doc2); - log('op1', op1_, '->', doc21); - log('----------'); - log(doc12, '!=', doc21); - throw e; + log.quiet = false + log('\nOops! Diamond property does not hold. Given document', doc) + log('op1 ', op1, ' / op2', op2) + log('op1_', op1_, ' / op2_', op2_) + log('---- 1') + log('op1', op1, '->', doc1) + log('op2', op2_, '->', doc12) + log('---- 2') + log('op2', op2, '->', doc2) + log('op1', op1_, '->', doc21) + log('----------') + log(doc12, '!=', doc21) + throw e } -}; +} const path = function(path, { op, expect }) { if (expect === undefined) { - expect = path.slice(); + expect = path.slice() } - const result = type.transformPosition(path, op); - assert.deepStrictEqual(result, expect); + const result = type.transformPosition(path, op) + assert.deepStrictEqual(result, expect) // Also check that path+X = expect+X - const path2 = path.concat('x'); - const expect2 = expect != null ? expect.concat('x') : null; + const path2 = path.concat('x') + const expect2 = expect != null ? expect.concat('x') : null - const result2 = type.transformPosition(path2, op); - return assert.deepStrictEqual(result2, expect2); -}; + const result2 = type.transformPosition(path2, op) + return assert.deepStrictEqual(result2, expect2) +} describe('json1', function() { before(function() { - type.registerSubtype(require('ot-simple')); - return type.setDebug(true); - }); - after(() => type.setDebug(false)); + type.registerSubtype(require('ot-simple')) + return type.setDebug(true) + }) + after(() => type.setDebug(false)) describe('checkOp', function() { const pass = function(op) { try { - return type.checkValidOp(op); + return type.checkValidOp(op) } catch (e) { - console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`); - throw e; + console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`) + throw e } - }; + } const fail = function(op) { try { - return assert.throws(() => type.checkValidOp(op)); + return assert.throws(() => type.checkValidOp(op)) } catch (e) { - console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`); - console.log('Should throw!'); - throw e; + console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`) + console.log('Should throw!') + throw e } - }; + } it('allows some simple valid ops', function() { - pass(null); - pass([{ i: [1, 2, 3] }]); - pass([{ r: {} }]); - pass([['x', { p: 0 }], ['y', { d: 0 }]]); - pass([[0, { p: 0 }], [10, { d: 0 }]]); - pass([ - ['a', { p: 0 }], - ['b', { d: 0 }], - ['x', { p: 1 }], - ['y', { d: 1 }] - ]); - pass([{ e: 'hi', et: 'simple' }]); - pass([{ es: ['hi'] }]); - return pass([{ ena: 5 }]); - }); + pass(null) + pass([{ i: [1, 2, 3] }]) + pass([{ r: {} }]) + pass([['x', { p: 0 }], ['y', { d: 0 }]]) + pass([[0, { p: 0 }], [10, { d: 0 }]]) + pass([['a', { p: 0 }], ['b', { d: 0 }], ['x', { p: 1 }], ['y', { d: 1 }]]) + pass([{ e: 'hi', et: 'simple' }]) + pass([{ es: ['hi'] }]) + return pass([{ ena: 5 }]) + }) it('disallows invalid syntax', function() { - fail(undefined); - fail({}); - fail('hi'); - fail(true); - fail(false); - fail(0); - fail(10); - fail([{}]); - fail([{ invalid: true }]); - fail([10, {}]); - fail([10, { invalid: true }]); - return fail([10, 'hi']); - }); + fail(undefined) + fail({}) + fail('hi') + fail(true) + fail(false) + fail(0) + fail(10) + fail([{}]) + fail([{ invalid: true }]) + fail([10, {}]) + fail([10, { invalid: true }]) + return fail([10, 'hi']) + }) it('throws if there is any empty leaves', function() { - fail([]); - fail(['x']); - fail(['x', {}]); - fail(['x', []]); - fail([10]); - fail([10, {}]); - return fail([10, []]); - }); + fail([]) + fail(['x']) + fail(['x', {}]) + fail(['x', []]) + fail([10]) + fail([10, {}]) + return fail([10, []]) + }) it('ensures path components are non-zero integers or strings', function() { - fail([-1, { r: {} }]); - fail([0.5, { r: {} }]); - fail([true, { r: {} }]); - fail([false, { r: {} }]); - fail([null, { r: {} }]); - return fail([undefined, { r: {} }]); - }); + fail([-1, { r: {} }]) + fail([0.5, { r: {} }]) + fail([true, { r: {} }]) + fail([false, { r: {} }]) + fail([null, { r: {} }]) + return fail([undefined, { r: {} }]) + }) it('does not allow two pickups or two drops in a component', function() { - fail([{ p: 0, r: {} }]); - fail([{ p: 1, r: {} }]); - fail(['x', { p: 0, r: {} }]); - fail(['x', { p: 1, r: {} }]); + fail([{ p: 0, r: {} }]) + fail([{ p: 1, r: {} }]) + fail(['x', { p: 0, r: {} }]) + fail(['x', { p: 1, r: {} }]) - fail([{ d: 0, i: 'hi' }]); - fail([{ d: 1, i: 'hi' }]); - fail([10, { d: 0, i: 'hi' }]); - return fail([10, { d: 1, i: 'hi' }]); - }); + fail([{ d: 0, i: 'hi' }]) + fail([{ d: 1, i: 'hi' }]) + fail([10, { d: 0, i: 'hi' }]) + return fail([10, { d: 1, i: 'hi' }]) + }) it('throws if there are mismatched pickups / drops', function() { - fail([{ p: 0 }]); - fail([{ d: 0 }]); - fail(['x', { p: 0 }]); - fail([10, { p: 0 }]); - fail(['x', { d: 0 }]); - return fail([10, { d: 0 }]); - }); + fail([{ p: 0 }]) + fail([{ d: 0 }]) + fail(['x', { p: 0 }]) + fail([10, { p: 0 }]) + fail(['x', { d: 0 }]) + return fail([10, { d: 0 }]) + }) it('throws if pick/drop indexes dont start at 0', function() { - fail([['x', { p: 1 }], ['y', { d: 1 }]]); - return fail([[10, { p: 1 }], [20, { d: 1 }]]); - }); + fail([['x', { p: 1 }], ['y', { d: 1 }]]) + return fail([[10, { p: 1 }], [20, { d: 1 }]]) + }) it('throws if a descent starts with an edit', () => - fail([10, [{ i: 'hi' }]])); + fail([10, [{ i: 'hi' }]])) it('throws if descents are out of order', function() { - fail(['x', ['b', { r: {} }], ['a', { r: {} }]]); - fail(['x', [10, { r: {} }], [5, { r: {} }]]); - fail(['x', ['a', { r: {} }], [5, { r: {} }]]); - fail(['x', ['a', { r: {} }], ['a', { r: {} }]]); - return fail(['x', [10, { r: {} }], [10, { r: {} }]]); - }); + fail(['x', ['b', { r: {} }], ['a', { r: {} }]]) + fail(['x', [10, { r: {} }], [5, { r: {} }]]) + fail(['x', ['a', { r: {} }], [5, { r: {} }]]) + fail(['x', ['a', { r: {} }], ['a', { r: {} }]]) + return fail(['x', [10, { r: {} }], [10, { r: {} }]]) + }) it('throws if descents start with the same scalar', () => - fail(['x', ['a', { r: {} }], ['a', { e: {} }]])); + fail(['x', ['a', { r: {} }], ['a', { e: {} }]])) it('throws if descents have two adjacent edits', function() { - fail([{ r: {} }, { p: 0 }]); - fail(['x', { r: {} }, { p: 0 }]); - return fail(['x', { r: {} }, { p: 0 }, 'y', { r: {} }]); - }); + fail([{ r: {} }, { p: 0 }]) + fail(['x', { r: {} }, { p: 0 }]) + return fail(['x', { r: {} }, { p: 0 }, 'y', { r: {} }]) + }) it.skip('does not allow ops to overwrite their own inserted data', function() { - fail([{ i: { x: 5 } }, 'x', { i: 6 }]); - return fail([{ i: ['hi'] }, 0, { i: 'omg' }]); - }); + fail([{ i: { x: 5 } }, 'x', { i: 6 }]) + return fail([{ i: ['hi'] }, 0, { i: 'omg' }]) + }) it.skip('does not allow immediate data directly parented in other immediate data', function() { - fail([{ i: {} }, 'x', { i: 5 }]); - fail([{ i: { x: 5 } }, 'x', 'y', { i: 6 }]); - return fail([{ i: [] }, 0, { i: 5 }]); - }); + fail([{ i: {} }, 'x', { i: 5 }]) + fail([{ i: { x: 5 } }, 'x', 'y', { i: 6 }]) + return fail([{ i: [] }, 0, { i: 5 }]) + }) it('does not allow the final item to be a single descent', () => - fail(['a', ['b', { r: {} }]])); // It should be ['a', 'b', r:{}] + fail(['a', ['b', { r: {} }]])) // It should be ['a', 'b', r:{}] it('does not allow anything after the descents at the end', function() { - fail([[1, { r: {} }], [2, { r: {} }], 5]); - fail([[1, { r: {} }], [2, { r: {} }], 5, { r: {} }]); - return fail([[1, { r: {} }], [2, { r: {} }], { r: {} }]); - }); + fail([[1, { r: {} }], [2, { r: {} }], 5]) + fail([[1, { r: {} }], [2, { r: {} }], 5, { r: {} }]) + return fail([[1, { r: {} }], [2, { r: {} }], { r: {} }]) + }) it('allows removes inside removes', function() { - pass(['x', { r: true }, 'y', { r: true }]); - pass(['x', { r: {} }, 'y', { r: true }]); + pass(['x', { r: true }, 'y', { r: true }]) + pass(['x', { r: {} }, 'y', { r: true }]) pass([ ['x', { r: true }, 'y', { p: 0 }, 'z', { r: true }], ['y', { d: 0 }] - ]); + ]) return pass([ ['x', { r: {} }, 'y', { p: 0 }, 'z', { r: true }], ['y', { d: 0 }] - ]); - }); + ]) + }) it('allows inserts inside inserts', function() { - pass([1, { i: {} }, 'x', { i: 10 }]); + pass([1, { i: {} }, 'x', { i: 10 }]) return pass([ [0, 'x', { p: 0 }], [1, { i: {} }, 'x', { d: 0 }, 'y', { i: 10 }] - ]); - }); + ]) + }) it.skip('fails if the operation drops items inside something it picked up', function() { - fail(['x', { r: true }, 1, { i: 'hi' }]); - fail(['x', { d: 0 }, 1, { p: 0 }]); - return fail([{ r: true }, 1, { p: 0, d: 0 }]); - }); + fail(['x', { r: true }, 1, { i: 'hi' }]) + fail(['x', { d: 0 }, 1, { p: 0 }]) + return fail([{ r: true }, 1, { p: 0, d: 0 }]) + }) return describe('edit', function() { it('requires all edits to specify their type', function() { - fail([{ e: {} }]); - fail([5, { e: {} }]); - return pass([{ e: {}, et: 'simple' }]); - }); + fail([{ e: {} }]) + fail([5, { e: {} }]) + return pass([{ e: {}, et: 'simple' }]) + }) it('allows edits to have null or false for the operation', function() { // These aren't valid operations according to the simple type, but the // type doesn't define a checkValidOp so we wouldn't be able to tell // anyway. - pass([{ e: null, et: 'simple' }]); - pass([5, { e: null, et: 'simple' }]); - pass([{ e: false, et: 'simple' }]); - return pass([5, { e: false, et: 'simple' }]); - }); + pass([{ e: null, et: 'simple' }]) + pass([5, { e: null, et: 'simple' }]) + pass([{ e: false, et: 'simple' }]) + return pass([5, { e: false, et: 'simple' }]) + }) it('does not allow an edit to use an unregistered type', function() { - fail([{ e: {}, et: 'an undefined type' }]); - return fail([{ e: null, et: 'an undefined type' }]); - }); + fail([{ e: {}, et: 'an undefined type' }]) + return fail([{ e: null, et: 'an undefined type' }]) + }) it('does not allow two edits in the same operation', function() { - fail([{ e: {}, et: 'simple', es: [1, 2, 3] }]); - fail([{ es: [], ena: 5 }]); - return fail([{ e: {}, et: 'simple', ena: 5 }]); - }); + fail([{ e: {}, et: 'simple', es: [1, 2, 3] }]) + fail([{ es: [], ena: 5 }]) + return fail([{ e: {}, et: 'simple', ena: 5 }]) + }) - it('fails if the type is missing', () => - fail([{ et: 'missing', e: {} }])); + it('fails if the type is missing', () => fail([{ et: 'missing', e: {} }])) - it('does not allow anything inside an edited subtree'); + it('does not allow anything inside an edited subtree') it.skip('does not allow an edit inside removed or picked up content', function() { - fail([{ r: true }, 1, { es: ['hi'] }]); - pass([1, { r: true }, 1, { es: ['hi'] }]); - fail(['x', { r: true }, 1, { es: ['hi'] }]); - pass([[1, { p: 0 }, 1, { es: ['hi'] }], [2, { d: 0 }]]); - fail([['x', { p: 0 }, 1, { es: ['hi'] }], ['y', { d: 0 }]]); + fail([{ r: true }, 1, { es: ['hi'] }]) + pass([1, { r: true }, 1, { es: ['hi'] }]) + fail(['x', { r: true }, 1, { es: ['hi'] }]) + pass([[1, { p: 0 }, 1, { es: ['hi'] }], [2, { d: 0 }]]) + fail([['x', { p: 0 }, 1, { es: ['hi'] }], ['y', { d: 0 }]]) // This is actually ok. - return pass([0, { p: 0 }, ['a', { es: [], r: true }], ['x', { d: 0 }]]); - }); + return pass([0, { p: 0 }, ['a', { es: [], r: true }], ['x', { d: 0 }]]) + }) return it.skip('does not allow you to drop inside something that was removed', function() { // These insert into the next list item - pass([[1, { r: true }, 1, { d: 0 }], [2, { p: 0 }]]); - pass([1, { p: 0 }, 'x', { d: 0 }]); + pass([[1, { r: true }, 1, { d: 0 }], [2, { p: 0 }]]) + pass([1, { p: 0 }, 'x', { d: 0 }]) // But this is not ok. - return fail(['x', { p: 0 }, 'a', { d: 0 }]); - }); - }); - }); + return fail(['x', { p: 0 }, 'a', { d: 0 }]) + }) + }) + }) describe('normalize', function() { const n = function(opIn, expect) { if (expect === undefined) { - expect = opIn; + expect = opIn } - const op = type.normalize(opIn); - return assert.deepStrictEqual(op, expect); - }; + const op = type.normalize(opIn) + return assert.deepStrictEqual(op, expect) + } it('does the right thing for noops', function() { - n(null); - return n([], null); - }); + n(null) + return n([], null) + }) it('normalizes some regular ops', function() { - n([{ i: 'hi' }]); - n([{ i: 'hi' }, 1, 2, 3], [{ i: 'hi' }]); - n([[1, 2, 3, { p: 0 }], [1, 2, 3, { d: 0 }]], [1, 2, 3, { p: 0, d: 0 }]); + n([{ i: 'hi' }]) + n([{ i: 'hi' }, 1, 2, 3], [{ i: 'hi' }]) + n([[1, 2, 3, { p: 0 }], [1, 2, 3, { d: 0 }]], [1, 2, 3, { p: 0, d: 0 }]) n( [[1, 2, 3, { p: 0 }], [1, 2, 30, { d: 0 }]], [1, 2, [3, { p: 0 }], [30, { d: 0 }]] - ); + ) return n( [[1, 2, 30, { p: 0 }], [1, 2, 3, { d: 0 }]], [1, 2, [3, { d: 0 }], [30, { p: 0 }]] - ); - }); + ) + }) - it('will let you insert null', () => n([{ i: null }])); + it('will let you insert null', () => n([{ i: null }])) it('normalizes embedded ops when available', function() { - n([{ es: [0, 'hi'] }], [{ es: ['hi'] }]); - n([{ et: 'text-unicode', e: ['hi'] }], [{ es: ['hi'] }]); - n([{ et: 'text-unicode', e: [0, 'hi'] }], [{ es: ['hi'] }]); - n([{ et: 'simple', e: {} }]); - n([{ et: 'number', e: 5 }], [{ ena: 5 }]); - return n([{ ena: 5 }]); - }); + n([{ es: [0, 'hi'] }], [{ es: ['hi'] }]) + n([{ et: 'text-unicode', e: ['hi'] }], [{ es: ['hi'] }]) + n([{ et: 'text-unicode', e: [0, 'hi'] }], [{ es: ['hi'] }]) + n([{ et: 'simple', e: {} }]) + n([{ et: 'number', e: 5 }], [{ ena: 5 }]) + return n([{ ena: 5 }]) + }) it.skip('normalizes embedded removes', function() { - n([1, { r: true }, 2, { r: true }], [1, { r: true }]); - return n([{ r: true }, 2, { r: true }], [{ r: true }]); - }); + n([1, { r: true }, 2, { r: true }], [1, { r: true }]) + return n([{ r: true }, 2, { r: true }], [{ r: true }]) + }) it('throws if the type is missing', () => // Not sure if this is the best behaviour but ... eh. - assert.throws(() => n([{ et: 'missing', e: {} }]))); + assert.throws(() => n([{ et: 'missing', e: {} }]))) return it('corrects weird pick and drop ids', () => - n( - [['x', { p: 1 }], ['y', { d: 1 }]], - [['x', { p: 0 }], ['y', { d: 0 }]] - )); - }); + n([['x', { p: 1 }], ['y', { d: 1 }]], [['x', { p: 0 }], ['y', { d: 0 }]])) + }) // ****** Apply ****** @@ -521,54 +512,54 @@ describe('json1', function() { doc: [], op: [0, { i: 17 }], expect: [17] - }); + }) return apply({ doc: {}, op: ['x', { i: 5 }], expect: { x: 5 } - }); - }); + }) + }) it('can edit the root', function() { apply({ doc: { x: 5 }, op: [{ r: true }], expect: undefined - }); + }) apply({ doc: '', op: [{ r: true }], expect: undefined - }); + }) apply({ doc: 'hi', op: [{ r: true, i: null }], expect: null - }); + }) apply({ doc: 'hi', op: [{ es: [2, ' there'] }], expect: 'hi there' - }); + }) - assert.throws(() => type.apply(null, [{ i: 5 }])); + assert.throws(() => type.apply(null, [{ i: 5 }])) apply({ doc: undefined, op: [{ i: 5 }], expect: 5 - }); + }) return apply({ doc: { x: 5 }, op: [{ r: {}, i: [1, 2, 3] }], expect: [1, 2, 3] - }); - }); + }) + }) // TODO: And an edit of the root. @@ -577,130 +568,130 @@ describe('json1', function() { doc: { x: 5 }, op: [['x', { p: 0 }], ['y', { d: 0 }]], expect: { y: 5 } - })); + })) it('can move 2', () => apply({ doc: [0, 1, 2], op: [[1, { p: 0 }], [2, { d: 0 }]], expect: [0, 2, 1] - })); + })) it('can handle complex list index stuff', () => apply({ doc: [0, 1, 2, 3, 4, 5], op: [[1, { r: {}, i: 11 }], [2, { r: {}, i: 12 }]], expect: [0, 11, 12, 3, 4, 5] - })); + })) it('correctly handles interspersed descent and edits', () => apply({ doc: { x: { y: { was: 'y' }, was: 'x' } }, op: [['X', { d: 0 }, 'Y', { d: 1 }], ['x', { p: 0 }, 'y', { p: 1 }]], expect: { X: { Y: { was: 'y' }, was: 'x' } } - })); + })) it('can edit strings', () => apply({ doc: 'errd', op: [{ es: [2, 'maghe'] }], expect: 'ermagherd' - })); + })) it('can edit numbers', () => apply({ doc: 5, op: [{ ena: 10 }], expect: 15 - })); + })) it('can edit child numbers', () => apply({ doc: [20], op: [0, { ena: -100 }], expect: [-80] - })); + })) it('can edit subdocuments using an embedded type', () => apply({ doc: { str: 'hai' }, op: [{ e: { position: 2, text: 'wai' }, et: 'simple' }], expect: { str: 'hawaii' } - })); + })) it('applies edits after drops', () => apply({ doc: { x: 'yooo' }, op: [['x', { p: 0 }], ['y', { d: 0, es: ['sup'] }]], expect: { y: 'supyooo' } - })); + })) it('throws when the op traverses missing items', function() { - assert.throws(() => type.apply([0, 'hi'], [1, { p: 0 }, 'x', { d: 0 }])); - return assert.throws(() => type.apply({}, [{ p: 0 }, 'a', { d: 0 }])); - }); + assert.throws(() => type.apply([0, 'hi'], [1, { p: 0 }, 'x', { d: 0 }])) + return assert.throws(() => type.apply({}, [{ p: 0 }, 'a', { d: 0 }])) + }) return it('throws if the type is missing', () => - assert.throws(() => type.apply({}, [{ et: 'missing', e: {} }]))); - }); + assert.throws(() => type.apply({}, [{ et: 'missing', e: {} }]))) + }) describe('apply path', function() { it('does not modify path when op is unrelated', function() { - path(['a', 'b', 'c'], { op: null }); - path(['a', 'b', 'c'], { op: ['x', { i: 5 }] }); - path(['a', 'b', 'c'], { op: ['x', { r: true }] }); - path(['a', 'b', 'c'], { op: [['x', { p: 0 }], ['y', { d: 0 }]] }); - path([1, 2, 3], { op: [2, { i: 5 }] }); - path([1, 2, 3], { op: [1, 2, 4, { i: 5 }] }); - path([1], { op: [1, 2, { r: true }] }); - return path(['x'], { op: ['x', 'y', { r: true }] }); - }); + path(['a', 'b', 'c'], { op: null }) + path(['a', 'b', 'c'], { op: ['x', { i: 5 }] }) + path(['a', 'b', 'c'], { op: ['x', { r: true }] }) + path(['a', 'b', 'c'], { op: [['x', { p: 0 }], ['y', { d: 0 }]] }) + path([1, 2, 3], { op: [2, { i: 5 }] }) + path([1, 2, 3], { op: [1, 2, 4, { i: 5 }] }) + path([1], { op: [1, 2, { r: true }] }) + return path(['x'], { op: ['x', 'y', { r: true }] }) + }) it('adjusts list indicies', function() { - path([2], { op: [1, { i: 5 }], expect: [3] }); - path([2], { op: [2, { i: 5 }], expect: [3] }); - path([2], { op: [1, { r: true }], expect: [1] }); - path([2], { op: [[1, { p: 0 }], [3, { d: 0 }]], expect: [1] }); - path([2], { op: [[1, { d: 0 }], [3, { p: 0 }]], expect: [3] }); - return path([2], { op: [[2, { d: 0 }], [3, { p: 0 }]], expect: [3] }); - }); + path([2], { op: [1, { i: 5 }], expect: [3] }) + path([2], { op: [2, { i: 5 }], expect: [3] }) + path([2], { op: [1, { r: true }], expect: [1] }) + path([2], { op: [[1, { p: 0 }], [3, { d: 0 }]], expect: [1] }) + path([2], { op: [[1, { d: 0 }], [3, { p: 0 }]], expect: [3] }) + return path([2], { op: [[2, { d: 0 }], [3, { p: 0 }]], expect: [3] }) + }) it('returns null when the object at the path was removed', function() { - path(['x'], { op: [{ r: true }], expect: null }); - path(['x'], { op: ['x', { r: true }], expect: null }); - path([1], { op: [{ r: true }], expect: null }); - return path([1], { op: [1, { r: true }], expect: null }); - }); + path(['x'], { op: [{ r: true }], expect: null }) + path(['x'], { op: ['x', { r: true }], expect: null }) + path([1], { op: [{ r: true }], expect: null }) + return path([1], { op: [1, { r: true }], expect: null }) + }) it('moves the path', function() { path(['a', 'z'], { op: [['a', { p: 0 }], ['y', { d: 0 }]], expect: ['y', 'z'] - }); + }) path(['a', 'b'], { op: [['a', 'b', { p: 0 }], ['z', { d: 0 }]], expect: ['z'] - }); - path(['a', 'b'], { op: [['a', 'b', 'c', { p: 0 }], ['z', { d: 0 }]] }); - path([1, 2], { op: [[1, { p: 0 }], [10, { d: 0 }]], expect: [10, 2] }); - path([1, 2], { op: [[1, 2, { p: 0 }], [10, { d: 0 }]], expect: [10] }); - path([1, 2], { op: [1, [1, { d: 0 }], [2, { p: 0 }]], expect: [1, 1] }); - return path([1, 2], { op: [[1, 2, 3, { p: 0 }], [10, { d: 0 }]] }); - }); + }) + path(['a', 'b'], { op: [['a', 'b', 'c', { p: 0 }], ['z', { d: 0 }]] }) + path([1, 2], { op: [[1, { p: 0 }], [10, { d: 0 }]], expect: [10, 2] }) + path([1, 2], { op: [[1, 2, { p: 0 }], [10, { d: 0 }]], expect: [10] }) + path([1, 2], { op: [1, [1, { d: 0 }], [2, { p: 0 }]], expect: [1, 1] }) + return path([1, 2], { op: [[1, 2, 3, { p: 0 }], [10, { d: 0 }]] }) + }) it('handles pick parent and move', () => path(['a', 'b', 'c'], { op: [['a', { r: true }, 'b', { p: 0 }], ['x', { d: 0 }]], expect: ['x', 'c'] - })); + })) it('adjusts indicies under a pick', () => path(['a', 'b', 10], { op: [['a', { p: 0 }, 'b', 1, { r: true }], ['x', { d: 0 }]], expect: ['x', 'b', 9] - })); + })) - it.skip('gen ops', function() {}); + it.skip('gen ops', function() {}) // This should do something like: // - Generate a document // - Generate op, a random operation @@ -713,17 +704,17 @@ describe('json1', function() { path(['x', 'y', 'z', 1], { op: ['x', 'y', 'z', { es: ['abc'] }], expect: ['x', 'y', 'z', 4] - }); + }) path(['x', 'y', 'z', 1], { op: ['x', 'y', 'z', { es: ['💃'] }], expect: ['x', 'y', 'z', 2] - }); + }) return path(['x', 'y', 'z'], { op: ['x', 'y', 'z', { es: ['💃'] }], expect: ['x', 'y', 'z'] - }); - }); - }); + }) + }) + }) // ******* Compose ******* @@ -733,7 +724,7 @@ describe('json1', function() { op1: null, op2: null, expect: null - })); + })) describe('op1 drop', function() { it('vs remove', () => @@ -741,43 +732,43 @@ describe('json1', function() { op1: [['x', { p: 0 }], ['y', { d: 0 }]], op2: ['y', { r: true }], expect: ['x', { r: true }] - })); + })) it('vs remove parent', () => compose({ op1: [['x', { p: 0 }], ['y', 0, { d: 0 }]], op2: ['y', { r: true }], expect: [['x', { r: true }], ['y', { r: true }]] - })); + })) it('vs remove child', () => compose({ op1: [['x', { p: 0 }], ['y', { d: 0 }]], op2: ['y', 'a', { r: true }], expect: [['x', { p: 0 }, 'a', { r: true }], ['y', { d: 0 }]] - })); + })) it('vs remove and pick child', () => compose({ op1: [['x', { p: 0 }], ['y', { d: 0 }]], op2: [['y', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]], expect: [['x', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]] - })); + })) it('vs pick', () => compose({ op1: [['x', { p: 0 }], ['y', { d: 0 }]], op2: [['y', { p: 0 }], ['z', { d: 0 }]], expect: [['x', { p: 0 }], ['z', { d: 0 }]] - })); + })) return it('is transformed by op2 picks', () => compose({ op1: [['x', { p: 0 }], ['y', 10, { d: 0 }]], op2: ['y', 0, { r: true }], expect: [['x', { p: 0 }], ['y', [0, { r: true }], [9, { d: 0 }]]] - })); - }); + })) + }) describe('op1 insert', function() { it('vs remove', () => @@ -785,77 +776,77 @@ describe('json1', function() { op1: ['x', { i: { a: 'hi' } }], op2: ['x', { r: true }], expect: null - })); + })) it('vs remove parent', () => compose({ op1: ['x', 0, { i: { a: 'hi' } }], op2: ['x', { r: true }], expect: ['x', { r: true }] - })); + })) it('vs remove child', () => compose({ op1: ['x', { i: { a: 'hi', b: 'woo' } }], op2: ['x', 'a', { r: true }], expect: ['x', { i: { b: 'woo' } }] - })); + })) it('vs remove and pick child', () => compose({ op1: ['x', { i: { a: 'hi', b: 'woo' } }], op2: [['x', { r: true }, 'a', { p: 0 }], ['y', { d: 0 }]], expect: ['y', { i: 'hi' }] - })); + })) it('vs remove an embedded insert', () => compose({ op1: ['x', { i: {} }, 'y', { i: 'hi' }], op2: ['x', 'y', { r: true }], expect: ['x', { i: {} }] - })); + })) it('vs remove from an embedded insert', () => compose({ op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }], op2: ['x', 'y', 1, { r: true }], expect: ['x', { i: {} }, 'y', { i: [1, 3] }] - })); + })) it('picks the correct element of an embedded insert', () => compose({ op1: ['x', { i: ['a', 'b', 'c'] }, 1, { i: 'XX' }], op2: [['x', 1, { p: 0 }], ['y', { d: 0 }]], expect: [['x', { i: ['a', 'b', 'c'] }], ['y', { i: 'XX' }]] - })); + })) it('picks the correct element of an embedded insert 2', () => compose({ op1: ['x', { i: ['a', 'b', 'c'] }, 1, { i: 'XX' }], op2: [['x', 3, { p: 0 }], ['y', { d: 0 }]], // should grab 'c'. expect: [['x', { i: ['a', 'b'] }, 1, { i: 'XX' }], ['y', { i: 'c' }]] - })); + })) it('moves all children', () => compose({ op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }], op2: [['x', { p: 0 }], ['z', { d: 0 }]], expect: ['z', { i: {} }, 'y', { i: [1, 2, 3] }] - })); + })) it('removes all children', () => compose({ op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }], op2: ['x', { r: true }], expect: null - })); + })) it('removes all children when removed at the destination', () => compose({ op1: [['x', { p: 0 }], ['y', { d: 0 }, 0, { i: 'hi' }]], op2: ['y', { r: true }], expect: ['x', { r: true }] - })); + })) it('vs op2 insert', () => compose({ @@ -863,22 +854,22 @@ describe('json1', function() { op1: [{ i: {} }], op2: ['x', { i: 'hi' }], expect: [{ i: {} }, 'x', { i: 'hi' }] - })); + })) it('vs op2 string edit', () => compose({ op1: [{ i: 'hi' }], op2: [{ es: [2, ' there'] }], expect: [{ i: 'hi', es: [2, ' there'] }] - })); + })) return it('vs op2 number edit', () => compose({ op1: [{ i: 10 }], op2: [{ ena: 20 }], expect: [{ i: 10, ena: 20 }] - })); - }); + })) + }) describe('op1 edit', function() { it('removes the edit if the edited object is deleted', () => @@ -886,56 +877,56 @@ describe('json1', function() { op1: ['x', { es: ['hi'] }], op2: ['x', { r: true }], expect: ['x', { r: true }] - })); + })) it('removes the edit in an embedded insert 1', () => compose({ op1: ['x', { i: '', es: ['hi'] }], op2: ['x', { r: true }], expect: null - })); + })) it('removes the edit in an embedded insert 2', () => compose({ op1: ['x', { i: [''] }, 0, { es: ['hi'] }], op2: ['x', 0, { r: true }], expect: ['x', { i: [] }] - })); + })) it('composes string edits', () => compose({ op1: [{ es: ['hi'] }], op2: [{ es: [2, ' there'] }], expect: [{ es: ['hi there'] }] - })); + })) it('composes number edits', () => compose({ op1: [{ ena: 10 }], op2: [{ ena: -8 }], expect: [{ ena: 2 }] - })); + })) it('transforms and composes edits', () => compose({ op1: ['x', { es: ['hi'] }], op2: [['x', { p: 0 }], ['y', { d: 0, es: [2, ' there'] }]], expect: [['x', { p: 0 }], ['y', { d: 0, es: ['hi there'] }]] - })); + })) it('preserves inserts with edits', () => compose({ op1: ['x', { i: 'hi' }], op2: [['x', { p: 0 }], ['y', { d: 0, es: [' there'] }]], expect: ['y', { i: 'hi', es: [' there'] }] - })); + })) it('allows a different edit in the same location', () => compose({ op1: ['x', { es: ['hi'] }], op2: ['x', { r: true, i: 'yo', es: [2, ' there'] }], expect: ['x', { r: true, i: 'yo', es: [2, ' there'] }] - })); + })) return it('throws if the type is missing', () => assert.throws(() => @@ -943,15 +934,15 @@ describe('json1', function() { [{ et: 'missing', e: {} }], [{ et: 'missing', e: {} }] ) - )); - }); + )) + }) describe('op2 pick', () => it('gets untransformed by op1 drops', () => ({ op1: [5, { i: 'hi' }], op2: [6, { r: true }], expect: [5, { r: true, i: 'hi' }] - }))); + }))) describe('op1 insert containing a drop', () => it('vs pick at insert', () => @@ -959,7 +950,7 @@ describe('json1', function() { op1: [['x', { p: 0 }], ['y', { i: {} }, 'x', { d: 0 }]], op2: [['y', { p: 0 }], ['z', { d: 0 }]], expect: [['x', { p: 0 }], ['z', { i: {} }, 'x', { d: 0 }]] - }))); + }))) describe('fuzzer tests', () => it('complicated transform of indicies', () => @@ -967,7 +958,7 @@ describe('json1', function() { op1: [0, { p: 0 }, 'x', 2, { d: 0 }], op2: [0, 'x', 0, { r: true }], expect: [[0, { p: 0 }, 'x', 1, { d: 0 }], [1, 'x', 0, { r: true }]] - }))); + }))) describe('setnull interaction', function() { // Currently failing. @@ -976,22 +967,22 @@ describe('json1', function() { op1: [{ i: [] }, [0, { i: 'a' }], [1, { i: 'b' }]], op2: [[0, { p: 0 }], [1, { d: 0 }]], expect: [{ i: [] }, [0, { i: 'b' }], [1, { i: 'a' }]] - })); + })) it('lets a setnull child be moved', () => compose({ op1: ['list', { i: [] }, 0, { i: 'hi' }], op2: [['list', 0, { p: 0 }], ['z', { d: 0 }]], expect: [['list', { i: [] }], ['z', { i: 'hi' }]] - })); + })) return it('lets a setnull child get modified', () => compose({ op1: [{ i: [] }, 0, { i: ['a'] }], op2: [0, 0, { r: 'a', i: 'b' }], expect: [{ i: [] }, 0, { i: [] }, 0, { i: 'b' }] - })); - }); + })) + }) //expect: [{i:[]}, 0, {i:['b']}] # Maybe better?? return describe('regression', function() { @@ -1000,7 +991,7 @@ describe('json1', function() { op1: [[0, { p: 0 }], [2, { d: 0 }]], op2: [[0, { p: 0 }], [1, { d: 0 }]], expect: [[0, { p: 1 }], [1, { p: 0, d: 0 }], [2, { d: 1 }]] - })); + })) it('skips op2 drops when calculating op1 drop index complex', () => compose({ @@ -1008,7 +999,7 @@ describe('json1', function() { op2: [[0, { p: 0 }], [1, { d: 0 }]], // expect: [[0, {p:1}], [1, {d:0, p:0}], [2, d:1]] expect: [[0, { p: 1 }], [1, { p: 0, d: 0 }], [2, { d: 1 }]] - })); + })) it('3', () => compose({ @@ -1016,7 +1007,7 @@ describe('json1', function() { op2: [1, { p: 0 }, 0, { d: 0 }], // ... it'd be way more consistent to drop the null separately rather than merging it?? expect: [{ i: [[]] }, [0, { i: '' }], [1, 0, { i: null }]] - })); + })) return it('4', () => compose({ @@ -1028,9 +1019,9 @@ describe('json1', function() { [0, ['a', { r: true }], ['b', { d: 0 }], ['c', { i: 'd' }]], [2, { p: 0 }] ] - })); - }); - }); + })) + }) + }) // *** Old stuff describe('old compose', function() { @@ -1044,21 +1035,21 @@ describe('json1', function() { ['x', { p: 1 }], ['y', { d: 1 }] ] - }); + }) return compose({ op1: [2, { i: 'hi' }], op2: [0, 'x', { r: true }], expect: [[0, 'x', { r: true }], [2, { i: 'hi' }]] - }); - }); + }) + }) it('translates drops in objects', () => compose({ op1: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], // x.a -> x.b op2: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y expect: [['x', { p: 0 }, 'a', { p: 1 }], ['y', { d: 0 }, 'b', { d: 1 }]] - })); // x.a -> y.b, x -> y + })) // x.a -> y.b, x -> y it('untranslates picks in objects', () => compose({ @@ -1069,29 +1060,29 @@ describe('json1', function() { ['y', { d: 0 }], ['z', { d: 1 }] ] - })); // x.a -> z, x -> y + })) // x.a -> z, x -> y it('insert gets carried wholesale', () => compose({ op1: ['x', { i: 'hi there' }], op2: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y expect: ['y', { i: 'hi there' }] - })); + })) it('insert gets edited by the op', () => compose({ op1: ['x', { i: { a: 1, b: 2, c: 3 } }], op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], expect: [['x', { i: { b: 2, c: 3 } }], ['y', { i: 1 }]] - })); + })) return it('does not merge mutual inserts', () => compose({ op1: [{ i: {} }], op2: ['x', { i: 'hi' }], expect: [{ i: {} }, 'x', { i: 'hi' }] - })); - }); + })) + }) // TODO: List nonsense. @@ -1106,19 +1097,19 @@ describe('json1', function() { op1: [['x', { p: 0 }], ['y', { d: 0 }]], op2: ['x', { r: true }], expect: null - })); + })) it('vs delete parent', () => xf({ op1: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], op2: ['x', { r: true }], expect: null - })); + })) it('vs delete parent 2', () => xf({ op1: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], op2: ['x', { r: true }], expect: null - })); + })) it('vs pick', () => xf({ @@ -1127,13 +1118,13 @@ describe('json1', function() { // Consider adding a conflict for this case. expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], expectRight: null - })); + })) it('vs pick parent', () => xf({ op1: [['x', 'a', { p: 0 }], ['z', { d: 0 }]], op2: [['x', { p: 0 }], ['y', { d: 0 }]], expect: [['y', 'a', { p: 0 }], ['z', { d: 0 }]] - })); + })) it('vs pick and pick child', () => xf({ @@ -1151,28 +1142,28 @@ describe('json1', function() { ['xc', { d: 1 }] ], expectRight: [['b', 'c', { p: 0 }], ['xc', { d: 0 }]] - })); + })) it('vs edit', () => xf({ op1: [['x', { p: 0 }], ['z', { d: 0 }]], op2: ['x', { es: ['hi'] }], expect: [['x', { p: 0 }], ['z', { d: 0 }]] - })); + })) it('vs delete, drop', () => xf({ op1: [['x', { p: 0 }], ['y', { d: 0 }]], op2: [['a', { p: 0 }], ['x', { r: 0, d: 0 }]], expect: null - })); + })) it('vs delete, insert', () => xf({ op1: [['x', { p: 0 }], ['y', { d: 0 }]], op2: ['x', { r: 0, i: 5 }], expect: null - })); + })) it( 'vs pick, drop to self', @@ -1189,7 +1180,7 @@ describe('json1', function() { op2: [['a', 1, { p: 0 }], ['y', { d: 0 }]], expect: null }) - ); + ) it('vs pick, drop', () => xf({ @@ -1197,7 +1188,7 @@ describe('json1', function() { op2: [['a', { p: 0 }], ['x', { p: 1, d: 0 }], ['y', { d: 1 }]], // a->x, x->y expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], expectRight: null - })); + })) it('vs pick, insert', () => xf({ @@ -1205,15 +1196,15 @@ describe('json1', function() { op2: [['x', { p: 0, i: 5 }], ['y', { d: 0 }]], expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], expectRight: null - })); + })) return it('vs pick, edit', () => ({ op1: [['x', { p: 0 }], ['z', { d: 0 }]], op2: [['x', { es: ['hi'], p: 0 }], ['y', { d: 0 }]], expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], expectRight: null - })); - }); + })) + }) describe('op1 delete', function() { it('vs delete', () => @@ -1221,33 +1212,33 @@ describe('json1', function() { op1: ['x', { r: true }], op2: ['x', { r: true }], expect: null - })); + })) it('vs delete parent', () => xf({ op1: ['x', 'a', { r: true }], op2: ['x', { r: true }], expect: null - })); + })) it('vs pick', () => xf({ op1: ['x', { r: true }], op2: [['x', { p: 0 }], ['y', { d: 0 }]], expect: ['y', { r: true }] - })); + })) it('vs pick parent', () => xf({ op1: ['x', 'a', { r: true }], op2: [['x', { p: 0 }], ['y', { d: 0 }]], expect: ['y', 'a', { r: true }] - })); + })) it('vs pick and drop', () => xf({ op1: ['x', { r: true }], op2: [['a', { p: 0 }], ['x', { d: 0, p: 1 }], ['z', { d: 1 }]], expect: ['z', { r: true }] - })); + })) it('vs edit', () => xf({ @@ -1255,14 +1246,14 @@ describe('json1', function() { op2: ['x', { es: ['hi'] }], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: ['x', { r: true }] - })); + })) it('vs move and insert', () => xf({ op1: ['a', 1, { r: true }], op2: [['a', { p: 0 }], ['b', { d: 0 }, [0, { i: 5 }], [1, { i: 5 }]]], expect: ['b', 3, { r: true }] - })); + })) return describe('vs pick child', function() { it('move in', () => @@ -1271,21 +1262,21 @@ describe('json1', function() { op2: [['a', { p: 0 }], ['x', 'y', { d: 0 }]], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: ['x', { r: true }, 'y', { r: true }] - })); // Also ok if its just x, r:true + })) // Also ok if its just x, r:true it('move across', () => xf({ op1: ['x', { r: true }], // delete doc.x op2: ['x', ['y', { p: 0 }], ['z', { d: 0 }]], expect: ['x', { r: true }] - })); + })) it('move out', () => xf({ op1: ['x', { r: true }], op2: [['x', 'y', { p: 0 }], ['y', { d: 0 }]], // move doc.x.y -> doc.y expect: [['x', { r: true }], ['y', { r: true }]] - })); // delete doc.x and doc.y + })) // delete doc.x and doc.y it('multiple out', () => xf({ @@ -1296,7 +1287,7 @@ describe('json1', function() { ['z', { d: 1 }] ], expect: [['x', { r: true }], ['y', { r: true }], ['z', { r: true }]] - })); + })) it('chain out', () => xf({ @@ -1311,7 +1302,7 @@ describe('json1', function() { op2: [['y', { p: 0 }], ['z', 'a', { d: 0 }]] }, // cMv(['y'], ['z', 'a']) expect: [['x', { r: true }], ['z', { r: true }, 'a', { r: true }]] - })); + })) return it('mess', () => xf({ @@ -1323,9 +1314,9 @@ describe('json1', function() { ['y', { r: true }, 'z', { p: 0 }], ['z', { d: 0 }] ] - })); - }); - }); + })) + }) + }) describe('op1 drop', function() { it('vs delete parent', () => @@ -1334,7 +1325,7 @@ describe('json1', function() { op2: ['y', { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: ['x', { r: true }] - })); + })) it('vs a cancelled parent', () => xf({ @@ -1350,14 +1341,14 @@ describe('json1', function() { op1: [['y', { p: 0 }], ['z', 'a', { d: 0 }]] }, // c1: cMv(['y'], ['z', 'a']) expect: ['y', { r: true }] - })); + })) it('vs pick parent', () => xf({ op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], op2: [['y', { p: 0 }], ['z', { d: 0 }]], expect: [['x', { p: 0 }], ['z', 'a', { d: 0 }]] - })); + })) it('vs drop', () => xf({ @@ -1366,7 +1357,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: [['x', { p: 0 }], ['z', { r: true, d: 0 }]], expectRight: ['x', { r: true }] - })); + })) it('vs drop (list)', () => xf({ @@ -1374,7 +1365,7 @@ describe('json1', function() { op2: [[5, { d: 0 }], [10, { p: 0 }]], expectLeft: [[0, { p: 0 }], [4, { d: 0 }]], expectRight: [[0, { p: 0 }], [5, { d: 0 }]] - })); + })) it('vs drop (chained)', () => xf({ @@ -1394,7 +1385,7 @@ describe('json1', function() { ['z', { r: true, d: 1 }, 'a', { d: 0 }] ], expectRight: [['a', { r: true }], ['x', { r: true }]] - })); + })) it('vs insert', () => xf({ @@ -1403,7 +1394,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: [['x', { p: 0 }], ['z', { r: true, d: 0 }]], expectRight: ['x', { r: true }] - })); + })) it('vs pick (a->b->c vs b->x)', () => xf({ @@ -1416,7 +1407,7 @@ describe('json1', function() { ['x', { p: 1 }] ], expectRight: [['a', { p: 0 }], ['b', { d: 0 }]] - })); + })) return describe.skip('vs move inside me', function() { // Note: This is *not* blackholeing! The edits are totally fine; we @@ -1431,7 +1422,7 @@ describe('json1', function() { ['y', { d: 1 }, 'x', { d: 0 }] ], expectRight: null - })); + })) it('in lists', () => xf({ @@ -1439,7 +1430,7 @@ describe('json1', function() { op2: [[0, 'y', { d: 0 }], [1, { p: 0 }]], expectLeft: [0, { p: 0, d: 1 }, ['x', { d: 0 }], ['y', { p: 1 }]], expectRight: null - })); + })) return it('multiple', () => xf({ @@ -1456,9 +1447,9 @@ describe('json1', function() { ['x', { d: 1 }, ['a', { d: 0 }], ['b', { d: 2 }]] ], expectRight: null - })); - }); - }); + })) + }) + }) describe('op1 insert', function() { it('vs delete parent', () => @@ -1467,14 +1458,14 @@ describe('json1', function() { op2: ['y', { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: null - })); + })) it('vs pick parent', () => xf({ op1: ['y', 'a', { i: 5 }], op2: [['y', { p: 0 }], ['z', { d: 0 }]], expect: ['z', 'a', { i: 5 }] - })); + })) it('vs drop', () => xf({ @@ -1483,7 +1474,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: ['z', { r: true, i: 5 }], expectRight: null - })); + })) it('vs insert', () => xf({ @@ -1492,7 +1483,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: ['z', { r: true, i: 5 }], expectRight: null - })); + })) it('vs insert at list position', () => xf({ @@ -1500,14 +1491,14 @@ describe('json1', function() { op2: [5, { i: 'there' }], expectLeft: [5, { i: 'hi' }], expectRight: [6, { i: 'hi' }] - })); + })) it('vs identical insert', () => xf({ op1: ['z', { i: 5 }], op2: ['z', { i: 5 }], expect: null - })); + })) // This is the new setNull for setting up schemas it('vs embedded inserts', function() { @@ -1515,19 +1506,19 @@ describe('json1', function() { op1: ['x', { i: {} }], op2: ['x', { i: {} }, 'y', { i: 5 }], expect: null - }); + }) xf({ op1: ['x', { i: {} }, 'y', { i: 5 }], op2: ['x', { i: {} }], expect: ['x', 'y', { i: 5 }] - }); + }) xf({ op1: ['x', { i: {} }, 'y', { i: 5 }], op2: ['x', { i: {} }, 'y', { i: 5 }], expect: null - }); + }) return xf({ op1: ['x', { i: {} }, 'y', { i: 5 }], @@ -1539,8 +1530,8 @@ describe('json1', function() { }, expectLeft: ['x', 'y', { r: true, i: 5 }], expectRight: null - }); - }); + }) + }) return it('with embedded edits', () => xf({ @@ -1548,8 +1539,8 @@ describe('json1', function() { op2: [{ i: '', es: ['bbb'] }], expectLeft: [{ es: ['aaa'] }], expectRight: [{ es: [3, 'aaa'] }] - })); - }); + })) + }) describe('op1 edit', function() { it('vs delete', () => @@ -1558,7 +1549,7 @@ describe('json1', function() { op2: ['x', { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: null - })); + })) it('vs delete parent', () => xf({ @@ -1566,14 +1557,14 @@ describe('json1', function() { op2: ['x', { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: null - })); + })) it('vs pick', () => xf({ op1: ['x', { es: ['hi'] }], op2: [['x', { p: 0 }], ['y', { d: 0 }]], expect: ['y', { es: ['hi'] }] - })); + })) it('vs edit string', () => xf({ @@ -1581,19 +1572,17 @@ describe('json1', function() { op2: ['x', { es: ['cd'] }], expectLeft: ['x', { es: ['ab'] }], expectRight: ['x', { es: [2, 'ab'] }] - })); + })) it('vs edit number', () => xf({ op1: [{ ena: 5 }], op2: [{ ena: 100 }], expect: [{ ena: 5 }] - })); + })) it('throws if edit types arent compatible', () => - assert.throws(() => - type.transform([{ es: [] }], [{ ena: 5 }], 'left') - )); + assert.throws(() => type.transform([{ es: [] }], [{ ena: 5 }], 'left'))) it('vs move and edit', () => xf({ @@ -1601,7 +1590,7 @@ describe('json1', function() { op2: [['x', { p: 0 }], ['y', { d: 0, es: [{ d: 1 }, 'cd'] }]], expectLeft: ['y', { es: ['ab'] }], expectRight: ['y', { es: [2, 'ab'] }] - })); + })) return it('throws if the type is missing', () => assert.throws(() => @@ -1610,8 +1599,8 @@ describe('json1', function() { [{ et: 'missing', e: {} }], 'left' ) - )); - }); + )) + }) describe('op2 cancel move', function() { it('and insert', () => @@ -1623,7 +1612,7 @@ describe('json1', function() { op2: ['y', 'b', { i: 5 }] }, expect: [['x', { r: true }], ['y', { r: true }, 'b', { r: true }]] - })); + })) return it('and another move (rm x vs x.a -> y, q -> y.b)', () => xf({ @@ -1638,8 +1627,8 @@ describe('json1', function() { op2: [['q', { p: 0 }], ['y', 'b', { d: 0 }]] }, expect: [['x', { r: true }], ['y', { r: true }, 'b', { r: true }]] - })); - }); + })) + }) describe('op2 list move an op1 drop', function() { it('vs op1 remove', () => @@ -1647,7 +1636,7 @@ describe('json1', function() { op1: [[0, { r: true }, 'a', { i: 'hi' }], [5, { r: true }]], op2: [[1, { p: 0 }], [4, { d: 0 }]], expect: [[0, { r: true }], [3, 'a', { i: 'hi' }], [5, { r: true }]] - })); + })) it('vs op1 remove 2', () => xf({ @@ -1662,14 +1651,14 @@ describe('json1', function() { [1, { r: true }, 'a', { i: 'hi' }], [2, { r: true }] ] - })); + })) it('vs op1 insert before', () => xf({ op1: [[0, { i: 'a' }], [1, { i: 'b' }], [2, 'a', { i: 'hi' }]], op2: [[0, { p: 0 }], [1, { d: 0 }]], expect: [[0, { i: 'a' }], [1, { i: 'b' }], [3, 'a', { i: 'hi' }]] - })); + })) return it('vs op1 insert before and replace', () => xf({ @@ -1680,8 +1669,8 @@ describe('json1', function() { [3, 'a', { r: true }], [4, 'a', { i: 'hi' }] ] - })); - }); + })) + }) return describe('list', () => describe('drop', function() { @@ -1691,13 +1680,13 @@ describe('json1', function() { op2: [9, { i: 9 }], expectLeft: [[5, { i: 5 }], [10, { i: 10 }]], expectRight: [[5, { i: 5 }], [11, { i: 10 }]] - })); + })) - it('transforms by p1 picks'); - it('transforms by p2 picks'); - return it('transforms by p2 drops'); - })); - }); + it('transforms by p1 picks') + it('transforms by p2 picks') + return it('transforms by p2 drops') + })) + }) describe('conflicts', function() { describe('drop into remove / rm unexpected', function() { @@ -1710,7 +1699,7 @@ describe('json1', function() { op2: ['a', { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: null - })); + })) it('errors if you drop', () => xf({ @@ -1718,7 +1707,7 @@ describe('json1', function() { op2: ['x', { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: ['a', { r: true }] - })); + })) it('errors if you rm then insert in a child', () => xf({ @@ -1729,7 +1718,7 @@ describe('json1', function() { op1: ['a', 'b', { i: 5 }] }, expect: null - })); + })) it('errors if the object is replaced', () => xf({ @@ -1740,7 +1729,7 @@ describe('json1', function() { op2: ['a', { r: true }] }, expect: null - })); + })) it('handles a delete of the source parent by op2', () => xf({ @@ -1752,7 +1741,7 @@ describe('json1', function() { }, expectLeft: ['b', 'c', { r: true }], expectRight: null - })); + })) return it.skip('returns symmetric errors when both ops delete the other', () => xf({ @@ -1765,8 +1754,8 @@ describe('json1', function() { op2: [['x', 'a', { i: {} }], ['y', { r: true }]], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: ['x', { r: true }] - })); - }); + })) + }) describe('overlapping drop', function() { it('errors if two ops insert different content into the same place in an object', () => @@ -1776,7 +1765,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: ['x', { r: true, i: 'hi' }], expectRight: null - })); + })) it('does not conflict if inserts are identical', () => xf({ @@ -1784,14 +1773,14 @@ describe('json1', function() { op2: ['x', { i: 'hi' }], expectLeft: null, expectRight: null - })); + })) it('does not conflict if the two operations make identical moves', () => xf({ op1: [['a', { p: 0 }], ['x', { d: 0 }]], op2: [['a', { p: 0 }], ['x', { d: 0 }]], expect: null - })); // ??? Also ok for left: ['x', p:0, d:0] + })) // ??? Also ok for left: ['x', p:0, d:0] it('does not conflict if inserts are into a list', () => xf({ @@ -1799,7 +1788,7 @@ describe('json1', function() { op2: [1, { i: 'yo' }], expectLeft: [1, { i: 'hi' }], expectRight: [2, { i: 'hi' }] - })); + })) it('errors if the inserts are at the root', () => xf({ @@ -1808,7 +1797,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: [{ r: true, i: 1 }], expectRight: null - })); + })) it('errors with insert vs drop', () => xf({ @@ -1818,7 +1807,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: ['x', { r: true, i: 'hi' }], expectRight: null - })); + })) it('errors with drop vs insert', () => xf({ @@ -1827,7 +1816,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: [['a', { p: 0 }], ['x', { r: true, d: 0 }]], expectRight: ['a', { r: true }] - })); + })) it('errors with drop vs drop', () => xf({ @@ -1836,7 +1825,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: [['a', { p: 0 }], ['x', { r: true, d: 0 }]], expectRight: ['a', { r: true }] - })); + })) return it('errors if the two sides insert in the vacuum', () => xf({ @@ -1854,8 +1843,8 @@ describe('json1', function() { op2: [['a', { p: 0 }], ['c', { d: 0 }]] }, expectRight: null - })); - }); + })) + }) describe('discarded edit', function() { it('edit removed directly', () => @@ -1864,7 +1853,7 @@ describe('json1', function() { op2: ['a', { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: null - })); + })) return it('edit inside new content throws RM_UNEXPECTED_CONTENT', () => xf({ @@ -1875,8 +1864,8 @@ describe('json1', function() { op1: ['a', 'b', { i: 'hi' }] }, expect: null - })); - }); + })) + }) return describe('blackhole', function() { it('detects and errors', () => @@ -1885,7 +1874,7 @@ describe('json1', function() { op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]], conflict: { type: BLACKHOLE }, expect: ['x', { r: true }, 'a', { r: true }] - })); // Also equivalent: ['x', r:true] + })) // Also equivalent: ['x', r:true] it('blackhole logic does not apply when op2 removes parent', () => xf({ @@ -1897,7 +1886,7 @@ describe('json1', function() { op2: ['y', 'xx', { r: true }] }, expect: ['x', { r: true }, 'a', { r: true }] - })); // Also ok: ['x', r:true] + })) // Also ok: ['x', r:true] it('blackhole logic still applies when op2 inserts', () => xf({ @@ -1909,7 +1898,7 @@ describe('json1', function() { op2: [['x', 'a', 'b', { d: 0 }], ['y', { p: 0 }]] }, expect: ['x', { r: true }, 'a', { r: true }, 'b', { r: true }] - })); + })) it('blackholes items in lists correctly', () => xf({ @@ -1917,7 +1906,7 @@ describe('json1', function() { op2: [[1, 'b', { d: 0 }], [2, { p: 0 }]], conflict: { type: BLACKHOLE }, expect: [1, { r: true }, 'b', { r: true }] - })); + })) it('blackholes items despite scrambled pick and drop slots', () => xf({ @@ -1928,7 +1917,7 @@ describe('json1', function() { op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]] }, expect: [['a', { p: 0, d: 0 }], ['x', { r: true }, 'a', { r: true }]] - })); + })) it('handles chained blackholes', () => xf({ @@ -1951,7 +1940,7 @@ describe('json1', function() { ['a', { r: true }, 'a', { r: true }], ['c', { r: true }, 'c', { r: true }] ] - })); + })) return it('creates conflict return values with valid slot ids', () => xf({ @@ -1971,9 +1960,9 @@ describe('json1', function() { ['b', { d: 0 }], ['x', { r: true }, 'a', { r: true }] ] - })); - }); - }); + })) + }) + }) describe('transform-old', function() { it('foo', () => @@ -1984,7 +1973,7 @@ describe('json1', function() { ], op2: ['x', { r: true }], expect: ['y', ['a', { p: 0 }], ['b', { d: 0 }]] - })); + })) // it 'hard', -> // op1: ['x', [1, r:true], [2, r:true, es:['hi']]] # Edit at index 4 originally. @@ -2003,7 +1992,7 @@ describe('json1', function() { ], expectLeft: [['_x', { p: 0 }], ['y', { d: 0 }]], expectRight: null - }))); // the object was moved fair and square. + }))) // the object was moved fair and square. describe('deletes', function() { it.skip('delete parent of a move', () => @@ -2019,7 +2008,7 @@ describe('json1', function() { // x.a -> x.b. op2: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], expect: [['x', { r: true }, 'b', { p: 0 }], ['z', { d: 0 }]] - })); // TODO: It would be better to do this in both cases. + })) // TODO: It would be better to do this in both cases. //expectRight: ['x', r:true] return it('awful delete nonsense', function() { @@ -2027,43 +2016,43 @@ describe('json1', function() { op1: [['x', { r: true }], ['y', { i: 'hi' }]], // delete doc.x, insert doc.y op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], // move doc.x.a -> doc.y expect: [['x', { r: true }], ['y', { r: true, i: 'hi' }]] - }); // del doc.x and doc.y, insert doc.y + }) // del doc.x and doc.y, insert doc.y xf({ op1: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], // x.a -> y op2: [['x', { r: true }], ['y', { i: 'hi' }]], // delete x, ins y expect: null - }); + }) return xf({ op1: [10, { r: true }], op2: [[5, { d: 0 }], [10, 1, { p: 0 }]], expect: [[5, { r: true }], [11, { r: true }]] - }); - }); - }); + }) + }) + }) // And how do those indexes interact with pick / drop operations?? describe('swap', function() { const swap = [ ['a', { p: 0 }, 'b', { p: 1 }], ['b', { d: 1 }, 'a', { d: 0 }] - ]; + ] it('noop vs swap', () => xf({ op1: null, op2: swap, expect: null - })); + })) return it('can swap two edits', () => xf({ op1: ['a', { es: ['a edit'] }, 'b', { es: ['b edit'] }], op2: swap, expect: ['b', { es: ['b edit'] }, 'a', { es: ['a edit'] }] - })); - }); + })) + }) describe('lists', function() { it('can rewrite simple list indexes', function() { @@ -2071,55 +2060,55 @@ describe('json1', function() { op1: [10, { es: ['edit'] }], op2: [0, { i: 'oh hi' }], expect: [11, { es: ['edit'] }] - }); + }) xf({ op1: [10, { r: true }], op2: [0, { i: 'oh hi' }], expect: [11, { r: true }] - }); + }) return xf({ op1: [10, { i: {} }], op2: [0, { i: 'oh hi' }], expect: [11, { i: {} }] - }); - }); + }) + }) it('can change the root from an object to a list', () => xf({ op1: ['a', { es: ['hi'] }], op2: [{ i: [], r: true }, [0, { d: 0 }], ['a', { p: 0 }]], expect: [0, { es: ['hi'] }] - })); + })) it('can handle adjacent drops', () => xf({ op1: [[11, { i: 1 }], [12, { i: 2 }], [13, { i: 3 }]], op2: [0, { r: true }], expect: [[10, { i: 1 }], [11, { i: 2 }], [12, { i: 3 }]] - })); + })) it('fixes drop indexes correctly 1', () => xf({ op1: [[0, { r: true }], [1, { i: 'hi' }]], op2: [1, { r: true }], expect: [0, { r: true, i: 'hi' }] - })); + })) it('list drop vs delete uses the correct result index', function() { xf({ op1: [2, { i: 'hi' }], op2: [2, { r: true }], expect: [2, { i: 'hi' }] - }); + }) return xf({ op1: [3, { i: 'hi' }], op2: [2, { r: true }], expect: [2, { i: 'hi' }] - }); - }); + }) + }) it('list drop vs drop uses the correct result index', () => xf({ @@ -2127,7 +2116,7 @@ describe('json1', function() { op2: [2, { i: 'other' }], expectLeft: [2, { i: 'hi' }], expectRight: [3, { i: 'hi' }] - })); + })) it('list drop vs delete and drop', function() { xf({ @@ -2135,55 +2124,55 @@ describe('json1', function() { op2: [2, { r: true, i: 'other' }], expectLeft: [2, { i: 'hi' }], expectRight: [3, { i: 'hi' }] - }); + }) xf({ op1: [3, { i: 'hi' }], op2: [[2, { r: true }], [3, { i: 'other' }]], expect: [2, { i: 'hi' }] - }); + }) return xf({ op1: [4, { i: 'hi' }], op2: [[2, { r: true }], [3, { i: 'other' }]], expectLeft: [3, { i: 'hi' }], expectRight: [4, { i: 'hi' }] - }); - }); + }) + }) it('list delete vs drop', function() { xf({ op1: [1, { r: true }], op2: [2, { i: 'hi' }], expect: [1, { r: true }] - }); + }) xf({ op1: [2, { r: true }], op2: [2, { i: 'hi' }], expect: [3, { r: true }] - }); + }) return xf({ op1: [3, { r: true }], op2: [2, { i: 'hi' }], expect: [4, { r: true }] - }); - }); + }) + }) it('list delete vs delete', () => xf({ op1: [1, { r: true }], op2: [1, { r: true }], expect: null - })); // It was already deleted. + })) // It was already deleted. it('fixes drop indexes correctly 2', () => xf({ op1: [[0, { r: true }], [1, { i: 'hi' }]], op2: [2, { r: true }], // Shouldn't affect the op. expect: [[0, { r: true }], [1, { i: 'hi' }]] - })); + })) it('insert vs delete parent', () => xf({ @@ -2191,7 +2180,7 @@ describe('json1', function() { op2: [2, { r: true }], conflict: { type: RM_UNEXPECTED_CONTENT }, expect: null - })); + })) it('transforms against inserts in my own list', () => xf({ @@ -2199,7 +2188,7 @@ describe('json1', function() { op1: [[0, { i: 'a' }], [2, { i: 'b' }]], op2: [1, { r: true }], expect: [[0, { i: 'a' }], [2, { i: 'b' }]] - })); + })) it('vs cancelled op2 drop', () => xf({ @@ -2207,14 +2196,14 @@ describe('json1', function() { op1: [['x', { r: true }], ['y', 3, { i: 5 }]], op2: [['x', 'a', { p: 0 }], ['y', 2, { d: 0 }]], expect: [['x', { r: true }], ['y', [2, { r: true }], [3, { i: 5 }]]] - })); + })) it('vs cancelled op1 drop', () => xf({ op1: [['x', { p: 0 }], ['y', [3, { d: 0 }], [4, { i: 5 }]]], op2: ['x', { r: true }], expect: ['y', 3, { i: 5 }] - })); + })) it('vs cancelled op1 pick', () => xf({ @@ -2222,7 +2211,7 @@ describe('json1', function() { op1: [[1, { p: 0 }], [4, { r: true, i: 4 }], [6, { d: 0 }]], op2: [1, { r: true }], expect: [[3, { r: true }], [4, { i: 4 }]] - })); + })) it('xxxxx 1', () => diamond({ @@ -2230,15 +2219,15 @@ describe('json1', function() { doc: Array.from('abcdef'), op1: [[1, { p: 0, i: 'AAA' }], [3, { i: 'BBB' }], [5, { d: 0 }]], op2: [1, { r: true }] - })); + })) return it('xxxxx 2', () => diamond({ doc: Array.from('abcdef'), op1: [[1, { p: 0, i: 'AAA' }], [3, { d: 0 }], [5, { i: 'CCC' }]], op2: [1, { r: true }] - })); - }); + })) + }) return describe('edit', function() { it('transforms edits by one another', () => @@ -2246,21 +2235,21 @@ describe('json1', function() { op1: [1, { es: [2, 'hi'] }], op2: [1, { es: ['yo'] }], expect: [1, { es: [4, 'hi'] }] - })); + })) it('copies in ops otherwise', () => xf({ op1: ['x', { e: { position: 2, text: 'wai' }, et: 'simple' }], op2: ['y', { r: true }], expect: ['x', { e: { position: 2, text: 'wai' }, et: 'simple' }] - })); + })) it('allows edits at the root', () => xf({ op1: [{ e: { position: 2, text: 'wai' }, et: 'simple' }], op2: [{ e: { position: 0, text: 'omg' }, et: 'simple' }], expect: [{ e: { position: 5, text: 'wai' }, et: 'simple' }] - })); + })) it('applies edits in the right order', () => xf({ @@ -2268,7 +2257,7 @@ describe('json1', function() { op1: [1, { es: [2, 'hi'] }], op2: [[1, { i: {} }], [2, { es: ['yo'] }]], expect: [2, { es: [4, 'hi'] }] - })); + })) return it('an edit on a deleted object goes away', () => xf({ @@ -2279,9 +2268,9 @@ describe('json1', function() { op2: [1, { r: true }] }, // .... It'd be better if this copied the remove. expect: null - })); - }); - }); + })) + }) + }) // TODO Numbers @@ -2292,21 +2281,21 @@ describe('json1', function() { doc: { the: '', Twas: 'the' }, op: ['the', { es: [] }], expect: { the: '', Twas: 'the' } - })); + })) it('does not duplicate list items from edits', () => apply({ doc: ['eyes'], op: [0, { es: [] }], expect: ['eyes'] - })); + })) it('will edit the root document', () => apply({ doc: '', op: [{ es: [] }], expect: '' - })); + })) // ------ These have nothing to do with apply. TODO: Move them out of this grouping. @@ -2316,7 +2305,7 @@ describe('json1', function() { doc: Array.from('abcde'), op1: [[0, { p: 0 }], [1, { d: 0 }]], op2: [[0, { p: 0 }], [4, { d: 0 }]] - })); + })) it('shuffles lists correctly', () => xf({ @@ -2324,21 +2313,21 @@ describe('json1', function() { op2: [[0, { p: 0 }], [10, { d: 0 }]], expectLeft: [[1, { d: 0 }], [10, { p: 0 }]], expectRight: null - })); + })) it('inserts before edits', function() { xf({ op1: [0, 'x', { i: 5 }], op2: [0, { i: 35 }], expect: [1, 'x', { i: 5 }] - }); + }) return xf({ op1: [0, { es: [] }], op2: [0, { i: 35 }], expect: [1, { es: [] }] - }); - }); + }) + }) it( 'duplicates become noops in a list', @@ -2364,7 +2353,7 @@ describe('json1', function() { op2: [0, { r: true }], expect: [0, { i: 5 }] }) - ); + ) it('p1 pick descends correctly', function() { xf({ @@ -2375,7 +2364,7 @@ describe('json1', function() { op1: [2, 1, { es: ['hi'] }] }, expect: [2, { r: true }] - }); + }) return xf({ op1: [[2, { r: true }, 1, { es: ['hi'] }], [3, 1, { r: true }]], @@ -2385,15 +2374,15 @@ describe('json1', function() { op1: [2, 1, { es: ['hi'] }] }, expect: [[2, { r: true }], [3, 1, { r: true }]] - }); - }); + }) + }) it('transforms picks correctly', () => xf({ op1: [1, 1, { r: true }], op2: [0, { p: 0, d: 0 }], expect: [1, 1, { r: true }] - })); + })) it('pick & drop vs insert after the picked item', () => xf({ @@ -2401,7 +2390,7 @@ describe('json1', function() { op2: [1, { i: 'hi' }], expectLeft: [0, { p: 0, d: 0 }], expectRight: [[0, { p: 0 }], [1, { d: 0 }]] - })); + })) it('pick same item vs shuffle list', () => xf({ @@ -2409,21 +2398,21 @@ describe('json1', function() { op2: [1, { d: 0 }, 'x', { p: 0 }], expectLeft: [1, { p: 0 }, 'y', { d: 0 }], expectRight: null - })); + })) it('remove the same item in a list', () => xf({ op1: [0, { r: true }], op2: [0, { r: true }], expect: null - })); + })) it('rm vs hold item', () => xf({ op1: [0, { r: true }], op2: [0, { p: 0, d: 0 }], expect: [0, { r: true }] - })); + })) it('moves child elements correctly', () => xf({ @@ -2431,7 +2420,7 @@ describe('json1', function() { op1: [1, 0, { p: 0, d: 0 }], op2: [[1, { d: 0 }], [2, { p: 0 }]], expect: [2, 0, { d: 0, p: 0 }] - })); + })) it('moves list indexes', () => xf({ @@ -2439,7 +2428,7 @@ describe('json1', function() { op1: [[0, 'hi', { d: 0 }], [1, { p: 0 }]], op2: [[0, { p: 0 }], [20, { d: 0 }]], expect: [[0, { p: 0 }], [19, 'hi', { d: 0 }]] - })); + })) it('insert empty string vs insert null', () => xf({ @@ -2449,7 +2438,7 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: [{ r: true, i: 'hi' }], expectRight: null - })); + })) it('move vs emplace', () => xf({ @@ -2458,7 +2447,7 @@ describe('json1', function() { op2: [1, { p: 0, d: 0 }], expectLeft: [0, { p: 0, d: 0 }], expectRight: [[0, { p: 0 }], [1, { d: 0 }]] - })); + })) it('rm chases a subdocument that was moved out', () => xf({ @@ -2466,7 +2455,7 @@ describe('json1', function() { op1: [0, { r: true }], op2: [0, { d: 0 }, 0, { p: 0 }], // Valid because lists. expect: [[0, { r: true }], [1, { r: true }]] - })); + })) it('colliding drops', () => xf({ @@ -2476,28 +2465,28 @@ describe('json1', function() { conflict: { type: DROP_COLLISION }, expectLeft: [[0, { p: 0 }, 'x', { d: 0 }], [1, 'x', { r: true }]], expectRight: [0, { r: true }] - })); + })) it('transform crash', () => xf({ op1: [['the', { r: true, d: 0 }], ['whiffling', { p: 0 }]], op2: ['the', { p: 0, d: 0 }], expect: [['the', { d: 0, r: true }], ['whiffling', { p: 0 }]] - })); + })) it('transforms drops when the parent is moved by a remove', () => xf({ op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], op2: ['a', 0, { r: 1 }], expect: [['a', { p: 0 }], ['b', { d: 0 }, 0, { i: 2 }]] - })); + })) it('transforms drops when the parent is moved by a drop', () => xf({ op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], op2: ['a', 0, { i: 1 }], expect: [['a', { p: 0 }], ['b', { d: 0 }, 2, { i: 2 }]] - })); + })) it('transforms conflicting drops obfuscated by a move', () => xf({ @@ -2505,7 +2494,7 @@ describe('json1', function() { op2: ['a', 1, { i: 1 }], expectLeft: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], expectRight: [['a', { p: 0 }], ['b', { d: 0 }, 2, { i: 2 }]] - })); + })) it('transforms edits when the parent is moved', () => xf({ @@ -2513,7 +2502,7 @@ describe('json1', function() { op2: ['x', { es: [{ d: 1 }, 'Z'] }], expectLeft: [['x', { p: 0 }], ['y', { d: 0, es: ['xxx'] }]], expectRight: [['x', { p: 0 }], ['y', { d: 0, es: [1, 'xxx'] }]] - })); + })) it('xf lots', () => xf({ @@ -2521,7 +2510,7 @@ describe('json1', function() { op2: [['a', { p: 0 }], ['c', { d: 0 }]], expectLeft: [['b', { d: 0, es: ['hi'] }], ['c', { p: 0 }]], expectRight: ['c', { es: ['hi'] }] - })); + })) it('inserts are moved back by the other op', () => xf({ @@ -2529,56 +2518,56 @@ describe('json1', function() { op2: [['a', { p: 0 }], ['c', { d: 0 }]], expectLeft: [['b', { d: 0 }, 'x', { i: 'hi' }], ['c', { p: 0 }]], expectRight: ['c', 'x', { i: 'hi' }] - })); + })) it('more awful edit moves', () => xf({ op1: [['a', { p: 0 }], ['c', { d: 0 }, 'x', { es: [] }]], op2: ['a', ['b', { d: 0 }], ['x', { p: 0 }]], expect: [['a', { p: 0 }], ['c', { d: 0 }, 'b', { es: [] }]] - })); + })) it('inserts null', () => xf({ op1: ['x', 'a', { i: null }], op2: [['x', { p: 0 }], ['y', { d: 0 }]], expect: ['y', 'a', { i: null }] - })); + })) it('preserves local insert if both sides delete', () => xf({ op1: [{ i: {}, r: true }, 'x', { i: 'yo' }], op2: [{ r: true }], expect: [{ i: {} }, 'x', { i: 'yo' }] - })); + })) it('handles insert/delete vs move', () => xf({ op1: ['a', { i: {}, r: true }, 'x', { i: 'yo' }], op2: [['a', { p: 0 }], ['b', { d: 0 }]], expect: [['a', { i: {} }, 'x', { i: 'yo' }], ['b', { r: true }]] - })); + })) it('insert pushes edit target', () => xf({ op1: [[0, { i: 'yo' }], [1, 'a', { es: [] }]], op2: [0, ['a', { p: 0 }], ['b', { d: 0 }]], expect: [[0, { i: 'yo' }], [1, 'b', { es: [] }]] - })); + })) it('composes simple regression', function() { compose({ op1: [0, { p: 0, d: 0 }], op2: [{ r: true }], expect: [{ r: true }, 0, { r: true }] - }); + }) return compose({ op1: ['a', 1, { r: true }], op2: ['a', { r: true }], expect: ['a', { r: true }, 1, { r: true }] - }); - }); + }) + }) it('ignores op2 inserts for index position after op1 insert', () => xf({ @@ -2589,7 +2578,7 @@ describe('json1', function() { op1: [{ r: true }] }, expect: [{ r: true, i: [] }, 0, { r: true, i: '' }] - })); + })) it('edit moved inside a removed area should be removed', () => xf({ @@ -2600,7 +2589,7 @@ describe('json1', function() { op1: [0, { r: true }] }, expect: [0, { r: true }, 'x', { r: true }] - })); + })) it('advances indexes correctly with mixed numbers', () => xf({ @@ -2616,7 +2605,7 @@ describe('json1', function() { ['zzz', { d: 1 }] ], expectRight: [['x', 0, { p: 0 }], ['zzz', { d: 0 }]] - })); + })) it('handles index positions past cancelled drops 1', () => xf({ @@ -2624,7 +2613,7 @@ describe('json1', function() { op2: [[0, { p: 0, d: 0 }], [1, { i: 23 }]], expectLeft: [0, { r: true, i: [''] }], expectRight: [[0, { r: true }], [1, { i: [''] }]] - })); + })) it('handles index positions past cancelled drops 2', () => xf({ @@ -2633,7 +2622,7 @@ describe('json1', function() { op2: [['a', { p: 0 }], ['b', [0, { d: 0 }], [1, { i: 'yo' }]]], expectLeft: ['b', 0, { i: 'hi', r: true }], expectRight: ['b', [0, { r: true }], [1, { i: 'hi' }]] - })); + })) it('calculates removed drop indexes correctly', () => xf({ @@ -2645,7 +2634,7 @@ describe('json1', function() { [2, { r: true }, 1, { d: 0 }] ], expectRight: [[1, { i: 'hi' }], [2, { r: true }]] - })); + })) it('removed drop indexes calc regression', () => xf({ @@ -2662,7 +2651,7 @@ describe('json1', function() { ['thought', { p: 0 }] ], expectRight: [1, 'its', { r: true }] - })); + })) it('removed drop indexes tele to op1 pick', () => xf({ @@ -2677,7 +2666,7 @@ describe('json1', function() { op2: [['a', 0, 0, { p: 0 }], ['b', 0, 1, 0, { d: 0 }]] }, expect: ['b', 0, 1, { r: true }, 0, { r: true }] - })); + })) it('tracks removed drop index teleports', () => xf({ @@ -2691,21 +2680,21 @@ describe('json1', function() { op2: [0, { p: 0 }, 0, { d: 0 }] }, expect: [0, { r: true }, 0, { r: true }] - })); + })) it('handles transforming past cancelled move', () => xf({ op1: [[0, { r: true }], [10, { i: [''] }]], op2: [0, { p: 0, d: 0 }], expect: [[0, { r: true }], [10, { i: [''] }]] - })); + })) it('correctly adjusts indexes in another fuzzer great', () => xf({ op1: [[0, { d: 0, r: true }], [3, { p: 0 }]], op2: [[0, { p: 0 }], [3, { d: 0 }]], expect: [[0, { d: 0 }], [2, { p: 0 }], [3, { r: true }]] - })); + })) it('op2 moves into something op1 removes and op1 moves into that', () => xf({ @@ -2716,7 +2705,7 @@ describe('json1', function() { op1: ['a', { r: true }] }, expect: ['a', { r: true }, ['aa', { r: true }], ['bb', { r: true }]] - })); // Also ok if we miss the second rs. + })) // Also ok if we miss the second rs. it('op2 moves into op1 remove edge cases', function() { // Sorry not minified. @@ -2749,7 +2738,7 @@ describe('json1', function() { [1, { i: 'time' }], ['he', { p: 0 }] ] - }); + }) return xf({ op1: [[0, [1, { p: 0 }], [2, { r: true }]], [1, 'xxx', { d: 0 }]], @@ -2759,8 +2748,8 @@ describe('json1', function() { [1, 'xxx', { d: 0 }] ], expectRight: [0, 2, { r: true }] - }); - }); + }) + }) it('translates indexes correctly in this fuzzer find', () => xf({ @@ -2768,7 +2757,7 @@ describe('json1', function() { op2: [[0, { p: 0, d: 0 }], [1, { i: 'y' }]], expectLeft: [[0, { p: 0 }], [1, 'x', { d: 0 }]], expectRight: null - })); + })) it('buries children of blackholed values', () => xf({ @@ -2785,7 +2774,7 @@ describe('json1', function() { // op1: [[0, 'c', d:0], [1, p:0]] expect: [0, { r: true }, 'x', { r: true }] - })); + })) it('does not conflict when removed target gets moved inside removed container', function() { // This edge case is interesting because we don't generate the same @@ -2801,7 +2790,7 @@ describe('json1', function() { }, expectLeft: [['a', { r: true }, 'y', { p: 0 }], ['b', { d: 0 }]], expectRight: ['a', { r: true }, 'y', { r: true }] - }); + }) xf({ op1: [['a', { r: true }, 1, { p: 0 }], ['b', { d: 0 }]], @@ -2812,10 +2801,10 @@ describe('json1', function() { op1: ['a', { r: true }] }, expectRight: ['a', { r: true }, 0, { r: true }] - }); + }) - return { expect: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]] }; - }); + return { expect: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]] } + }) it('compose copies op2 edit data', () => compose({ @@ -2826,7 +2815,7 @@ describe('json1', function() { ['x', { p: 0 }], ['y', { d: 0 }, 'b', { es: [] }] ] - })); + })) it('does not conflict when the dest is salvaged', () => xf({ @@ -2838,28 +2827,28 @@ describe('json1', function() { op1: ['b', { i: 'hi' }] }, expectRight: null - })); + })) it('does not conflict on identical r/i pairs', () => xf({ op1: [{ i: [], r: true }], op2: [{ i: [], r: true }], expect: null - })); + })) it('allows embedded edits in identical r/i', () => xf({ op1: [{ r: true, i: '', es: [] }], op2: [{ r: true, i: '' }], expect: [{ es: [] }] - })); + })) it('does not conflict on identical r/i pairs with identical drops inside', () => xf({ op1: [{ i: {}, r: true }, 'a', { i: 'a' }], op2: [{ i: {}, r: true }, 'a', { i: 'a' }], expect: null - })); + })) it('generates a DROP_COLLISION on children', () => xf({ @@ -2872,7 +2861,7 @@ describe('json1', function() { }, expectLeft: ['a', { r: true, i: 'a' }], expectRight: null - })); + })) it('Transforms edit moves into the right dest', () => xf({ @@ -2899,7 +2888,7 @@ describe('json1', function() { [1, { i: 1 }], [2, { r: true }] ] - })); + })) it('adjusts indexes of pick -> drop', () => xf({ @@ -2907,7 +2896,7 @@ describe('json1', function() { op2: [[0, { i: 'yo', p: 0 }], [1, { d: 0 }]], expectLeft: [[0, { d: 0 }], [1, { p: 0 }]], expectRight: null - })); + })) it('clears output outDrop when theres no pick', () => xf({ @@ -2926,7 +2915,7 @@ describe('json1', function() { ['the', { d: 0 }] ], expectRight: ['bird', { r: true }] - })); + })) it('pushes drop indexes by other held items', () => xf({ @@ -2940,7 +2929,7 @@ describe('json1', function() { [2, { p: 0 }] ], expectRight: [0, 1, [0, { i: 'hi' }], [3, { es: [] }]] - })); + })) it('composes correctly with lots of removes', () => compose({ @@ -2952,14 +2941,14 @@ describe('json1', function() { [2, { r: true }], [3, 1, { r: true }] ] - })); + })) it('does not descend twice when p/r on an identical insert', () => xf({ op1: [['a', { p: 0, i: '' }], ['b', { d: 0 }]], op2: ['a', { r: true, i: '' }], expect: null - })); + })) it('conflicts underneath a moved / inserted child', () => xf({ @@ -2972,7 +2961,7 @@ describe('json1', function() { }, expectLeft: ['a', 'x', { r: true, i: 5 }], expectRight: null - })); + })) it('clears drop2 in transform moves', () => xf({ @@ -2980,7 +2969,7 @@ describe('json1', function() { op1: [0, { d: 0 }, ['a', { es: [] }], ['b', { p: 0 }]], op2: [0, 'b', ['a', { p: 0 }], ['b', { d: 0 }]], expect: [0, { d: 0 }, 'b', { p: 0, es: [] }] - })); + })) it('descends correctly when op2 picks and drops', () => xf({ @@ -2997,7 +2986,7 @@ describe('json1', function() { ['b', { d: 0 }, [1, { es: [] }], [2, { i: null }]], ['e', { p: 0 }] ] - })); + })) it('composes a pick out of the insert', () => compose({ @@ -3005,49 +2994,49 @@ describe('json1', function() { op2: [[0, { r: true }, 'c', { d: 0 }], [1, 'x', { p: 0 }]], // expect: [{i: [{c: 6}]}] expect: [{ i: [{}] }, 0, 'c', { i: 6 }] - })); + })) it('is not overeager to remove intermediate literal array items', () => compose({ op1: [[0, { i: ['a', 'b'] }, 0, { p: 0 }], [1, 0, { d: 0 }]], op2: [0, { r: ['a'] }, 1, { r: 'b' }], expect: [0, 0, { d: 0, p: 0 }] - })); + })) it('descends down insert indexes correctly', () => compose({ op1: [{ i: [{}, 'a'] }, 1, { i: 'b' }], op2: [[1, { r: 'b' }], [2, { r: 'a' }]], expect: [{ i: [{}] }] - })); + })) it('handles composes with ena: 0', () => compose({ op1: [{ i: 10 }], op2: [{ ena: 0 }], expect: [{ i: 10, ena: 0 }] - })); // Also ok: just discarding the ena:0. + })) // Also ok: just discarding the ena:0. it('handles rm parent with cross move', () => compose({ op1: [['a', { p: 0 }], ['b', 1, { d: 0 }]], op2: [['b', { r: true }, 1, { p: 0 }], ['c', { d: 0 }]], expect: [['a', { p: 0 }], ['b', { r: true }], ['c', { d: 0 }]] - })); + })) it('lets you remove children of an op at 2 levels', () => compose({ op1: [{ i: ['a', { x: 'hi' }] }], op2: [{ r: true }, 1, 'x', { r: true }], expect: null - })); + })) it('discards op1 inserts inside a removed chunk', () => compose({ op1: ['y', [1, { i: 'x' }], [2, { i: ['a', 'b'] }]], op2: [{ r: true }, 'y', 2, 0, { r: true }], expect: [{ r: true }] - })); + })) it('handles deeply nested blackhole operations', () => xf({ @@ -3061,7 +3050,7 @@ describe('json1', function() { ], conflict: { type: BLACKHOLE }, expect: ['x', { r: true }, 'xx', { r: true }, 'j', 'jj', { r: true }] - })); + })) it('does not list removed op1 moves in the blackhole info', () => xf({ @@ -3076,7 +3065,7 @@ describe('json1', function() { op2: [['a', { p: 0 }], ['b', 'y', { d: 0 }]] }, expect: ['b', { r: true }, 'y', { r: true }] - })); + })) return it('handles overlapping pick in blackholes', () => xf({ @@ -3108,6 +3097,6 @@ describe('json1', function() { op1: [['a', { p: 0 }], ['b', 0, { d: 0 }]] }, expectRight: ['a', { r: true }, ['x', { r: true }], ['y', { r: true }]] - })); - }); -}); + })) + }) +}) From 7081f378b4d7daf73b8b059aeae0f072ad276a43 Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 09:41:03 +0530 Subject: [PATCH 06/11] Update lock files for both NPM and Yarn. --- package-lock.json | 1360 ++------------------------------------------- yarn.lock | 81 ++- 2 files changed, 108 insertions(+), 1333 deletions(-) diff --git a/package-lock.json b/package-lock.json index b194d5c..f0ba749 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,382 +4,45 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@goto-bus-stop/common-shake": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@goto-bus-stop/common-shake/-/common-shake-2.2.0.tgz", - "integrity": "sha512-AlNzclZ0UebzHyxYfHSKqmlwCtwcECirZDLhN96gGuj5oHAkba/27+4AlCWyXaycM9cUh12L8/2vjmvjn60pkQ==", - "requires": { - "acorn": "^5.1.1", - "debug": "^2.6.8", - "escope": "^3.6.0" - }, - "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - } - } - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "acorn": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", - "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==" - }, - "acorn-dynamic-import": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==" - }, - "acorn-node": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.6.2.tgz", - "integrity": "sha512-rIhNEZuNI8ibQcL7ANm/mGyPukIaZsRNX9psFNQURyJW0nu6k8wjSDld20z6v2mDBWqX13pIEnk9gGZJHIlEXg==", - "requires": { - "acorn": "^6.0.2", - "acorn-dynamic-import": "^4.0.0", - "acorn-walk": "^6.1.0", - "xtend": "^4.0.1" - } - }, - "acorn-walk": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", - "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==" - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=" - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "browser-pack": { - "version": "5.0.1", - "resolved": "http://registry.npmjs.org/browser-pack/-/browser-pack-5.0.1.tgz", - "integrity": "sha1-QZdxmyDG4KqglFHFER5T77b7wY0=", - "requires": { - "JSONStream": "^1.0.3", - "combine-source-map": "~0.6.1", - "defined": "^1.0.0", - "through2": "^1.0.0", - "umd": "^3.0.0" - }, - "dependencies": { - "combine-source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.6.1.tgz", - "integrity": "sha1-m0oJwxYDPXaODxHgKfonMOB5rZY=", - "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.5.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.4.2" - } - }, - "convert-source-map": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=" - }, - "inline-source-map": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.5.0.tgz", - "integrity": "sha1-Skxd2OT7Xps82mDIIt+tyu5m4K8=", - "requires": { - "source-map": "~0.4.0" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "source-map": { - "version": "0.4.4", - "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "requires": { - "amdefine": ">=0.0.4" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "through2": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/through2/-/through2-1.1.1.tgz", - "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", - "requires": { - "readable-stream": ">=1.1.13-1 <1.2.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - } - } - }, - "browser-pack-flat": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/browser-pack-flat/-/browser-pack-flat-3.2.0.tgz", - "integrity": "sha512-tk/LexpgMImZyDfpWSPyIlQ3frZYTyGLpW+Ytd0Fj9VW03Fil9IrKzcVKN87wZHWhP6LbdKh3STRnIkHIR+UTQ==", - "requires": { - "JSONStream": "^1.3.2", - "combine-source-map": "^0.8.0", - "convert-source-map": "^1.5.1", - "count-lines": "^0.1.2", - "dedent": "^0.7.0", - "estree-is-member-expression": "^1.0.0", - "estree-is-require": "^1.0.0", - "esutils": "^2.0.2", - "path-parse": "^1.0.5", - "scope-analyzer": "^2.0.0", - "stream-combiner": "^0.2.2", - "through2": "^2.0.3", - "transform-ast": "^2.4.2", - "umd": "^3.0.3", - "wrap-comment": "^1.0.0" - } - }, - "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "browser-unpack": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-unpack/-/browser-unpack-1.3.0.tgz", - "integrity": "sha512-hK81IeAN/PcqzSKTqKWe3jzmB41PV/23N1w1w2N/KBPv+sjp31sMlen9arVIENzrH8IzaCWA2Fx6SV0kJyhAFQ==", - "requires": { - "acorn": "^5.6.2", - "concat-stream": "^1.5.0", - "minimist": "^1.1.1" - }, - "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" - }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "bundle-collapser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bundle-collapser/-/bundle-collapser-1.3.0.tgz", - "integrity": "sha1-9LT/WLLyLudwGyD6djBuI/U6P7Y=", - "requires": { - "browser-pack": "^5.0.1", - "browser-unpack": "^1.1.0", - "concat-stream": "^1.5.0", - "falafel": "^2.1.0", - "minimist": "^1.1.1", - "through2": "^2.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, - "call-matcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.1.0.tgz", - "integrity": "sha512-IoQLeNwwf9KTNbtSA7aEBb1yfDbdnzwjCetjkC8io5oGeOmK2CBNdg0xr+tadRYKO0p7uQyZzvon0kXlZbvGrw==", - "requires": { - "core-js": "^2.0.0", - "deep-equal": "^1.0.0", - "espurify": "^1.6.0", - "estraverse": "^4.0.0" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "coffeescript": { - "version": "2.3.2", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", - "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - }, - "dependencies": { - "convert-source-map": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, "commander": { "version": "2.15.1", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, - "common-shakeify": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/common-shakeify/-/common-shakeify-0.5.2.tgz", - "integrity": "sha512-znhuzdj4zgvMz5u6cM9FI1WFcxTJ8lvGGW1FaaU0eMZ9o9LqIy3j43SrUYsQJKwdI8+1p/55YpeHTz68G+0Zsw==", - "requires": { - "@goto-bus-stop/common-shake": "^2.2.0", - "convert-source-map": "^1.5.1", - "through2": "^2.0.3", - "transform-ast": "^2.4.3", - "wrap-comment": "^1.0.1" - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "core-js": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", - "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "count-lines": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/count-lines/-/count-lines-0.1.2.tgz", - "integrity": "sha1-4zST+2hgqC9xWdgjeEP7+u/uWWI=" - }, - "d": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "requires": { - "es5-ext": "^0.10.9" - } - }, - "dash-ast": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", - "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "debug": { "version": "3.1.0", @@ -390,282 +53,17 @@ "ms": "2.0.0" } }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" - }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, - "duplexer": { - "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" - }, - "duplexify": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", - "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "requires": { - "once": "^1.4.0" - } - }, - "envify": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/envify/-/envify-4.1.0.tgz", - "integrity": "sha512-IKRVVoAYr4pIx4yIWNsz9mOsboxlNXiu7TNBnem/K/uTHdkyzXWDzHCK7UTolqBbgaBz0tQHsD3YNls0uIIjiw==", - "requires": { - "esprima": "^4.0.0", - "through": "~2.3.4" - } - }, - "es5-ext": { - "version": "0.10.46", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", - "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "escodegen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", - "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - } - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "espurify": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.8.1.tgz", - "integrity": "sha512-ZDko6eY/o+D/gHCWyHTU85mKDgYcS4FJj7S+YD6WIInm7GQ6AnOjmcL4+buFV/JOztVLELi/7MmuGU5NHta0Mg==", - "requires": { - "core-js": "^2.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" - }, - "estree-is-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-is-function/-/estree-is-function-1.0.0.tgz", - "integrity": "sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA==" - }, - "estree-is-identifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-is-identifier/-/estree-is-identifier-1.0.0.tgz", - "integrity": "sha512-2BDRGrkQJV/NhCAmmE33A35WAaxq3WQaGHgQuD//7orGWfpFqj8Srkwvx0TH+20yIdOF1yMQwi8anv5ISec2AQ==" - }, - "estree-is-member-expression": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-is-member-expression/-/estree-is-member-expression-1.0.0.tgz", - "integrity": "sha512-Ec+X44CapIGExvSZN+pGkmr5p7HwUVQoPQSd458Lqwvaf4/61k/invHSh4BYK8OXnCkfEhWuIoG5hayKLQStIg==" - }, - "estree-is-require": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-is-require/-/estree-is-require-1.0.0.tgz", - "integrity": "sha512-oWxQdSEmnUwNZsDQYiBNpVxKEhMmsJQSSxnDrwsr1MWtooCLfhgzsNGzmokdmfK0EzEIS5V4LPvqxv1Kmb1vvA==", - "requires": { - "estree-is-identifier": "^1.0.0" - } - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "extend": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.3.0.tgz", - "integrity": "sha1-0VFvsP9WJNLr+RI+odrFoZlABPg=" - }, - "falafel": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.1.0.tgz", - "integrity": "sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw=", - "requires": { - "acorn": "^5.0.0", - "foreach": "^2.0.5", - "isarray": "0.0.1", - "object-keys": "^1.0.6" - }, - "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - } - } - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "from2-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/from2-string/-/from2-string-1.1.0.tgz", - "integrity": "sha1-GCgrJ9CKJnyzAwzSuLSw8hKvdSo=", - "requires": { - "from2": "^2.0.3" - } + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "fs.realpath": { "version": "1.0.0", @@ -673,11 +71,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" - }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -698,14 +91,6 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -731,105 +116,27 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", - "requires": { - "source-map": "~0.5.3" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=" - }, - "magic-string": { - "version": "0.23.2", - "resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.23.2.tgz", - "integrity": "sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==", - "requires": { - "sourcemap-codec": "^1.4.1" - } - }, - "merge-source-map": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", - "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=", - "requires": { - "source-map": "^0.5.6" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "minify-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minify-stream/-/minify-stream-1.2.0.tgz", - "integrity": "sha512-bIjBH7uGROwzWwgtbLO7U/yi+NBTLGs5YYidUiGD9nJZ5wuxX0485c48vtJ7WlNZNnKvHXA1D1ZXpfWJqf4fyg==", - "requires": { - "concat-stream": "^1.6.0", - "convert-source-map": "^1.5.0", - "duplexify": "^3.5.1", - "from2-string": "^1.1.0", - "terser": "^3.7.5", - "xtend": "^4.0.1" - } + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -838,6 +145,8 @@ }, "mocha": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { "browser-stdout": "1.3.1", @@ -856,509 +165,66 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "multi-stage-sourcemap": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/multi-stage-sourcemap/-/multi-stage-sourcemap-0.2.1.tgz", - "integrity": "sha1-sJ/IWG6qF/gdV1xK0C4Pej9rEQU=", - "requires": { - "source-map": "^0.1.34" - }, - "dependencies": { - "source-map": { - "version": "0.1.43", - "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, - "mutexify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.2.0.tgz", - "integrity": "sha512-oprzxd2zhfrJqEuB98qc1dRMMonClBQ57UPDjnbcrah4orEMTq1jq3+AcdFe5ePzdbJXI7zmdhfftIdMnhYFoQ==" - }, - "nanobench": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nanobench/-/nanobench-2.1.1.tgz", - "integrity": "sha512-z+Vv7zElcjN+OpzAxAquUayFLGK3JI/ubCl0Oh64YQqsTGG09CGqieJVQw4ui8huDnnAgrvTv93qi5UaOoNj8A==", - "requires": { - "browser-process-hrtime": "^0.1.2", - "chalk": "^1.1.3", - "mutexify": "^1.1.0", - "pretty-hrtime": "^1.0.2" - } - }, - "next-tick": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, "ot-fuzzer": { - "version": "1.2.0", - "dev": true, - "requires": { - "cli-progress": "^2.1.1", - "seedrandom": "^2.4.4" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "cli-progress": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "requires": { - "colors": "^1.1.2", - "string-width": "^2.1.1" - } - }, - "coffee-script": { - "version": "1.12.7", - "bundled": true - }, - "colors": { - "version": "1.3.3", - "bundled": true, - "dev": true - }, - "commander": { - "version": "2.3.0", - "bundled": true - }, - "debug": { - "version": "2.0.0", - "bundled": true, - "requires": { - "ms": "0.6.2" - } - }, - "diff": { - "version": "1.0.8", - "bundled": true - }, - "escape-string-regexp": { - "version": "1.0.2", - "bundled": true - }, - "glob": { - "version": "3.2.3", - "bundled": true, - "requires": { - "graceful-fs": "~2.0.0", - "inherits": "2", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "2.0.3", - "bundled": true - }, - "growl": { - "version": "1.8.1", - "bundled": true - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "jade": { - "version": "0.26.3", - "bundled": true, - "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "bundled": true - }, - "mkdirp": { - "version": "0.3.0", - "bundled": true - } - } - }, - "lru-cache": { - "version": "2.7.3", - "bundled": true - }, - "minimatch": { - "version": "0.2.14", - "bundled": true, - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "mkdirp": { - "version": "0.5.0", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "1.21.5", - "bundled": true, - "requires": { - "commander": "2.3.0", - "debug": "2.0.0", - "diff": "1.0.8", - "escape-string-regexp": "1.0.2", - "glob": "3.2.3", - "growl": "1.8.1", - "jade": "0.26.3", - "mkdirp": "0.5.0" - } - }, - "ms": { - "version": "0.6.2", - "bundled": true - }, - "seedrandom": { - "version": "2.4.4", - "bundled": true, - "dev": true - }, - "sigmund": { - "version": "1.0.1", - "bundled": true - }, - "string-width": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ot-fuzzer/-/ot-fuzzer-1.1.0.tgz", + "integrity": "sha512-48w+h509Sq48JMsit5/qPCxhWIzztS52DkhIztqiFV0UT2MA6BWu2JiLHSw6cLren1O5IWhNjU7LObOmVOAFGg==", + "dev": true }, "ot-simple": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ot-simple/-/ot-simple-1.0.0.tgz", + "integrity": "sha1-B42ED4HqOq04y+aUdfgbLGdU+1A=", "dev": true }, "ot-text": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ot-text/-/ot-text-1.0.2.tgz", + "integrity": "sha512-1xjcAjB57tYtv722j8J+IaYe4fKZOeaQ3zDa0clUlkhboawrq+dk1cpDfe4+TBSb9NNe7Yra9+tJKsw+8q4H9A==", "dev": true }, "ot-text-unicode": { - "version": "2.0.0", - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "bundled": true - }, - "coffeescript": { - "bundled": true - }, - "commander": { - "version": "2.15.1", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "debug": { - "version": "3.1.0", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - }, - "diff": { - "version": "3.5.0", - "bundled": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "bundled": true - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "growl": { - "version": "1.10.5", - "bundled": true - }, - "has-flag": { - "version": "3.0.0", - "bundled": true - }, - "he": { - "version": "1.1.1", - "bundled": true - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "bundled": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "ot-fuzzer": { - "bundled": true - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "supports-color": { - "version": "5.4.0", - "bundled": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "typescript": { - "bundled": true - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-3.0.0.tgz", + "integrity": "sha512-YwVxEFVLV9WE40KavenjlHy3Y0C2OZoJeNCHKEpJlmu/scJaY9tlSrlYNBU7WJgboIz/IoKsd0Poxf+SYLRmrw==", + "requires": { + "unicount": "^1.0.0" } }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "scope-analyzer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/scope-analyzer/-/scope-analyzer-2.0.5.tgz", - "integrity": "sha512-+U5H0417mnTEstCD5VwOYO7V4vYuSqwqjFap40ythe67bhMFL5C3UgPwyBv7KDJsqUBIKafOD57xMlh1rN7eaw==", - "requires": { - "array-from": "^2.1.1", - "es6-map": "^0.1.5", - "es6-set": "^0.1.5", - "es6-symbol": "^3.1.1", - "estree-is-function": "^1.0.0", - "get-assigned-identifiers": "^1.1.0" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, - "sourcemap-codec": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", - "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==" - }, - "stream-combiner": { - "version": "0.2.2", - "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", - "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", - "requires": { - "duplexer": "~0.1.1", - "through": "~2.3.4" - } - }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -1372,6 +238,7 @@ "version": "3.11.0", "resolved": "https://registry.npmjs.org/terser/-/terser-3.11.0.tgz", "integrity": "sha512-5iLMdhEPIq3zFWskpmbzmKwMQixKmTYwY3Ox9pjtSklBLnHiuQ0GKJLhL1HSYtyffHM3/lDIFBnb82m9D7ewwQ==", + "dev": true, "requires": { "commander": "~2.17.1", "source-map": "~0.6.1", @@ -1381,154 +248,21 @@ "commander": { "version": "2.17.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" - } - } - }, - "through": { - "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "tinyify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tinyify/-/tinyify-2.5.0.tgz", - "integrity": "sha512-SdKsUZM0k57hwdhjqZedWI4YSUcuVMmJabP6kOAvwHSlJJFR9PkTJ5meIiBRwjOnVwc0Q1xiTdj/FjzyGi099A==", - "requires": { - "browser-pack-flat": "^3.0.9", - "bundle-collapser": "^1.3.0", - "common-shakeify": "^0.5.2", - "envify": "^4.1.0", - "minify-stream": "^1.1.0", - "uglifyify": "^5.0.0", - "unassertify": "^2.1.1" - } - }, - "transform-ast": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/transform-ast/-/transform-ast-2.4.4.tgz", - "integrity": "sha512-AxjeZAcIOUO2lev2GDe3/xZ1Q0cVGjIMk5IsriTy8zbWlsEnjeB025AhkhBJHoy997mXpLd4R+kRbvnnQVuQHQ==", - "requires": { - "acorn-node": "^1.3.0", - "convert-source-map": "^1.5.1", - "dash-ast": "^1.0.0", - "is-buffer": "^2.0.0", - "magic-string": "^0.23.2", - "merge-source-map": "1.0.4", - "nanobench": "^2.1.1" - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "uglifyify": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/uglifyify/-/uglifyify-5.0.1.tgz", - "integrity": "sha512-PO44rgExvwj3rkK0UzenHVnPU18drBy9x9HOUmgkuRh6K2KIsDqrB5LqxGtjybgGTOS1JeP8SBc+TN5rhiva6w==", - "requires": { - "convert-source-map": "~1.1.0", - "extend": "^1.2.1", - "minimatch": "^3.0.2", - "terser": "^3.7.5", - "through": "~2.3.4" - }, - "dependencies": { - "convert-source-map": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=" - } - } - }, - "umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==" - }, - "unassert": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/unassert/-/unassert-1.5.1.tgz", - "integrity": "sha1-y8iOw4dBfFpeTALTzQe+mL11/3Y=", - "requires": { - "acorn": "^4.0.0", - "call-matcher": "^1.0.1", - "deep-equal": "^1.0.0", - "espurify": "^1.3.0", - "estraverse": "^4.1.0", - "esutils": "^2.0.2", - "object-assign": "^4.1.0" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" - } - } - }, - "unassertify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/unassertify/-/unassertify-2.1.1.tgz", - "integrity": "sha512-YIAaIlc6/KC9Oib8cVZLlpDDhK1UTEuaDyx9BwD97xqxDZC0cJOqwFcs/Y6K3m73B5VzHsRTBLXNO0dxS/GkTw==", - "requires": { - "acorn": "^5.1.0", - "convert-source-map": "^1.1.1", - "escodegen": "^1.6.1", - "multi-stage-sourcemap": "^0.2.1", - "through": "^2.3.7", - "unassert": "^1.3.1" - }, - "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true } } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "wordwrap": { + "unicount": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - }, - "wrap-comment": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wrap-comment/-/wrap-comment-1.0.1.tgz", - "integrity": "sha512-APccrMwl/ont0RHFTXNAQfM647duYYEfs6cngrIyTByTI0xbWnDnPSptFZhS68L4WCjt2ZxuhCFwuY6Pe88KZQ==" + "resolved": "https://registry.npmjs.org/unicount/-/unicount-1.0.0.tgz", + "integrity": "sha512-laO0gw4tnW759KI25w8z+58D4K+pnXehZvfKdFj3meYn/NSH85/0I8Gb4jj32tuZaX7zv1rSeP2K87rdChIZUw==" }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true } } } diff --git a/yarn.lock b/yarn.lock index e106f3f..eec7e90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -25,20 +30,28 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -coffeescript@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-2.3.2.tgz#e854a7020dfe47b7cf4dd412042e32ef1e269810" - integrity sha512-YObiFDoukx7qPBi/K0kUKyntEZDfBQiqs/DbrR1xzASKOBjGT7auD85/DiPeRr9k++lRj7l3uA9TNMLfyfcD/Q== +cli-progress@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-2.1.1.tgz#45ee1b143487c19043a3262131ccb4676f87f032" + integrity sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA== + dependencies: + colors "^1.1.2" + string-width "^2.1.1" + +colors@^1.1.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" + integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== commander@2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== -commander@~2.17.1: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== +commander@^2.19.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== concat-map@0.0.1: version "0.0.1" @@ -107,6 +120,11 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -156,9 +174,12 @@ once@^1.3.0: wrappy "1" ot-fuzzer@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ot-fuzzer/-/ot-fuzzer-1.1.0.tgz#b556a799117ceeb458794ef376f34b985f62dc8d" - integrity sha512-48w+h509Sq48JMsit5/qPCxhWIzztS52DkhIztqiFV0UT2MA6BWu2JiLHSw6cLren1O5IWhNjU7LObOmVOAFGg== + version "1.2.1" + resolved "https://registry.yarnpkg.com/ot-fuzzer/-/ot-fuzzer-1.2.1.tgz#41f70305fdd1d55268f6cc169c14c0eb9e5fb31c" + integrity sha512-dOm+Wb1Mqrw8ql5ksiZFf3Bdsruj5r4mHaa3COqtbg0ClkGjxZXwMGgMLjWslq/b0maeiw5yvYwIdH6As4svHg== + dependencies: + cli-progress "^2.1.1" + seedrandom "^2.4.4" ot-simple@^1.0.0: version "1.0.0" @@ -182,10 +203,15 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -source-map-support@~0.5.6: - version "0.5.9" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" - integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== +seedrandom@^2.4.4: + version "2.4.4" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.4.tgz#b25ea98632c73e45f58b77cfaa931678df01f9ba" + integrity sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA== + +source-map-support@~0.5.10: + version "0.5.12" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" + integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -195,6 +221,21 @@ source-map@^0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + supports-color@5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" @@ -203,13 +244,13 @@ supports-color@5.4.0: has-flag "^3.0.0" terser@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-3.11.0.tgz#60782893e1f4d6788acc696351f40636d0e37af0" - integrity sha512-5iLMdhEPIq3zFWskpmbzmKwMQixKmTYwY3Ox9pjtSklBLnHiuQ0GKJLhL1HSYtyffHM3/lDIFBnb82m9D7ewwQ== + version "3.17.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" + integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== dependencies: - commander "~2.17.1" + commander "^2.19.0" source-map "~0.6.1" - source-map-support "~0.5.6" + source-map-support "~0.5.10" unicount@^1.0.0: version "1.0.0" From 1965cef533dd9134ee7fa12a6c2e92138331dcec Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 09:49:56 +0530 Subject: [PATCH 07/11] Bring back CoffeeScript registering in Mocha opts. --- package-lock.json | 6 ++++++ package.json | 1 + test/mocha.opts | 1 + 3 files changed, 8 insertions(+) diff --git a/package-lock.json b/package-lock.json index f0ba749..8917150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,12 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "coffeescript": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.4.1.tgz", + "integrity": "sha512-34GV1aHrsMpTaO3KfMJL40ZNuvKDR/g98THHnE9bQj8HjMaZvSrLik99WWqyMhRtbe8V5hpx5iLgdcSvM/S2wg==", + "dev": true + }, "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", diff --git a/package.json b/package.json index 8f18fdd..df7bcff 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "ot-text-unicode": "^3.0.0" }, "devDependencies": { + "coffeescript": "^2.4.1", "mocha": "^5.2.0", "ot-fuzzer": "^1.1.0", "ot-simple": "^1.0.0", diff --git a/test/mocha.opts b/test/mocha.opts index 4d7bcbf..988738e 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,2 +1,3 @@ +-r coffeescript/register --reporter spec --check-leaks From 7a5aa6e9b84fde33d3af645cef9421de63f776e7 Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 10:00:01 +0530 Subject: [PATCH 08/11] Manually clean test/cursor.js --- test/cursor.js | 64 ++++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/test/cursor.js b/test/cursor.js index 2aa3b97..1efa76b 100644 --- a/test/cursor.js +++ b/test/cursor.js @@ -1,10 +1,3 @@ -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS205: Consider reworking code to avoid use of IIFEs - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ const { writeCursor, readCursor } = require('../lib/cursor') const assert = require('assert') @@ -16,13 +9,13 @@ const data = require('fs') describe('cursors', function() { describe('writeCursor duplicates', function() { - const test = function(op) { + const test = op => { const w = writeCursor() - var f = function(l) { + const f = l => { assert(Array.isArray(l)) let depth = 0 - for (let c of Array.from(l)) { + for (let c of l) { if (['string', 'number'].includes(typeof c)) { depth++ w.descend(c) @@ -45,22 +38,19 @@ describe('cursors', function() { return assert.deepEqual(op, w.get()) } - return Array.from(data).map(d => - (d => it(`${JSON.stringify(d)}`, () => test(d)))(d) - ) + return data.map(d => (d => it(`${JSON.stringify(d)}`, () => test(d)))(d)) }) describe('copy using read cursors', function() { - const test = function(op) { - let f + const test = op => { const r = readCursor(op) const w = writeCursor() const path = [] - ;(f = function() { - let component, k - if ((component = r.getComponent())) { + const f = () => { + const component = r.getComponent() + if (component) { // console.log 'component', component - for (k in component) { + for (let k in component) { const v = component[k] w.write(k, v) } @@ -69,30 +59,28 @@ describe('cursors', function() { assert.deepStrictEqual(r.getPath(), path) assert.deepStrictEqual(w.getPath(), path) - return (() => { - const result = [] - for (k of r) { - path.push(k) - w.descend(k) - f() - w.ascend() - result.push(path.pop()) - } - return result - })() - })() + const result = [] + for (let k of r) { + path.push(k) + w.descend(k) + f() + w.ascend() + result.push(path.pop()) + } + return result + } + + f() return assert.deepEqual(op, w.get()) } // console.log op // console.log w.get() - return Array.from(data).map(d => - (d => it(`${JSON.stringify(d)}`, () => test(d)))(d) - ) + return data.map(d => it(`${JSON.stringify(d)}`, () => test(d))) }) return describe('fuzzer', () => - it('cleans up position after mergeTree', function() { + it('cleans up position after mergeTree', () => { const a = [1, 'c', { d: 1 }] const w = writeCursor(a) @@ -113,9 +101,9 @@ describe('cursors', function() { }) function __range__(left, right, inclusive) { - let range = [] - let ascending = left < right - let end = !inclusive ? right : ascending ? right + 1 : right - 1 + const range = [] + const ascending = left < right + const end = !inclusive ? right : ascending ? right + 1 : right - 1 for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { range.push(i) } From 9a63512cdedaeaa5357d1dad255bed66a335743b Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 10:21:17 +0530 Subject: [PATCH 09/11] Manually clean test/genOp.js --- test/genOp.js | 105 ++++++++++++++++---------------------------------- 1 file changed, 34 insertions(+), 71 deletions(-) diff --git a/test/genOp.js b/test/genOp.js index c5333dd..0e35598 100644 --- a/test/genOp.js +++ b/test/genOp.js @@ -1,14 +1,5 @@ -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS202: Simplify dynamic range loops - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ let genRandomOp const { randomInt, randomReal, randomWord } = require('ot-fuzzer') -// require 'ot-fuzzer' const assert = require('assert') const { writeCursor } = require('../lib/cursor') @@ -18,36 +9,22 @@ const { type } = require('../lib/json1') // This is an awful function to clone a document snapshot for use by the random // op generator. .. Since we don't want to corrupt the original object with // the changes the op generator will make. -const clone = function(o) { - if (typeof o === 'object') { - return JSON.parse(JSON.stringify(o)) - } else { - return o - } -} +const clone = o => (typeof o === 'object' ? JSON.parse(JSON.stringify(o)) : o) -const randomKey = function(obj) { +const randomKey = obj => { // this works on arrays too! if (Array.isArray(obj)) { - if (obj.length === 0) { - return undefined - } else { - return randomInt(obj.length) - } + return obj.length === 0 ? undefined : randomInt(obj.length) } else { const keys = Object.keys(obj) - if (keys.length === 0) { - return undefined - } else { - return keys[randomInt(keys.length)] - } + return keys.length === 0 ? undefined : keys[randomInt(keys.length)] } } const letters = 'abxyz' // Generate a random new key for a value in obj. -const randomNewKey = function(obj) { +const randomNewKey = obj => { if (Array.isArray(obj)) { return randomInt(obj.length + 1) } else { @@ -65,7 +42,7 @@ const randomNewKey = function(obj) { } // Generate a random object -var randomThing = function() { +const randomThing = () => { switch (randomInt(7)) { case 0: return null @@ -74,7 +51,7 @@ var randomThing = function() { case 2: return randomWord() case 3: - var obj = {} + const obj = {} for ( let i = 1, end = randomInt(2), asc = 1 <= end; asc ? i <= end : i >= end; @@ -93,7 +70,7 @@ var randomThing = function() { } // Pick a random path to something in the object. -const randomPath = function(data) { +const randomPath = data => { if (data == null || typeof data !== 'object') { return [] } @@ -112,11 +89,10 @@ const randomPath = function(data) { return path } -const randomWalkPick = function(w, container) { +const randomWalkPick = (w, container) => { const path = randomPath(container.data) let parent = container let key = 'data' - //log 'rwp', container, path, parent for (let p of Array.from(path)) { parent = parent[key] @@ -127,16 +103,16 @@ const randomWalkPick = function(w, container) { return [path, parent, key, operand] } -// Returns [path, parent, key] if we can drop here, or null if no drop is -// possible -const randomWalkDrop = function(w, container) { +// Returns [path, parent, key] if we can drop here, +// or null if no drop is possible +const randomWalkDrop = (w, container) => { if (container.data === undefined) { return [[], container, 'data'] } else if (typeof container.data !== 'object' || container.data === null) { return null // Can't insert into a document that is a string or number } - let [path, parent, key, operand] = Array.from(randomWalkPick(w, container)) + let [path, parent, key, operand] = randomWalkPick(w, container) if (typeof operand === 'object' && operand !== null) { parent = operand } else { @@ -145,27 +121,19 @@ const randomWalkDrop = function(w, container) { path.pop() } key = randomNewKey(parent) - // log 'key', key return [path, parent, key] } -const set = function(container, key, value) { - if (value === undefined) { - if (Array.isArray(container)) { - return container.splice(key, 1) - } else { - return delete container[key] - } - } else { - if (Array.isArray(container)) { - return container.splice(key, 0, value) - } else { - return (container[key] = value) - } - } -} +const set = (container, key, value) => + value === undefined + ? Array.isArray(container) + ? container.splice(key, 1) + : delete container[key] + : Array.isArray(container) + ? container.splice(key, 0, value) + : (container[key] = value) -const genRandomOpPart = function(data) { +const genRandomOpPart = data => { // log 'genRandomOp', data let key1, parent1, path1 @@ -185,14 +153,12 @@ const genRandomOpPart = function(data) { switch (mode) { case 0: case 1: - var [path, parent, key, operand] = Array.from( - randomWalkPick(w, container) - ) + const [path, parent, key, operand] = randomWalkPick(w, container) //log 'ppko', path, parent, key, operand if (mode === 1 && typeof operand === 'string') { // Edit it! const genString = require('ot-text/test/genOp') - const [stringOp, result] = Array.from(genString(operand)) + const [stringOp, result] = genString(operand) w.write('es', stringOp) parent[key] = result } else if (mode === 1 && typeof operand === 'number') { @@ -210,9 +176,9 @@ const genRandomOpPart = function(data) { case 2: // insert something - var walk = randomWalkDrop(w, container) + const walk = randomWalkDrop(w, container) if (walk !== null) { - ;[path, parent, key] = Array.from(walk) + const [path, parent, key] = walk //log 'walk', walk const val = randomThing() if (parent !== container) { @@ -225,10 +191,8 @@ const genRandomOpPart = function(data) { case 3: // Move something. We'll pick up the current operand... - ;[path1, parent1, key1, operand] = Array.from( - randomWalkPick(w, container) - ) - if (operand !== undefined) { + const [path1, parent1, key1, operand1] = randomWalkPick(w, container) + if (operand1 !== undefined) { set(parent1, key1, undefined) // remove it from the result... if (parent1 === container) { @@ -250,7 +214,7 @@ const genRandomOpPart = function(data) { randomWalkDrop(w, container) ) w.descend(key2) - set(parent2, key2, operand) + set(parent2, key2, operand1) w.write('d', 0) } } @@ -267,7 +231,7 @@ const genRandomOpPart = function(data) { return [op, doc] } -module.exports = genRandomOp = function(doc) { +module.exports = genRandomOp = doc => { doc = clone(doc) // 90% chance of adding an op the first time through, then 50% each successive time. @@ -277,14 +241,13 @@ module.exports = genRandomOp = function(doc) { while (randomReal() < chance) { let opc - ;[opc, doc] = Array.from(genRandomOpPart(doc)) + ;[opc, doc] = genRandomOpPart(doc) log(opc) // type.setDebug false op = type.compose( op, opc ) - chance = 0.5 } @@ -305,9 +268,9 @@ if (require.main === module) { // log genRandomOp(undefined) function __range__(left, right, inclusive) { - let range = [] - let ascending = left < right - let end = !inclusive ? right : ascending ? right + 1 : right - 1 + const range = [] + const ascending = left < right + const end = !inclusive ? right : ascending ? right + 1 : right - 1 for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { range.push(i) } From 83f6ed86dbe577fe7a8d374b0c04380598b4ff7e Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 10:25:08 +0530 Subject: [PATCH 10/11] Manually clean test/immutable.js --- test/immutable.js | 121 ++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 69 deletions(-) diff --git a/test/immutable.js b/test/immutable.js index bd6f93f..a88ab11 100644 --- a/test/immutable.js +++ b/test/immutable.js @@ -1,90 +1,73 @@ -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS202: Simplify dynamic range loops - * DS205: Consider reworking code to avoid use of IIFEs - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ const assert = require('assert') const { type } = require('../lib/json1') const log = require('../lib/log') const genOp = require('./genOp') const deepClone = require('../lib/deepClone') -// This tests that none of apply / compose / transform / genOp mutate their -// input - +// This tests that none of apply / compose / transform / genOp mutate their input describe('immutable guarantees', function() { const origDoc = { x: 'hi', y: 'omg', z: [1, 'whoa', 3] } const expectDoc = deepClone(origDoc) const n = 1000 this.slow(n * 10) - it('apply does not mutate', () => - (() => { - const result = [] - for ( - let i = 1, end = n, asc = 1 <= end; - asc ? i <= end : i >= end; - asc ? i++ : i-- - ) { - const [op, doc] = Array.from(genOp(origDoc)) - assert.deepStrictEqual(origDoc, expectDoc) + it('apply does not mutate', () => { + const result = [] + for ( + let i = 1, end = n, asc = 1 <= end; + asc ? i <= end : i >= end; + asc ? i++ : i-- + ) { + const [op, doc] = genOp(origDoc) + assert.deepStrictEqual(origDoc, expectDoc) - const expectOp = deepClone(op) - type.apply(origDoc, op) + const expectOp = deepClone(op) + type.apply(origDoc, op) - assert.deepStrictEqual(origDoc, expectDoc) - result.push(assert.deepStrictEqual(op, expectOp)) - } - return result - })()) + assert.deepStrictEqual(origDoc, expectDoc) + result.push(assert.deepStrictEqual(op, expectOp)) + } + return result + }) - it('compose does not mutate', () => - (() => { - const result = [] - for ( - let i = 1, end = n, asc = 1 <= end; - asc ? i <= end : i >= end; - asc ? i++ : i-- - ) { - let op2 - let [op1, doc] = Array.from(genOp(origDoc)) - ;[op2, doc] = Array.from(genOp(doc)) + it('compose does not mutate', () => { + for ( + let i = 1, end = n, asc = 1 <= end; + asc ? i <= end : i >= end; + asc ? i++ : i-- + ) { + let op2 + let [op1, doc] = genOp(origDoc) + ;[op2, doc] = genOp(doc) - const expectOp1 = deepClone(op1) - const expectOp2 = deepClone(op2) - type.compose( - op1, - op2 - ) + const expectOp1 = deepClone(op1) + const expectOp2 = deepClone(op2) + type.compose( + op1, + op2 + ) - assert.deepStrictEqual(op1, expectOp1) - result.push(assert.deepStrictEqual(op2, expectOp2)) - } - return result - })()) + assert.deepStrictEqual(op1, expectOp1) + assert.deepStrictEqual(op2, expectOp2) + } + }) - return it('transform does not mutate', () => - (() => { - const result = [] - for ( - let i = 1, end = n, asc = 1 <= end; - asc ? i <= end : i >= end; - asc ? i++ : i-- - ) { - const [op1, doc1] = Array.from(genOp(origDoc)) - const [op2, doc2] = Array.from(genOp(origDoc)) + it('transform does not mutate', () => { + for ( + let i = 1, end = n, asc = 1 <= end; + asc ? i <= end : i >= end; + asc ? i++ : i-- + ) { + const [op1, doc1] = genOp(origDoc) + const [op2, doc2] = genOp(origDoc) - const expectOp1 = deepClone(op1) - const expectOp2 = deepClone(op2) + const expectOp1 = deepClone(op1) + const expectOp2 = deepClone(op2) - type.transformNoConflict(op1, op2, 'left') - type.transformNoConflict(op2, op1, 'right') - assert.deepStrictEqual(op1, expectOp1) - result.push(assert.deepStrictEqual(op2, expectOp2)) - } - return result - })()) + type.transformNoConflict(op1, op2, 'left') + type.transformNoConflict(op2, op1, 'right') + assert.deepStrictEqual(op1, expectOp1) + assert.deepStrictEqual(op2, expectOp2) + } + }) }) From 567f5c5d6fbe7c4526f77ce695d9b27ad10a5699 Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 10:56:12 +0530 Subject: [PATCH 11/11] Manually clean test/test.js. Closes #7 --- test/test.js | 482 ++++++++++++++++++++++++--------------------------- 1 file changed, 229 insertions(+), 253 deletions(-) diff --git a/test/test.js b/test/test.js index f924b67..e59b242 100644 --- a/test/test.js +++ b/test/test.js @@ -1,10 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS205: Consider reworking code to avoid use of IIFEs - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ // Unit tests for the JSON1 OT type. // // These tests are quite unstructured. You can see the skeletons of a few @@ -13,7 +6,6 @@ // Cleanups welcome, so long as you don't remove any tests. const assert = require('assert') -// {type} = require '../index' const { type } = require('../lib/json1') const log = require('../lib/log') const deepClone = require('../lib/deepClone') @@ -21,7 +13,7 @@ const deepClone = require('../lib/deepClone') const { transform } = type const { DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE } = type -const apply = function({ doc: snapshot, op, expect }) { +const apply = ({ doc: snapshot, op, expect }) => { type.setDebug(false) const orig = deepClone(snapshot) @@ -31,22 +23,23 @@ const apply = function({ doc: snapshot, op, expect }) { return assert.deepStrictEqual(result, expect) } catch (e) { console.log( - `Apply failed! Repro apply( ${JSON.stringify(snapshot)}, ${JSON.stringify( - op - )} )` + [ + 'Apply failed! Repro ', + `apply( ${JSON.stringify(snapshot)}, ${JSON.stringify(op)} )` + ].join('') ) console.log(`expected output: ${JSON.stringify(expect)}`) throw e } } -const d = function(fn) { +const d = fn => { type.setDebug(true) fn() return type.setDebug(false) } -const compose = function({ op1, op2, expect }) { +const compose = ({ op1, op2, expect }) => { try { const result = type.compose( op1, @@ -54,7 +47,7 @@ const compose = function({ op1, op2, expect }) { ) return assert.deepStrictEqual(result, expect) } catch (e) { - d(function() { + d(() => { console.error('FAIL! Repro with:') console.log(`compose( ${JSON.stringify(op1)}, ${JSON.stringify(op2)} )`) console.log(`expected output: ${JSON.stringify(expect)}`) @@ -69,20 +62,15 @@ const compose = function({ op1, op2, expect }) { const invConflict = ({ type, op1, op2 }) => ({ type, op1: op2, op2: op1 }) -const otherSide = function(side) { - if (side === 'left') { - return 'right' - } else { - return 'left' - } -} -const checkConflict = function({ +const otherSide = side => (side === 'left' ? 'right' : 'left') + +const checkConflict = ({ op1, op2, side, conflict: expectConflict, expect -}) { +}) => { // We should get the same conflict with xf(op1, op2, left) and xf(op2, op1, right). if (expectConflict != null) { if (!expectConflict.op1) { @@ -93,48 +81,45 @@ const checkConflict = function({ } } - return (() => { - const result = [] - for (var [side_, op1_, op2_, ec] of [ - [side, op1, op2, expectConflict], - [ - otherSide(side), - op2, - op1, - expectConflict ? invConflict(expectConflict) : null - ] - ]) { - try { - // d -> log('tryTransform', side_, op1_, op2_) - const { ok, conflict } = type.tryTransform(op1_, op2_, side_) - if (ec == null) { - // We don't care what the result is here; just that it doesn't conflict. - result.push(assert(ok)) - } else { - assert(!ok, `Conflict erroneously succeeded (${side_})`) - // d -> log('conflict', conflict) - conflict.op1 = type.normalize(conflict.op1) - conflict.op2 = type.normalize(conflict.op2) - result.push(assert.deepStrictEqual(conflict, ec)) - } - } catch (e) { - d(function() { - console.error('FAIL! Repro with:') - console.log( - `tryTransform(${JSON.stringify(op1_)}, ${JSON.stringify( - op2_ - )}, '${side_}')` - ) - return type.tryTransform(op1_, op2_, side_) - }) - throw e + const result = [] + for (let [side_, op1_, op2_, ec] of [ + [side, op1, op2, expectConflict], + [ + otherSide(side), + op2, + op1, + expectConflict ? invConflict(expectConflict) : null + ] + ]) { + try { + // d -> log('tryTransform', side_, op1_, op2_) + const { ok, conflict } = type.tryTransform(op1_, op2_, side_) + if (ec == null) { + // We don't care what the result is here; just that it doesn't conflict. + result.push(assert(ok)) + } else { + assert(!ok, `Conflict erroneously succeeded (${side_})`) + // d -> log('conflict', conflict) + conflict.op1 = type.normalize(conflict.op1) + conflict.op2 = type.normalize(conflict.op2) + result.push(assert.deepStrictEqual(conflict, ec)) } + } catch (e) { + d(() => { + console.error('FAIL! Repro with:') + console.log( + `tryTransform(${JSON.stringify(op1_)}, ${JSON.stringify( + op2_ + )}, '${side_}')` + ) + return type.tryTransform(op1_, op2_, side_) + }) + throw e } - return result - })() + } } -const xf = function({ +const xf = ({ op1, op2, conflict, @@ -143,7 +128,7 @@ const xf = function({ expect, expectLeft, expectRight -}) { +}) => { if (expect !== undefined) { expectLeft = expectRight = expect } @@ -151,39 +136,33 @@ const xf = function({ conflictLeft = conflictRight = conflict } - return (() => { - const result1 = [] - for (var [side, e, c] of [ - ['left', expectLeft, conflictLeft], - ['right', expectRight, conflictRight] - ]) { - checkConflict({ op1, op2, side, conflict: c, expect: e }) - - try { - const result = - c != null - ? type.transformNoConflict(op1, op2, side) - : transform(op1, op2, side) - result1.push(assert.deepStrictEqual(result, e)) - } catch (error) { - e = error - d(function() { - console.error('FAIL! Repro with:') - return console.log( - `transform(${JSON.stringify(op1)}, ${JSON.stringify( - op2 - )}, '${side}')` - ) - }) - // if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side - throw e - } + for (let [side, e, c] of [ + ['left', expectLeft, conflictLeft], + ['right', expectRight, conflictRight] + ]) { + checkConflict({ op1, op2, side, conflict: c, expect: e }) + + try { + const result = + c != null + ? type.transformNoConflict(op1, op2, side) + : transform(op1, op2, side) + assert.deepStrictEqual(result, e) + } catch (error) { + e = error + d(() => { + console.error('FAIL! Repro with:') + console.log( + `transform(${JSON.stringify(op1)}, ${JSON.stringify(op2)}, '${side}')` + ) + }) + // if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side + throw e } - return result1 - })() + } } -const diamond = function({ doc, op1, op2 }) { +const diamond = ({ doc, op1, op2 }) => { let doc1, doc12, doc2, doc21, op1_, op2_ type.setDebug(false) @@ -216,7 +195,7 @@ const diamond = function({ doc, op1, op2 }) { } } -const path = function(path, { op, expect }) { +const path = (path, { op, expect }) => { if (expect === undefined) { expect = path.slice() } @@ -229,18 +208,18 @@ const path = function(path, { op, expect }) { const expect2 = expect != null ? expect.concat('x') : null const result2 = type.transformPosition(path2, op) - return assert.deepStrictEqual(result2, expect2) + assert.deepStrictEqual(result2, expect2) } -describe('json1', function() { - before(function() { +describe('json1', () => { + before(() => { type.registerSubtype(require('ot-simple')) return type.setDebug(true) }) after(() => type.setDebug(false)) - describe('checkOp', function() { - const pass = function(op) { + describe('checkOp', () => { + const pass = op => { try { return type.checkValidOp(op) } catch (e) { @@ -249,7 +228,7 @@ describe('json1', function() { } } - const fail = function(op) { + const fail = op => { try { return assert.throws(() => type.checkValidOp(op)) } catch (e) { @@ -259,7 +238,7 @@ describe('json1', function() { } } - it('allows some simple valid ops', function() { + it('allows some simple valid ops', () => { pass(null) pass([{ i: [1, 2, 3] }]) pass([{ r: {} }]) @@ -268,10 +247,10 @@ describe('json1', function() { pass([['a', { p: 0 }], ['b', { d: 0 }], ['x', { p: 1 }], ['y', { d: 1 }]]) pass([{ e: 'hi', et: 'simple' }]) pass([{ es: ['hi'] }]) - return pass([{ ena: 5 }]) + pass([{ ena: 5 }]) }) - it('disallows invalid syntax', function() { + it('disallows invalid syntax', () => { fail(undefined) fail({}) fail('hi') @@ -283,29 +262,29 @@ describe('json1', function() { fail([{ invalid: true }]) fail([10, {}]) fail([10, { invalid: true }]) - return fail([10, 'hi']) + fail([10, 'hi']) }) - it('throws if there is any empty leaves', function() { + it('throws if there is any empty leaves', () => { fail([]) fail(['x']) fail(['x', {}]) fail(['x', []]) fail([10]) fail([10, {}]) - return fail([10, []]) + fail([10, []]) }) - it('ensures path components are non-zero integers or strings', function() { + it('ensures path components are non-zero integers or strings', () => { fail([-1, { r: {} }]) fail([0.5, { r: {} }]) fail([true, { r: {} }]) fail([false, { r: {} }]) fail([null, { r: {} }]) - return fail([undefined, { r: {} }]) + fail([undefined, { r: {} }]) }) - it('does not allow two pickups or two drops in a component', function() { + it('does not allow two pickups or two drops in a component', () => { fail([{ p: 0, r: {} }]) fail([{ p: 1, r: {} }]) fail(['x', { p: 0, r: {} }]) @@ -314,123 +293,117 @@ describe('json1', function() { fail([{ d: 0, i: 'hi' }]) fail([{ d: 1, i: 'hi' }]) fail([10, { d: 0, i: 'hi' }]) - return fail([10, { d: 1, i: 'hi' }]) + fail([10, { d: 1, i: 'hi' }]) }) - it('throws if there are mismatched pickups / drops', function() { + it('throws if there are mismatched pickups / drops', () => { fail([{ p: 0 }]) fail([{ d: 0 }]) fail(['x', { p: 0 }]) fail([10, { p: 0 }]) fail(['x', { d: 0 }]) - return fail([10, { d: 0 }]) + fail([10, { d: 0 }]) }) - it('throws if pick/drop indexes dont start at 0', function() { + it('throws if pick/drop indexes dont start at 0', () => { fail([['x', { p: 1 }], ['y', { d: 1 }]]) - return fail([[10, { p: 1 }], [20, { d: 1 }]]) + fail([[10, { p: 1 }], [20, { d: 1 }]]) }) it('throws if a descent starts with an edit', () => fail([10, [{ i: 'hi' }]])) - it('throws if descents are out of order', function() { + it('throws if descents are out of order', () => { fail(['x', ['b', { r: {} }], ['a', { r: {} }]]) fail(['x', [10, { r: {} }], [5, { r: {} }]]) fail(['x', ['a', { r: {} }], [5, { r: {} }]]) fail(['x', ['a', { r: {} }], ['a', { r: {} }]]) - return fail(['x', [10, { r: {} }], [10, { r: {} }]]) + fail(['x', [10, { r: {} }], [10, { r: {} }]]) }) it('throws if descents start with the same scalar', () => fail(['x', ['a', { r: {} }], ['a', { e: {} }]])) - it('throws if descents have two adjacent edits', function() { + it('throws if descents have two adjacent edits', () => { fail([{ r: {} }, { p: 0 }]) fail(['x', { r: {} }, { p: 0 }]) - return fail(['x', { r: {} }, { p: 0 }, 'y', { r: {} }]) + fail(['x', { r: {} }, { p: 0 }, 'y', { r: {} }]) }) - it.skip('does not allow ops to overwrite their own inserted data', function() { + it.skip('does not allow ops to overwrite their own inserted data', () => { fail([{ i: { x: 5 } }, 'x', { i: 6 }]) - return fail([{ i: ['hi'] }, 0, { i: 'omg' }]) + fail([{ i: ['hi'] }, 0, { i: 'omg' }]) }) - it.skip('does not allow immediate data directly parented in other immediate data', function() { + it.skip('does not allow immediate data directly parented in other immediate data', () => { fail([{ i: {} }, 'x', { i: 5 }]) fail([{ i: { x: 5 } }, 'x', 'y', { i: 6 }]) - return fail([{ i: [] }, 0, { i: 5 }]) + fail([{ i: [] }, 0, { i: 5 }]) }) it('does not allow the final item to be a single descent', () => fail(['a', ['b', { r: {} }]])) // It should be ['a', 'b', r:{}] - it('does not allow anything after the descents at the end', function() { + it('does not allow anything after the descents at the end', () => { fail([[1, { r: {} }], [2, { r: {} }], 5]) fail([[1, { r: {} }], [2, { r: {} }], 5, { r: {} }]) - return fail([[1, { r: {} }], [2, { r: {} }], { r: {} }]) + fail([[1, { r: {} }], [2, { r: {} }], { r: {} }]) }) - it('allows removes inside removes', function() { + it('allows removes inside removes', () => { pass(['x', { r: true }, 'y', { r: true }]) pass(['x', { r: {} }, 'y', { r: true }]) pass([ ['x', { r: true }, 'y', { p: 0 }, 'z', { r: true }], ['y', { d: 0 }] ]) - return pass([ - ['x', { r: {} }, 'y', { p: 0 }, 'z', { r: true }], - ['y', { d: 0 }] - ]) + pass([['x', { r: {} }, 'y', { p: 0 }, 'z', { r: true }], ['y', { d: 0 }]]) }) - it('allows inserts inside inserts', function() { + it('allows inserts inside inserts', () => { pass([1, { i: {} }, 'x', { i: 10 }]) - return pass([ - [0, 'x', { p: 0 }], - [1, { i: {} }, 'x', { d: 0 }, 'y', { i: 10 }] - ]) + pass([[0, 'x', { p: 0 }], [1, { i: {} }, 'x', { d: 0 }, 'y', { i: 10 }]]) }) - it.skip('fails if the operation drops items inside something it picked up', function() { + it.skip('fails if the operation drops items inside something it picked up', () => { fail(['x', { r: true }, 1, { i: 'hi' }]) fail(['x', { d: 0 }, 1, { p: 0 }]) - return fail([{ r: true }, 1, { p: 0, d: 0 }]) + fail([{ r: true }, 1, { p: 0, d: 0 }]) }) - return describe('edit', function() { - it('requires all edits to specify their type', function() { + describe('edit', () => { + it('requires all edits to specify their type', () => { fail([{ e: {} }]) fail([5, { e: {} }]) - return pass([{ e: {}, et: 'simple' }]) + pass([{ e: {}, et: 'simple' }]) }) - it('allows edits to have null or false for the operation', function() { + it('allows edits to have null or false for the operation', () => { // These aren't valid operations according to the simple type, but the // type doesn't define a checkValidOp so we wouldn't be able to tell // anyway. pass([{ e: null, et: 'simple' }]) pass([5, { e: null, et: 'simple' }]) pass([{ e: false, et: 'simple' }]) - return pass([5, { e: false, et: 'simple' }]) + pass([5, { e: false, et: 'simple' }]) }) - it('does not allow an edit to use an unregistered type', function() { + it('does not allow an edit to use an unregistered type', () => { fail([{ e: {}, et: 'an undefined type' }]) - return fail([{ e: null, et: 'an undefined type' }]) + fail([{ e: null, et: 'an undefined type' }]) }) - it('does not allow two edits in the same operation', function() { + it('does not allow two edits in the same operation', () => { fail([{ e: {}, et: 'simple', es: [1, 2, 3] }]) fail([{ es: [], ena: 5 }]) - return fail([{ e: {}, et: 'simple', ena: 5 }]) + fail([{ e: {}, et: 'simple', ena: 5 }]) }) it('fails if the type is missing', () => fail([{ et: 'missing', e: {} }])) it('does not allow anything inside an edited subtree') - it.skip('does not allow an edit inside removed or picked up content', function() { + it.skip('does not allow an edit inside removed or picked up content', () => { fail([{ r: true }, 1, { es: ['hi'] }]) pass([1, { r: true }, 1, { es: ['hi'] }]) fail(['x', { r: true }, 1, { es: ['hi'] }]) @@ -438,35 +411,35 @@ describe('json1', function() { fail([['x', { p: 0 }, 1, { es: ['hi'] }], ['y', { d: 0 }]]) // This is actually ok. - return pass([0, { p: 0 }, ['a', { es: [], r: true }], ['x', { d: 0 }]]) + pass([0, { p: 0 }, ['a', { es: [], r: true }], ['x', { d: 0 }]]) }) - return it.skip('does not allow you to drop inside something that was removed', function() { + it.skip('does not allow you to drop inside something that was removed', () => { // These insert into the next list item pass([[1, { r: true }, 1, { d: 0 }], [2, { p: 0 }]]) pass([1, { p: 0 }, 'x', { d: 0 }]) // But this is not ok. - return fail(['x', { p: 0 }, 'a', { d: 0 }]) + fail(['x', { p: 0 }, 'a', { d: 0 }]) }) }) }) - describe('normalize', function() { - const n = function(opIn, expect) { + describe('normalize', () => { + const n = (opIn, expect) => { if (expect === undefined) { expect = opIn } const op = type.normalize(opIn) - return assert.deepStrictEqual(op, expect) + assert.deepStrictEqual(op, expect) } - it('does the right thing for noops', function() { + it('does the right thing for noops', () => { n(null) - return n([], null) + n([], null) }) - it('normalizes some regular ops', function() { + it('normalizes some regular ops', () => { n([{ i: 'hi' }]) n([{ i: 'hi' }, 1, 2, 3], [{ i: 'hi' }]) n([[1, 2, 3, { p: 0 }], [1, 2, 3, { d: 0 }]], [1, 2, 3, { p: 0, d: 0 }]) @@ -474,7 +447,7 @@ describe('json1', function() { [[1, 2, 3, { p: 0 }], [1, 2, 30, { d: 0 }]], [1, 2, [3, { p: 0 }], [30, { d: 0 }]] ) - return n( + n( [[1, 2, 30, { p: 0 }], [1, 2, 3, { d: 0 }]], [1, 2, [3, { d: 0 }], [30, { p: 0 }]] ) @@ -482,46 +455,46 @@ describe('json1', function() { it('will let you insert null', () => n([{ i: null }])) - it('normalizes embedded ops when available', function() { + it('normalizes embedded ops when available', () => { n([{ es: [0, 'hi'] }], [{ es: ['hi'] }]) n([{ et: 'text-unicode', e: ['hi'] }], [{ es: ['hi'] }]) n([{ et: 'text-unicode', e: [0, 'hi'] }], [{ es: ['hi'] }]) n([{ et: 'simple', e: {} }]) n([{ et: 'number', e: 5 }], [{ ena: 5 }]) - return n([{ ena: 5 }]) + n([{ ena: 5 }]) }) - it.skip('normalizes embedded removes', function() { + it.skip('normalizes embedded removes', () => { n([1, { r: true }, 2, { r: true }], [1, { r: true }]) - return n([{ r: true }, 2, { r: true }], [{ r: true }]) + n([{ r: true }, 2, { r: true }], [{ r: true }]) }) it('throws if the type is missing', () => // Not sure if this is the best behaviour but ... eh. assert.throws(() => n([{ et: 'missing', e: {} }]))) - return it('corrects weird pick and drop ids', () => + it('corrects weird pick and drop ids', () => n([['x', { p: 1 }], ['y', { d: 1 }]], [['x', { p: 0 }], ['y', { d: 0 }]])) }) // ****** Apply ****** - describe('apply', function() { - it('Can set properties', function() { + describe('apply', () => { + it('Can set properties', () => { apply({ doc: [], op: [0, { i: 17 }], expect: [17] }) - return apply({ + apply({ doc: {}, op: ['x', { i: 5 }], expect: { x: 5 } }) }) - it('can edit the root', function() { + it('can edit the root', () => { apply({ doc: { x: 5 }, op: [{ r: true }], @@ -554,7 +527,7 @@ describe('json1', function() { expect: 5 }) - return apply({ + apply({ doc: { x: 5 }, op: [{ r: {}, i: [1, 2, 3] }], expect: [1, 2, 3] @@ -626,17 +599,17 @@ describe('json1', function() { expect: { y: 'supyooo' } })) - it('throws when the op traverses missing items', function() { + it('throws when the op traverses missing items', () => { assert.throws(() => type.apply([0, 'hi'], [1, { p: 0 }, 'x', { d: 0 }])) - return assert.throws(() => type.apply({}, [{ p: 0 }, 'a', { d: 0 }])) + assert.throws(() => type.apply({}, [{ p: 0 }, 'a', { d: 0 }])) }) - return it('throws if the type is missing', () => + it('throws if the type is missing', () => assert.throws(() => type.apply({}, [{ et: 'missing', e: {} }]))) }) - describe('apply path', function() { - it('does not modify path when op is unrelated', function() { + describe('apply path', () => { + it('does not modify path when op is unrelated', () => { path(['a', 'b', 'c'], { op: null }) path(['a', 'b', 'c'], { op: ['x', { i: 5 }] }) path(['a', 'b', 'c'], { op: ['x', { r: true }] }) @@ -644,26 +617,26 @@ describe('json1', function() { path([1, 2, 3], { op: [2, { i: 5 }] }) path([1, 2, 3], { op: [1, 2, 4, { i: 5 }] }) path([1], { op: [1, 2, { r: true }] }) - return path(['x'], { op: ['x', 'y', { r: true }] }) + path(['x'], { op: ['x', 'y', { r: true }] }) }) - it('adjusts list indicies', function() { + it('adjusts list indicies', () => { path([2], { op: [1, { i: 5 }], expect: [3] }) path([2], { op: [2, { i: 5 }], expect: [3] }) path([2], { op: [1, { r: true }], expect: [1] }) path([2], { op: [[1, { p: 0 }], [3, { d: 0 }]], expect: [1] }) path([2], { op: [[1, { d: 0 }], [3, { p: 0 }]], expect: [3] }) - return path([2], { op: [[2, { d: 0 }], [3, { p: 0 }]], expect: [3] }) + path([2], { op: [[2, { d: 0 }], [3, { p: 0 }]], expect: [3] }) }) - it('returns null when the object at the path was removed', function() { + it('returns null when the object at the path was removed', () => { path(['x'], { op: [{ r: true }], expect: null }) path(['x'], { op: ['x', { r: true }], expect: null }) path([1], { op: [{ r: true }], expect: null }) - return path([1], { op: [1, { r: true }], expect: null }) + path([1], { op: [1, { r: true }], expect: null }) }) - it('moves the path', function() { + it('moves the path', () => { path(['a', 'z'], { op: [['a', { p: 0 }], ['y', { d: 0 }]], expect: ['y', 'z'] @@ -676,7 +649,7 @@ describe('json1', function() { path([1, 2], { op: [[1, { p: 0 }], [10, { d: 0 }]], expect: [10, 2] }) path([1, 2], { op: [[1, 2, { p: 0 }], [10, { d: 0 }]], expect: [10] }) path([1, 2], { op: [1, [1, { d: 0 }], [2, { p: 0 }]], expect: [1, 1] }) - return path([1, 2], { op: [[1, 2, 3, { p: 0 }], [10, { d: 0 }]] }) + path([1, 2], { op: [[1, 2, 3, { p: 0 }], [10, { d: 0 }]] }) }) it('handles pick parent and move', () => @@ -691,14 +664,14 @@ describe('json1', function() { expect: ['x', 'b', 9] })) - it.skip('gen ops', function() {}) + it.skip('gen ops', () => {}) // This should do something like: // - Generate a document // - Generate op, a random operation // - Generate a path to somewhere in the document and an edit we can do there -> op2 // - Check that transform(op2, op) == op2 at transformPosition(path) or something like that. - return it('calls transformPosition with embedded string edits if available', function() { + it('calls transformPosition with embedded string edits if available', () => { // For embedded string operations (and other things that have // transformPosition or transformPosition or whatever) we should call that. path(['x', 'y', 'z', 1], { @@ -709,7 +682,7 @@ describe('json1', function() { op: ['x', 'y', 'z', { es: ['💃'] }], expect: ['x', 'y', 'z', 2] }) - return path(['x', 'y', 'z'], { + path(['x', 'y', 'z'], { op: ['x', 'y', 'z', { es: ['💃'] }], expect: ['x', 'y', 'z'] }) @@ -718,7 +691,7 @@ describe('json1', function() { // ******* Compose ******* - describe('compose', function() { + describe('compose', () => { it('composes empty ops to nothing', () => compose({ op1: null, @@ -726,7 +699,7 @@ describe('json1', function() { expect: null })) - describe('op1 drop', function() { + describe('op1 drop', () => { it('vs remove', () => compose({ op1: [['x', { p: 0 }], ['y', { d: 0 }]], @@ -762,7 +735,7 @@ describe('json1', function() { expect: [['x', { p: 0 }], ['z', { d: 0 }]] })) - return it('is transformed by op2 picks', () => + it('is transformed by op2 picks', () => compose({ op1: [['x', { p: 0 }], ['y', 10, { d: 0 }]], op2: ['y', 0, { r: true }], @@ -770,7 +743,7 @@ describe('json1', function() { })) }) - describe('op1 insert', function() { + describe('op1 insert', () => { it('vs remove', () => compose({ op1: ['x', { i: { a: 'hi' } }], @@ -863,7 +836,7 @@ describe('json1', function() { expect: [{ i: 'hi', es: [2, ' there'] }] })) - return it('vs op2 number edit', () => + it('vs op2 number edit', () => compose({ op1: [{ i: 10 }], op2: [{ ena: 20 }], @@ -871,7 +844,7 @@ describe('json1', function() { })) }) - describe('op1 edit', function() { + describe('op1 edit', () => { it('removes the edit if the edited object is deleted', () => compose({ op1: ['x', { es: ['hi'] }], @@ -928,7 +901,7 @@ describe('json1', function() { expect: ['x', { r: true, i: 'yo', es: [2, ' there'] }] })) - return it('throws if the type is missing', () => + it('throws if the type is missing', () => assert.throws(() => type.compose( [{ et: 'missing', e: {} }], @@ -960,7 +933,7 @@ describe('json1', function() { expect: [[0, { p: 0 }, 'x', 1, { d: 0 }], [1, 'x', 0, { r: true }]] }))) - describe('setnull interaction', function() { + describe('setnull interaction', () => { // Currently failing. it('reorders items inside a setnull region', () => compose({ @@ -976,7 +949,7 @@ describe('json1', function() { expect: [['list', { i: [] }], ['z', { i: 'hi' }]] })) - return it('lets a setnull child get modified', () => + it('lets a setnull child get modified', () => compose({ op1: [{ i: [] }, 0, { i: ['a'] }], op2: [0, 0, { r: 'a', i: 'b' }], @@ -985,7 +958,7 @@ describe('json1', function() { }) //expect: [{i:[]}, 0, {i:['b']}] # Maybe better?? - return describe('regression', function() { + describe('regression', () => { it('skips op2 drops when calculating op1 drop index simple', () => compose({ op1: [[0, { p: 0 }], [2, { d: 0 }]], @@ -1009,7 +982,7 @@ describe('json1', function() { expect: [{ i: [[]] }, [0, { i: '' }], [1, 0, { i: null }]] })) - return it('4', () => + it('4', () => compose({ // This one triggered a bug in cursor! op1: [0, [0, ['a', { r: true }], ['b', { d: 0 }]], [2, { p: 0 }]], @@ -1024,8 +997,8 @@ describe('json1', function() { }) // *** Old stuff - describe('old compose', function() { - it('gloms together unrelated edits', function() { + describe('old compose', () => { + it('gloms together unrelated edits', () => { compose({ op1: [['a', { p: 0 }], ['b', { d: 0 }]], op2: [['x', { p: 0 }], ['y', { d: 0 }]], @@ -1037,7 +1010,7 @@ describe('json1', function() { ] }) - return compose({ + compose({ op1: [2, { i: 'hi' }], op2: [0, 'x', { r: true }], expect: [[0, 'x', { r: true }], [2, { i: 'hi' }]] @@ -1076,7 +1049,7 @@ describe('json1', function() { expect: [['x', { i: { b: 2, c: 3 } }], ['y', { i: 1 }]] })) - return it('does not merge mutual inserts', () => + it('does not merge mutual inserts', () => compose({ op1: [{ i: {} }], op2: ['x', { i: 'hi' }], @@ -1090,8 +1063,8 @@ describe('json1', function() { // ****** Transform ****** - describe('transform', function() { - describe('op1 pick', function() { + describe('transform', () => { + describe('op1 pick', () => { it('vs delete', () => xf({ op1: [['x', { p: 0 }], ['y', { d: 0 }]], @@ -1198,7 +1171,7 @@ describe('json1', function() { expectRight: null })) - return it('vs pick, edit', () => ({ + it('vs pick, edit', () => ({ op1: [['x', { p: 0 }], ['z', { d: 0 }]], op2: [['x', { es: ['hi'], p: 0 }], ['y', { d: 0 }]], expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], @@ -1206,7 +1179,7 @@ describe('json1', function() { })) }) - describe('op1 delete', function() { + describe('op1 delete', () => { it('vs delete', () => xf({ op1: ['x', { r: true }], @@ -1304,7 +1277,7 @@ describe('json1', function() { expect: [['x', { r: true }], ['z', { r: true }, 'a', { r: true }]] })) - return it('mess', () => + it('mess', () => xf({ // yeesh op1: [['x', { r: true }, 'y', 'z', { p: 0 }], ['z', { d: 0 }]], @@ -1318,7 +1291,7 @@ describe('json1', function() { }) }) - describe('op1 drop', function() { + describe('op1 drop', () => { it('vs delete parent', () => xf({ op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], @@ -1432,7 +1405,7 @@ describe('json1', function() { expectRight: null })) - return it('multiple', () => + it('multiple', () => xf({ // a->x.a, b->x.b op1: [ @@ -1451,7 +1424,7 @@ describe('json1', function() { }) }) - describe('op1 insert', function() { + describe('op1 insert', () => { it('vs delete parent', () => xf({ op1: ['y', 'a', { i: 5 }], @@ -1533,7 +1506,7 @@ describe('json1', function() { }) }) - return it('with embedded edits', () => + it('with embedded edits', () => xf({ op1: [{ i: '', es: ['aaa'] }], op2: [{ i: '', es: ['bbb'] }], @@ -1542,7 +1515,7 @@ describe('json1', function() { })) }) - describe('op1 edit', function() { + describe('op1 edit', () => { it('vs delete', () => xf({ op1: ['x', { es: ['hi'] }], @@ -1592,7 +1565,7 @@ describe('json1', function() { expectRight: ['y', { es: [2, 'ab'] }] })) - return it('throws if the type is missing', () => + it('throws if the type is missing', () => assert.throws(() => type.transform( [{ et: 'missing', e: {} }], @@ -1602,7 +1575,7 @@ describe('json1', function() { )) }) - describe('op2 cancel move', function() { + describe('op2 cancel move', () => { it('and insert', () => xf({ op1: ['x', { r: true }], @@ -1614,7 +1587,7 @@ describe('json1', function() { expect: [['x', { r: true }], ['y', { r: true }, 'b', { r: true }]] })) - return it('and another move (rm x vs x.a -> y, q -> y.b)', () => + it('and another move (rm x vs x.a -> y, q -> y.b)', () => xf({ op1: ['x', { r: true }], op2: [ @@ -1630,7 +1603,7 @@ describe('json1', function() { })) }) - describe('op2 list move an op1 drop', function() { + describe('op2 list move an op1 drop', () => { it('vs op1 remove', () => xf({ op1: [[0, { r: true }, 'a', { i: 'hi' }], [5, { r: true }]], @@ -1660,7 +1633,7 @@ describe('json1', function() { expect: [[0, { i: 'a' }], [1, { i: 'b' }], [3, 'a', { i: 'hi' }]] })) - return it('vs op1 insert before and replace', () => + it('vs op1 insert before and replace', () => xf({ op1: [[0, { i: 'xx' }, 'a', { r: true }], [1, 'a', { i: 'hi' }]], op2: [[0, { p: 0 }], [3, { d: 0 }]], @@ -1672,8 +1645,8 @@ describe('json1', function() { })) }) - return describe('list', () => - describe('drop', function() { + describe('list', () => + describe('drop', () => { it('transforms by p1 drops', () => xf({ op1: [[5, { i: 5 }], [10, { i: 10 }]], @@ -1684,12 +1657,12 @@ describe('json1', function() { it('transforms by p1 picks') it('transforms by p2 picks') - return it('transforms by p2 drops') + it('transforms by p2 drops') })) }) - describe('conflicts', function() { - describe('drop into remove / rm unexpected', function() { + describe('conflicts', () => { + describe('drop into remove / rm unexpected', () => { // xfConflict does both xf(op1, op2, left) and xf(op2, op1, right), and // uses invConflict. So this also tests RM_UNEXPECTED_CONTENT with each // test case. @@ -1743,7 +1716,7 @@ describe('json1', function() { expectRight: null })) - return it.skip('returns symmetric errors when both ops delete the other', () => + it.skip('returns symmetric errors when both ops delete the other', () => xf({ // The problem here is that there's two conflicts we want to return. // Which one should be returned first? It'd be nice for the order of @@ -1757,7 +1730,7 @@ describe('json1', function() { })) }) - describe('overlapping drop', function() { + describe('overlapping drop', () => { it('errors if two ops insert different content into the same place in an object', () => xf({ op1: ['x', { i: 'hi' }], @@ -1827,7 +1800,7 @@ describe('json1', function() { expectRight: ['a', { r: true }] })) - return it('errors if the two sides insert in the vacuum', () => + it('errors if the two sides insert in the vacuum', () => xf({ op1: [['a', { p: 0 }], ['b', { d: 0 }], ['c', { i: 5 }]], op2: [['a', { p: 0 }], ['b', { i: 6 }], ['c', { d: 0 }]], @@ -1846,7 +1819,7 @@ describe('json1', function() { })) }) - describe('discarded edit', function() { + describe('discarded edit', () => { it('edit removed directly', () => xf({ op1: ['a', { es: [] }], @@ -1855,7 +1828,7 @@ describe('json1', function() { expect: null })) - return it('edit inside new content throws RM_UNEXPECTED_CONTENT', () => + it('edit inside new content throws RM_UNEXPECTED_CONTENT', () => xf({ op1: ['a', 'b', { i: 'hi', es: [] }], op2: ['a', { r: true }], @@ -1867,7 +1840,7 @@ describe('json1', function() { })) }) - return describe('blackhole', function() { + describe('blackhole', () => { it('detects and errors', () => xf({ op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], @@ -1942,7 +1915,7 @@ describe('json1', function() { ] })) - return it('creates conflict return values with valid slot ids', () => + it('creates conflict return values with valid slot ids', () => xf({ op1: [ ['a', { p: 0 }], @@ -1964,7 +1937,7 @@ describe('json1', function() { }) }) - describe('transform-old', function() { + describe('transform-old', () => { it('foo', () => xf({ op1: [ @@ -1994,7 +1967,7 @@ describe('json1', function() { expectRight: null }))) // the object was moved fair and square. - describe('deletes', function() { + describe('deletes', () => { it.skip('delete parent of a move', () => xf({ // The current logic of transform actually just burns everything (in a @@ -2011,7 +1984,7 @@ describe('json1', function() { })) // TODO: It would be better to do this in both cases. //expectRight: ['x', r:true] - return it('awful delete nonsense', function() { + it('awful delete nonsense', () => { xf({ op1: [['x', { r: true }], ['y', { i: 'hi' }]], // delete doc.x, insert doc.y op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], // move doc.x.a -> doc.y @@ -2024,7 +1997,7 @@ describe('json1', function() { expect: null }) - return xf({ + xf({ op1: [10, { r: true }], op2: [[5, { d: 0 }], [10, 1, { p: 0 }]], expect: [[5, { r: true }], [11, { r: true }]] @@ -2033,7 +2006,7 @@ describe('json1', function() { }) // And how do those indexes interact with pick / drop operations?? - describe('swap', function() { + describe('swap', () => { const swap = [ ['a', { p: 0 }, 'b', { p: 1 }], ['b', { d: 1 }, 'a', { d: 0 }] @@ -2046,7 +2019,7 @@ describe('json1', function() { expect: null })) - return it('can swap two edits', () => + it('can swap two edits', () => xf({ op1: ['a', { es: ['a edit'] }, 'b', { es: ['b edit'] }], op2: swap, @@ -2054,8 +2027,8 @@ describe('json1', function() { })) }) - describe('lists', function() { - it('can rewrite simple list indexes', function() { + describe('lists', () => { + it('can rewrite simple list indexes', () => { xf({ op1: [10, { es: ['edit'] }], op2: [0, { i: 'oh hi' }], @@ -2068,7 +2041,7 @@ describe('json1', function() { expect: [11, { r: true }] }) - return xf({ + xf({ op1: [10, { i: {} }], op2: [0, { i: 'oh hi' }], expect: [11, { i: {} }] @@ -2096,14 +2069,14 @@ describe('json1', function() { expect: [0, { r: true, i: 'hi' }] })) - it('list drop vs delete uses the correct result index', function() { + it('list drop vs delete uses the correct result index', () => { xf({ op1: [2, { i: 'hi' }], op2: [2, { r: true }], expect: [2, { i: 'hi' }] }) - return xf({ + xf({ op1: [3, { i: 'hi' }], op2: [2, { r: true }], expect: [2, { i: 'hi' }] @@ -2118,7 +2091,7 @@ describe('json1', function() { expectRight: [3, { i: 'hi' }] })) - it('list drop vs delete and drop', function() { + it('list drop vs delete and drop', () => { xf({ op1: [2, { i: 'hi' }], op2: [2, { r: true, i: 'other' }], @@ -2132,7 +2105,7 @@ describe('json1', function() { expect: [2, { i: 'hi' }] }) - return xf({ + xf({ op1: [4, { i: 'hi' }], op2: [[2, { r: true }], [3, { i: 'other' }]], expectLeft: [3, { i: 'hi' }], @@ -2140,7 +2113,7 @@ describe('json1', function() { }) }) - it('list delete vs drop', function() { + it('list delete vs drop', () => { xf({ op1: [1, { r: true }], op2: [2, { i: 'hi' }], @@ -2153,7 +2126,7 @@ describe('json1', function() { expect: [3, { r: true }] }) - return xf({ + xf({ op1: [3, { r: true }], op2: [2, { i: 'hi' }], expect: [4, { r: true }] @@ -2221,7 +2194,7 @@ describe('json1', function() { op2: [1, { r: true }] })) - return it('xxxxx 2', () => + it('xxxxx 2', () => diamond({ doc: Array.from('abcdef'), op1: [[1, { p: 0, i: 'AAA' }], [3, { d: 0 }], [5, { i: 'CCC' }]], @@ -2229,7 +2202,7 @@ describe('json1', function() { })) }) - return describe('edit', function() { + describe('edit', () => { it('transforms edits by one another', () => xf({ op1: [1, { es: [2, 'hi'] }], @@ -2259,7 +2232,7 @@ describe('json1', function() { expect: [2, { es: [4, 'hi'] }] })) - return it('an edit on a deleted object goes away', () => + it('an edit on a deleted object goes away', () => xf({ op1: [1, { es: [2, 'hi'] }], op2: [1, { r: 'yo' }], @@ -2275,7 +2248,7 @@ describe('json1', function() { // TODO Numbers // ***** Test cases found by the fuzzer which have caused issues - return describe('fuzzer tests', function() { + describe('fuzzer tests', () => { it('asdf', () => apply({ doc: { the: '', Twas: 'the' }, @@ -2315,14 +2288,14 @@ describe('json1', function() { expectRight: null })) - it('inserts before edits', function() { + it('inserts before edits', () => { xf({ op1: [0, 'x', { i: 5 }], op2: [0, { i: 35 }], expect: [1, 'x', { i: 5 }] }) - return xf({ + xf({ op1: [0, { es: [] }], op2: [0, { i: 35 }], expect: [1, { es: [] }] @@ -2355,7 +2328,7 @@ describe('json1', function() { }) ) - it('p1 pick descends correctly', function() { + it('p1 pick descends correctly', () => { xf({ op1: [2, { r: true }, 1, { es: ['hi'] }], op2: [3, 1, { r: true }], @@ -2366,7 +2339,7 @@ describe('json1', function() { expect: [2, { r: true }] }) - return xf({ + xf({ op1: [[2, { r: true }, 1, { es: ['hi'] }], [3, 1, { r: true }]], op2: [3, 2, { r: true }], conflict: { @@ -2555,14 +2528,14 @@ describe('json1', function() { expect: [[0, { i: 'yo' }], [1, 'b', { es: [] }]] })) - it('composes simple regression', function() { + it('composes simple regression', () => { compose({ op1: [0, { p: 0, d: 0 }], op2: [{ r: true }], expect: [{ r: true }, 0, { r: true }] }) - return compose({ + compose({ op1: ['a', 1, { r: true }], op2: ['a', { r: true }], expect: ['a', { r: true }, 1, { r: true }] @@ -2707,7 +2680,7 @@ describe('json1', function() { expect: ['a', { r: true }, ['aa', { r: true }], ['bb', { r: true }]] })) // Also ok if we miss the second rs. - it('op2 moves into op1 remove edge cases', function() { + it('op2 moves into op1 remove edge cases', () => { // Sorry not minified. xf({ op1: [ @@ -2740,7 +2713,7 @@ describe('json1', function() { ] }) - return xf({ + xf({ op1: [[0, [1, { p: 0 }], [2, { r: true }]], [1, 'xxx', { d: 0 }]], op2: [0, 1, { i: {}, p: 0 }, 'b', { d: 0 }], expectLeft: [ @@ -2776,7 +2749,7 @@ describe('json1', function() { expect: [0, { r: true }, 'x', { r: true }] })) - it('does not conflict when removed target gets moved inside removed container', function() { + it('does not conflict when removed target gets moved inside removed container', () => { // This edge case is interesting because we don't generate the same // conflicts on left and right. We want our move of a.x to escape the // object before removing it, but when we're right, the other operation's @@ -2803,7 +2776,10 @@ describe('json1', function() { expectRight: ['a', { r: true }, 0, { r: true }] }) - return { expect: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]] } + // TODO have a look at this - doesn't seem right to just define an object + { + expect: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]] + } }) it('compose copies op2 edit data', () => @@ -3067,7 +3043,7 @@ describe('json1', function() { expect: ['b', { r: true }, 'y', { r: true }] })) - return it('handles overlapping pick in blackholes', () => + it('handles overlapping pick in blackholes', () => xf({ // This looks complicated, but its really not so bad. Its: // a->b.0, a.x -> z