From 310a9bbaaa985dc3c33a1c2b373688bf5639d1e4 Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 06:32:54 +0530 Subject: [PATCH 1/7] Add README content for presence --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index cc1de54..6314a10 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,42 @@ The JSON OT type can be used to edit arbitrary JSON documents. +It has been forked from https://github.com/ottypes/json0 and modified to add Presence. + +## Presence + +(inspired by https://github.com/Teamwork/ot-rich-text#presence) + +The shape of our presence data is as follows: + +``` +{ + u: '123', // user ID + c: 8, // number of changes made by this user + s: [ // list of selections + [ 1, 1 ], // collapsed selection + [ 5, 7 ], // forward selection + [ 9, 4 ] // backward selection + ] +} +``` + +Each selection listed in `s` ends with a 2-element array containing the selection start index and the selection end index. The elements in the array preceeding the last two represent the path of a `text0` entry within the `json0` data structure. + +For example, the following entry in the `s` array represents the user's cursor position within the `content` field (`data.content`): + +``` +['content', 2, 2] +``` + +We can access deeply nested entries with this structure as well. For example, the following `s` entry represents a text selection in `data.files[3].text`: + +``` +['files', 3, 'text', 4, 7] +``` + +The rest of the README content is from the original repo https://github.com/ottypes/json0. + ## Features The JSON OT type supports the following operations: From 0350dcf0669acb9a29ad795422ace0b1568c8c5f Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 06:52:01 +0530 Subject: [PATCH 2/7] Decaffienate tests --- .gitignore | 1 + test/json0-generator.coffee | 176 -------------- test/json0-generator.js | 222 ++++++++++++++++++ test/json0.coffee | 405 -------------------------------- test/json0.js | 453 ++++++++++++++++++++++++++++++++++++ test/text0-generator.coffee | 30 --- test/text0-generator.js | 40 ++++ test/text0.coffee | 120 ---------- test/text0.js | 145 ++++++++++++ 9 files changed, 861 insertions(+), 731 deletions(-) delete mode 100644 test/json0-generator.coffee create mode 100644 test/json0-generator.js delete mode 100644 test/json0.coffee create mode 100644 test/json0.js delete mode 100644 test/text0-generator.coffee create mode 100644 test/text0-generator.js delete mode 100644 test/text0.coffee create mode 100644 test/text0.js diff --git a/.gitignore b/.gitignore index 691378f..3344b9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.swp *.DS_Store node_modules +package-lock.json diff --git a/test/json0-generator.coffee b/test/json0-generator.coffee deleted file mode 100644 index 7ca5734..0000000 --- a/test/json0-generator.coffee +++ /dev/null @@ -1,176 +0,0 @@ -json0 = require '../lib/json0' -{randomInt, randomReal, randomWord} = require 'ot-fuzzer' - -# 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) -> JSON.parse(JSON.stringify(o)) - -randomKey = (obj) -> - if Array.isArray(obj) - if obj.length == 0 - undefined - else - randomInt obj.length - else - count = 0 - - for key of obj - result = key if randomReal() < 1/++count - result - -# Generate a random new key for a value in obj. -# obj must be an Object. -randomNewKey = (obj) -> - # There's no do-while loop in coffeescript. - key = randomWord() - key = randomWord() while obj[key] != undefined - key - -# Generate a random object -randomThing = -> - switch randomInt 6 - when 0 then null - when 1 then '' - when 2 then randomWord() - when 3 - obj = {} - obj[randomNewKey(obj)] = randomThing() for [1..randomInt(5)] - obj - when 4 then (randomThing() for [1..randomInt(5)]) - when 5 then randomInt(50) - -# Pick a random path to something in the object. -randomPath = (data) -> - path = [] - - while randomReal() > 0.85 and typeof data == 'object' - key = randomKey data - break unless key? - - path.push key - data = data[key] - - path - - -module.exports = genRandomOp = (data) -> - pct = 0.95 - - container = data: clone data - - op = while randomReal() < pct - pct *= 0.6 - - # Pick a random object in the document operate on. - path = randomPath(container['data']) - - # parent = the container for the operand. parent[key] contains the operand. - parent = container - key = 'data' - for p in path - parent = parent[key] - key = p - operand = parent[key] - - if randomReal() < 0.4 and parent != container and Array.isArray(parent) - # List move - newIndex = randomInt parent.length - - # Remove the element from its current position in the list - parent.splice key, 1 - # Insert it in the new position. - parent.splice newIndex, 0, operand - - {p:path, lm:newIndex} - - else if randomReal() < 0.3 or operand == null - # Replace - - newValue = randomThing() - parent[key] = newValue - - if Array.isArray(parent) - {p:path, ld:operand, li:clone(newValue)} - else - {p:path, od:operand, oi:clone(newValue)} - - else if typeof operand == 'string' - # String. This code is adapted from the text op generator. - - if randomReal() > 0.5 or operand.length == 0 - # Insert - pos = randomInt(operand.length + 1) - str = randomWord() + ' ' - - path.push pos - parent[key] = operand[...pos] + str + operand[pos..] - c = {p:path, si:str} - else - # Delete - pos = randomInt(operand.length) - length = Math.min(randomInt(4), operand.length - pos) - str = operand[pos...(pos + length)] - - path.push pos - parent[key] = operand[...pos] + operand[pos + length..] - c = {p:path, sd:str} - - if json0._testStringSubtype - # Subtype - subOp = {p:path.pop()} - if c.si? - subOp.i = c.si - else - subOp.d = c.sd - - c = {p:path, t:'text0', o:[subOp]} - - c - - else if typeof operand == 'number' - # Number - inc = randomInt(10) - 3 - parent[key] += inc - {p:path, na:inc} - - else if Array.isArray(operand) - # Array. Replace is covered above, so we'll just randomly insert or delete. - # This code looks remarkably similar to string insert, above. - - if randomReal() > 0.5 or operand.length == 0 - # Insert - pos = randomInt(operand.length + 1) - obj = randomThing() - - path.push pos - operand.splice pos, 0, obj - {p:path, li:clone(obj)} - else - # Delete - pos = randomInt operand.length - obj = operand[pos] - - path.push pos - operand.splice pos, 1 - {p:path, ld:clone(obj)} - else - # Object - k = randomKey(operand) - - if randomReal() > 0.5 or not k? - # Insert - k = randomNewKey(operand) - obj = randomThing() - - path.push k - operand[k] = obj - {p:path, oi:clone(obj)} - else - obj = operand[k] - - path.push k - delete operand[k] - {p:path, od:clone(obj)} - - [op, container.data] diff --git a/test/json0-generator.js b/test/json0-generator.js new file mode 100644 index 0000000..cccb542 --- /dev/null +++ b/test/json0-generator.js @@ -0,0 +1,222 @@ +/* + * 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 + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let genRandomOp; +const json0 = require('../lib/json0'); +const {randomInt, randomReal, randomWord} = require('ot-fuzzer'); + +// 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 = o => JSON.parse(JSON.stringify(o)); + +const randomKey = function(obj) { + if (Array.isArray(obj)) { + if (obj.length === 0) { + return undefined; + } else { + return randomInt(obj.length); + } + } else { + let result; + let count = 0; + + for (let key in obj) { + if (randomReal() < (1/++count)) { result = key; } + } + return result; + } +}; + +// Generate a random new key for a value in obj. +// obj must be an Object. +const randomNewKey = function(obj) { + // There's no do-while loop in coffeescript. + let key = randomWord(); + while (obj[key] !== undefined) { key = randomWord(); } + return key; +}; + +// Generate a random object +var randomThing = function() { + switch (randomInt(6)) { + case 0: return null; + case 1: return ''; + case 2: return randomWord(); + case 3: + var obj = {}; + for (let i = 1, end = randomInt(5), asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { obj[randomNewKey(obj)] = randomThing(); } + return obj; + case 4: return (__range__(1, randomInt(5), true).map((j) => randomThing())); + case 5: return randomInt(50); + } +}; + +// Pick a random path to something in the object. +const randomPath = function(data) { + const path = []; + + while ((randomReal() > 0.85) && (typeof data === 'object')) { + const key = randomKey(data); + if (key == null) { break; } + + path.push(key); + data = data[key]; + } + + return path; +}; + + +module.exports = (genRandomOp = function(data) { + let pct = 0.95; + + const container = {data: clone(data)}; + + const op = (() => { + const result = []; + while (randomReal() < pct) { + var length, obj, p, pos; + pct *= 0.6; + + // Pick a random object in the document operate on. + const path = randomPath(container['data']); + + // parent = the container for the operand. parent[key] contains the operand. + let parent = container; + let key = 'data'; + for (p of Array.from(path)) { + parent = parent[key]; + key = p; + } + const operand = parent[key]; + + if ((randomReal() < 0.4) && (parent !== container) && Array.isArray(parent)) { + // List move + const newIndex = randomInt(parent.length); + + // Remove the element from its current position in the list + parent.splice(key, 1); + // Insert it in the new position. + parent.splice(newIndex, 0, operand); + + result.push({p:path, lm:newIndex}); + + } else if ((randomReal() < 0.3) || (operand === null)) { + // Replace + + const newValue = randomThing(); + parent[key] = newValue; + + if (Array.isArray(parent)) { + result.push({p:path, ld:operand, li:clone(newValue)}); + } else { + result.push({p:path, od:operand, oi:clone(newValue)}); + } + + } else if (typeof operand === 'string') { + // String. This code is adapted from the text op generator. + + var c, str; + if ((randomReal() > 0.5) || (operand.length === 0)) { + // Insert + pos = randomInt(operand.length + 1); + str = randomWord() + ' '; + + path.push(pos); + parent[key] = operand.slice(0, pos) + str + operand.slice(pos); + c = {p:path, si:str}; + } else { + // Delete + pos = randomInt(operand.length); + length = Math.min(randomInt(4), operand.length - pos); + str = operand.slice(pos, (pos + length)); + + path.push(pos); + parent[key] = operand.slice(0, pos) + operand.slice(pos + length); + c = {p:path, sd:str}; + } + + if (json0._testStringSubtype) { + // Subtype + const subOp = {p:path.pop()}; + if (c.si != null) { + subOp.i = c.si; + } else { + subOp.d = c.sd; + } + + c = {p:path, t:'text0', o:[subOp]}; + } + + result.push(c); + + } else if (typeof operand === 'number') { + // Number + const inc = randomInt(10) - 3; + parent[key] += inc; + result.push({p:path, na:inc}); + + } else if (Array.isArray(operand)) { + // Array. Replace is covered above, so we'll just randomly insert or delete. + // This code looks remarkably similar to string insert, above. + + if ((randomReal() > 0.5) || (operand.length === 0)) { + // Insert + pos = randomInt(operand.length + 1); + obj = randomThing(); + + path.push(pos); + operand.splice(pos, 0, obj); + result.push({p:path, li:clone(obj)}); + } else { + // Delete + pos = randomInt(operand.length); + obj = operand[pos]; + + path.push(pos); + operand.splice(pos, 1); + result.push({p:path, ld:clone(obj)}); + } + } else { + // Object + let k = randomKey(operand); + + if ((randomReal() > 0.5) || (k == null)) { + // Insert + k = randomNewKey(operand); + obj = randomThing(); + + path.push(k); + operand[k] = obj; + result.push({p:path, oi:clone(obj)}); + } else { + obj = operand[k]; + + path.push(k); + delete operand[k]; + result.push({p:path, od:clone(obj)}); + } + } + } + return result; + })(); + + return [op, container.data]; +}); + +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/json0.coffee b/test/json0.coffee deleted file mode 100644 index 531f76e..0000000 --- a/test/json0.coffee +++ /dev/null @@ -1,405 +0,0 @@ -# Tests for JSON OT type. - -assert = require 'assert' -nativetype = require '../lib/json0' - -fuzzer = require 'ot-fuzzer' - -nativetype.registerSubtype - name: 'mock' - transform: (a, b, side) -> - return { mock: true } - -# Cross-transform helper function. Transform server by client and client by -# server. Returns [server, client]. -transformX = (type, left, right) -> - [type.transform(left, right, 'left'), type.transform(right, left, 'right')] - -genTests = (type) -> - # The random op tester above will test that the OT functions are admissable, - # but debugging problems it detects is a pain. - # - # These tests should pick up *most* problems with a normal JSON OT - # implementation. - - describe 'sanity', -> - describe '#create()', -> it 'returns null', -> - assert.deepEqual type.create(), null - - describe '#compose()', -> - it 'od,oi --> od+oi', -> - assert.deepEqual [{p:['foo'], od:1, oi:2}], type.compose [{p:['foo'],od:1}],[{p:['foo'],oi:2}] - assert.deepEqual [{p:['foo'], od:1},{p:['bar'], oi:2}], type.compose [{p:['foo'],od:1}],[{p:['bar'],oi:2}] - it 'merges od+oi, od+oi -> od+oi', -> - assert.deepEqual [{p:['foo'], od:1, oi:2}], type.compose [{p:['foo'],od:1,oi:3}],[{p:['foo'],od:3,oi:2}] - - - describe '#transform()', -> it 'returns sane values', -> - t = (op1, op2) -> - assert.deepEqual op1, type.transform op1, op2, 'left' - assert.deepEqual op1, type.transform op1, op2, 'right' - - t [], [] - t [{p:['foo'], oi:1}], [] - t [{p:['foo'], oi:1}], [{p:['bar'], oi:2}] - - describe 'number', -> - it 'Adds a number', -> - assert.deepEqual 3, type.apply 1, [{p:[], na:2}] - assert.deepEqual [3], type.apply [1], [{p:[0], na:2}] - - it 'compresses two adds together in compose', -> - assert.deepEqual [{p:['a', 'b'], na:3}], type.compose [{p:['a', 'b'], na:1}], [{p:['a', 'b'], na:2}] - assert.deepEqual [{p:['a'], na:1}, {p:['b'], na:2}], type.compose [{p:['a'], na:1}], [{p:['b'], na:2}] - - it 'doesn\'t overwrite values when it merges na in append', -> - rightHas = 21 - leftHas = 3 - - rightOp = [{"p":[],"od":0,"oi":15},{"p":[],"na":4},{"p":[],"na":1},{"p":[],"na":1}] - leftOp = [{"p":[],"na":4},{"p":[],"na":-1}] - [right_, left_] = transformX type, rightOp, leftOp - - s_c = type.apply rightHas, left_ - c_s = type.apply leftHas, right_ - assert.deepEqual s_c, c_s - - - # Strings should be handled internally by the text type. We'll just do some basic sanity checks here. - describe 'string', -> - describe '#apply()', -> it 'works', -> - assert.deepEqual 'abc', type.apply 'a', [{p:[1], si:'bc'}] - assert.deepEqual 'bc', type.apply 'abc', [{p:[0], sd:'a'}] - assert.deepEqual {x:'abc'}, type.apply {x:'a'}, [{p:['x', 1], si:'bc'}] - - describe '#transform()', -> - it 'splits deletes', -> - assert.deepEqual type.transform([{p:[0], sd:'ab'}], [{p:[1], si:'x'}], 'left'), [{p:[0], sd:'a'}, {p:[1], sd:'b'}] - - it 'cancels out other deletes', -> - assert.deepEqual type.transform([{p:['k', 5], sd:'a'}], [{p:['k', 5], sd:'a'}], 'left'), [] - - it 'does not throw errors with blank inserts', -> - assert.deepEqual type.transform([{p: ['k', 5], si:''}], [{p: ['k', 3], si: 'a'}], 'left'), [] - - describe 'string subtype', -> - describe '#apply()', -> - it 'works', -> - assert.deepEqual 'abc', type.apply 'a', [{p:[], t:'text0', o:[{p:1, i:'bc'}]}] - assert.deepEqual 'bc', type.apply 'abc', [{p:[], t:'text0', o:[{p:0, d:'a'}]}] - assert.deepEqual {x:'abc'}, type.apply {x:'a'}, [{p:['x'], t:'text0', o:[{p:1, i:'bc'}]}] - - describe '#transform()', -> - it 'splits deletes', -> - a = [{p:[], t:'text0', o:[{p:0, d:'ab'}]}] - b = [{p:[], t:'text0', o:[{p:1, i:'x'}]}] - assert.deepEqual type.transform(a, b, 'left'), [{p:[], t:'text0', o:[{p:0, d:'a'}, {p:1, d:'b'}]}] - - it 'cancels out other deletes', -> - assert.deepEqual type.transform([{p:['k'], t:'text0', o:[{p:5, d:'a'}]}], [{p:['k'], t:'text0', o:[{p:5, d:'a'}]}], 'left'), [] - - it 'does not throw errors with blank inserts', -> - assert.deepEqual type.transform([{p:['k'], t:'text0', o:[{p:5, i:''}]}], [{p:['k'], t:'text0', o:[{p:3, i:'a'}]}], 'left'), [] - - describe 'subtype with non-array operation', -> - describe '#transform()', -> - it 'works', -> - a = [{p:[], t:'mock', o:'foo'}] - b = [{p:[], t:'mock', o:'bar'}] - assert.deepEqual type.transform(a, b, 'left'), [{p:[], t:'mock', o:{mock:true}}] - - describe 'list', -> - describe 'apply', -> - it 'inserts', -> - assert.deepEqual ['a', 'b', 'c'], type.apply ['b', 'c'], [{p:[0], li:'a'}] - assert.deepEqual ['a', 'b', 'c'], type.apply ['a', 'c'], [{p:[1], li:'b'}] - assert.deepEqual ['a', 'b', 'c'], type.apply ['a', 'b'], [{p:[2], li:'c'}] - - it 'deletes', -> - assert.deepEqual ['b', 'c'], type.apply ['a', 'b', 'c'], [{p:[0], ld:'a'}] - assert.deepEqual ['a', 'c'], type.apply ['a', 'b', 'c'], [{p:[1], ld:'b'}] - assert.deepEqual ['a', 'b'], type.apply ['a', 'b', 'c'], [{p:[2], ld:'c'}] - - it 'replaces', -> - assert.deepEqual ['a', 'y', 'b'], type.apply ['a', 'x', 'b'], [{p:[1], ld:'x', li:'y'}] - - it 'moves', -> - assert.deepEqual ['a', 'b', 'c'], type.apply ['b', 'a', 'c'], [{p:[1], lm:0}] - assert.deepEqual ['a', 'b', 'c'], type.apply ['b', 'a', 'c'], [{p:[0], lm:1}] - - ### - 'null moves compose to nops', -> - assert.deepEqual [], type.compose [], [{p:[3],lm:3}] - assert.deepEqual [], type.compose [], [{p:[0,3],lm:3}] - assert.deepEqual [], type.compose [], [{p:['x','y',0],lm:0}] - ### - - describe '#transform()', -> - it 'bumps paths when list elements are inserted or removed', -> - assert.deepEqual [{p:[2, 200], si:'hi'}], type.transform [{p:[1, 200], si:'hi'}], [{p:[0], li:'x'}], 'left' - assert.deepEqual [{p:[1, 201], si:'hi'}], type.transform [{p:[0, 201], si:'hi'}], [{p:[0], li:'x'}], 'right' - assert.deepEqual [{p:[0, 202], si:'hi'}], type.transform [{p:[0, 202], si:'hi'}], [{p:[1], li:'x'}], 'left' - assert.deepEqual [{p:[2], t:'text0', o:[{p:200, i:'hi'}]}], type.transform [{p:[1], t:'text0', o:[{p:200, i:'hi'}]}], [{p:[0], li:'x'}], 'left' - assert.deepEqual [{p:[1], t:'text0', o:[{p:201, i:'hi'}]}], type.transform [{p:[0], t:'text0', o:[{p:201, i:'hi'}]}], [{p:[0], li:'x'}], 'right' - assert.deepEqual [{p:[0], t:'text0', o:[{p:202, i:'hi'}]}], type.transform [{p:[0], t:'text0', o:[{p:202, i:'hi'}]}], [{p:[1], li:'x'}], 'left' - - assert.deepEqual [{p:[0, 203], si:'hi'}], type.transform [{p:[1, 203], si:'hi'}], [{p:[0], ld:'x'}], 'left' - assert.deepEqual [{p:[0, 204], si:'hi'}], type.transform [{p:[0, 204], si:'hi'}], [{p:[1], ld:'x'}], 'left' - assert.deepEqual [{p:['x',3], si: 'hi'}], type.transform [{p:['x',3], si:'hi'}], [{p:['x',0,'x'], li:0}], 'left' - assert.deepEqual [{p:['x',3,'x'], si: 'hi'}], type.transform [{p:['x',3,'x'], si:'hi'}], [{p:['x',5], li:0}], 'left' - assert.deepEqual [{p:['x',4,'x'], si: 'hi'}], type.transform [{p:['x',3,'x'], si:'hi'}], [{p:['x',0], li:0}], 'left' - assert.deepEqual [{p:[0], t:'text0', o:[{p:203, i:'hi'}]}], type.transform [{p:[1], t:'text0', o:[{p:203, i:'hi'}]}], [{p:[0], ld:'x'}], 'left' - assert.deepEqual [{p:[0], t:'text0', o:[{p:204, i:'hi'}]}], type.transform [{p:[0], t:'text0', o:[{p:204, i:'hi'}]}], [{p:[1], ld:'x'}], 'left' - assert.deepEqual [{p:['x'], t:'text0', o:[{p:3,i: 'hi'}]}], type.transform [{p:['x'], t:'text0', o:[{p:3, i:'hi'}]}], [{p:['x',0,'x'], li:0}], 'left' - - assert.deepEqual [{p:[1],ld:2}], type.transform [{p:[0],ld:2}], [{p:[0],li:1}], 'left' - assert.deepEqual [{p:[1],ld:2}], type.transform [{p:[0],ld:2}], [{p:[0],li:1}], 'right' - - it 'converts ops on deleted elements to noops', -> - assert.deepEqual [], type.transform [{p:[1, 0], si:'hi'}], [{p:[1], ld:'x'}], 'left' - assert.deepEqual [], type.transform [{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], ld:'x'}], 'left' - assert.deepEqual [{p:[0],li:'x'}], type.transform [{p:[0],li:'x'}], [{p:[0],ld:'y'}], 'left' - assert.deepEqual [], type.transform [{p:[0],na:-3}], [{p:[0],ld:48}], 'left' - - it 'converts ops on replaced elements to noops', -> - assert.deepEqual [], type.transform [{p:[1, 0], si:'hi'}], [{p:[1], ld:'x', li:'y'}], 'left' - assert.deepEqual [], type.transform [{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], ld:'x', li:'y'}], 'left' - assert.deepEqual [{p:[0], li:'hi'}], type.transform [{p:[0], li:'hi'}], [{p:[0], ld:'x', li:'y'}], 'left' - - it 'changes deleted data to reflect edits', -> - assert.deepEqual [{p:[1], ld:'abc'}], type.transform [{p:[1], ld:'a'}], [{p:[1, 1], si:'bc'}], 'left' - assert.deepEqual [{p:[1], ld:'abc'}], type.transform [{p:[1], ld:'a'}], [{p:[1], t:'text0', o:[{p:1, i:'bc'}]}], 'left' - - it 'Puts the left op first if two inserts are simultaneous', -> - assert.deepEqual [{p:[1], li:'a'}], type.transform [{p:[1], li:'a'}], [{p:[1], li:'b'}], 'left' - assert.deepEqual [{p:[2], li:'b'}], type.transform [{p:[1], li:'b'}], [{p:[1], li:'a'}], 'right' - - it 'converts an attempt to re-delete a list element into a no-op', -> - assert.deepEqual [], type.transform [{p:[1], ld:'x'}], [{p:[1], ld:'x'}], 'left' - assert.deepEqual [], type.transform [{p:[1], ld:'x'}], [{p:[1], ld:'x'}], 'right' - - - describe '#compose()', -> - it 'composes insert then delete into a no-op', -> - assert.deepEqual [], type.compose [{p:[1], li:'abc'}], [{p:[1], ld:'abc'}] - assert.deepEqual [{p:[1],ld:null,li:'x'}], type.transform [{p:[0],ld:null,li:"x"}], [{p:[0],li:"The"}], 'right' - - it 'doesn\'t change the original object', -> - a = [{p:[0],ld:'abc',li:null}] - assert.deepEqual [{p:[0],ld:'abc'}], type.compose a, [{p:[0],ld:null}] - assert.deepEqual [{p:[0],ld:'abc',li:null}], a - - it 'composes together adjacent string ops', -> - assert.deepEqual [{p:[100], si:'hi'}], type.compose [{p:[100], si:'h'}], [{p:[101], si:'i'}] - assert.deepEqual [{p:[], t:'text0', o:[{p:100, i:'hi'}]}], type.compose [{p:[], t:'text0', o:[{p:100, i:'h'}]}], [{p:[], t:'text0', o:[{p:101, i:'i'}]}] - - it 'moves ops on a moved element with the element', -> - assert.deepEqual [{p:[10], ld:'x'}], type.transform [{p:[4], ld:'x'}], [{p:[4], lm:10}], 'left' - assert.deepEqual [{p:[10, 1], si:'a'}], type.transform [{p:[4, 1], si:'a'}], [{p:[4], lm:10}], 'left' - assert.deepEqual [{p:[10], t:'text0', o:[{p:1, i:'a'}]}], type.transform [{p:[4], t:'text0', o:[{p:1, i:'a'}]}], [{p:[4], lm:10}], 'left' - assert.deepEqual [{p:[10, 1], li:'a'}], type.transform [{p:[4, 1], li:'a'}], [{p:[4], lm:10}], 'left' - assert.deepEqual [{p:[10, 1], ld:'b', li:'a'}], type.transform [{p:[4, 1], ld:'b', li:'a'}], [{p:[4], lm:10}], 'left' - - assert.deepEqual [{p:[0],li:null}], type.transform [{p:[0],li:null}], [{p:[0],lm:1}], 'left' - # [_,_,_,_,5,6,7,_] - # c: [_,_,_,_,5,'x',6,7,_] p:5 li:'x' - # s: [_,6,_,_,_,5,7,_] p:5 lm:1 - # correct: [_,6,_,_,_,5,'x',7,_] - assert.deepEqual [{p:[6],li:'x'}], type.transform [{p:[5],li:'x'}], [{p:[5],lm:1}], 'left' - # [_,_,_,_,5,6,7,_] - # c: [_,_,_,_,5,6,7,_] p:5 ld:6 - # s: [_,6,_,_,_,5,7,_] p:5 lm:1 - # correct: [_,_,_,_,5,7,_] - assert.deepEqual [{p:[1],ld:6}], type.transform [{p:[5],ld:6}], [{p:[5],lm:1}], 'left' - #assert.deepEqual [{p:[0],li:{}}], type.transform [{p:[0],li:{}}], [{p:[0],lm:0}], 'right' - assert.deepEqual [{p:[0],li:[]}], type.transform [{p:[0],li:[]}], [{p:[1],lm:0}], 'left' - assert.deepEqual [{p:[2],li:'x'}], type.transform [{p:[2],li:'x'}], [{p:[0],lm:1}], 'left' - - it 'moves target index on ld/li', -> - assert.deepEqual [{p:[0],lm:1}], type.transform [{p:[0], lm: 2}], [{p:[1], ld:'x'}], 'left' - assert.deepEqual [{p:[1],lm:3}], type.transform [{p:[2], lm: 4}], [{p:[1], ld:'x'}], 'left' - assert.deepEqual [{p:[0],lm:3}], type.transform [{p:[0], lm: 2}], [{p:[1], li:'x'}], 'left' - assert.deepEqual [{p:[3],lm:5}], type.transform [{p:[2], lm: 4}], [{p:[1], li:'x'}], 'left' - assert.deepEqual [{p:[1],lm:1}], type.transform [{p:[0], lm: 0}], [{p:[0], li:28}], 'left' - - it 'tiebreaks lm vs. ld/li', -> - assert.deepEqual [], type.transform [{p:[0], lm: 2}], [{p:[0], ld:'x'}], 'left' - assert.deepEqual [], type.transform [{p:[0], lm: 2}], [{p:[0], ld:'x'}], 'right' - assert.deepEqual [{p:[1], lm:3}], type.transform [{p:[0], lm: 2}], [{p:[0], li:'x'}], 'left' - assert.deepEqual [{p:[1], lm:3}], type.transform [{p:[0], lm: 2}], [{p:[0], li:'x'}], 'right' - - it 'replacement vs. deletion', -> - assert.deepEqual [{p:[0],li:'y'}], type.transform [{p:[0],ld:'x',li:'y'}], [{p:[0],ld:'x'}], 'right' - - it 'replacement vs. insertion', -> - assert.deepEqual [{p:[1],ld:{},li:"brillig"}], type.transform [{p:[0],ld:{},li:"brillig"}], [{p:[0],li:36}], 'left' - - it 'replacement vs. replacement', -> - assert.deepEqual [], type.transform [{p:[0],ld:null,li:[]}], [{p:[0],ld:null,li:0}], 'right' - assert.deepEqual [{p:[0],ld:[],li:0}], type.transform [{p:[0],ld:null,li:0}], [{p:[0],ld:null,li:[]}], 'left' - - it 'composes replace with delete of replaced element results in insert', -> - assert.deepEqual [{p:[2],ld:[]}], type.compose [{p:[2],ld:[],li:null}], [{p:[2],ld:null}] - - it 'lm vs lm', -> - assert.deepEqual [{p:[0],lm:2}], type.transform [{p:[0],lm:2}], [{p:[2],lm:1}], 'left' - assert.deepEqual [{p:[4],lm:4}], type.transform [{p:[3],lm:3}], [{p:[5],lm:0}], 'left' - assert.deepEqual [{p:[2],lm:0}], type.transform [{p:[2],lm:0}], [{p:[1],lm:0}], 'left' - assert.deepEqual [{p:[2],lm:1}], type.transform [{p:[2],lm:0}], [{p:[1],lm:0}], 'right' - assert.deepEqual [{p:[3],lm:1}], type.transform [{p:[2],lm:0}], [{p:[5],lm:0}], 'right' - assert.deepEqual [{p:[3],lm:0}], type.transform [{p:[2],lm:0}], [{p:[5],lm:0}], 'left' - assert.deepEqual [{p:[0],lm:5}], type.transform [{p:[2],lm:5}], [{p:[2],lm:0}], 'left' - assert.deepEqual [{p:[0],lm:5}], type.transform [{p:[2],lm:5}], [{p:[2],lm:0}], 'left' - assert.deepEqual [{p:[0],lm:0}], type.transform [{p:[1],lm:0}], [{p:[0],lm:5}], 'right' - assert.deepEqual [{p:[0],lm:0}], type.transform [{p:[1],lm:0}], [{p:[0],lm:1}], 'right' - assert.deepEqual [{p:[1],lm:1}], type.transform [{p:[0],lm:1}], [{p:[1],lm:0}], 'left' - assert.deepEqual [{p:[1],lm:2}], type.transform [{p:[0],lm:1}], [{p:[5],lm:0}], 'right' - assert.deepEqual [{p:[3],lm:2}], type.transform [{p:[2],lm:1}], [{p:[5],lm:0}], 'right' - assert.deepEqual [{p:[2],lm:1}], type.transform [{p:[3],lm:1}], [{p:[1],lm:3}], 'left' - assert.deepEqual [{p:[2],lm:3}], type.transform [{p:[1],lm:3}], [{p:[3],lm:1}], 'left' - assert.deepEqual [{p:[2],lm:6}], type.transform [{p:[2],lm:6}], [{p:[0],lm:1}], 'left' - assert.deepEqual [{p:[2],lm:6}], type.transform [{p:[2],lm:6}], [{p:[0],lm:1}], 'right' - assert.deepEqual [{p:[2],lm:6}], type.transform [{p:[2],lm:6}], [{p:[1],lm:0}], 'left' - assert.deepEqual [{p:[2],lm:6}], type.transform [{p:[2],lm:6}], [{p:[1],lm:0}], 'right' - assert.deepEqual [{p:[0],lm:2}], type.transform [{p:[0],lm:1}], [{p:[2],lm:1}], 'left' - assert.deepEqual [{p:[2],lm:0}], type.transform [{p:[2],lm:1}], [{p:[0],lm:1}], 'right' - assert.deepEqual [{p:[1],lm:1}], type.transform [{p:[0],lm:0}], [{p:[1],lm:0}], 'left' - assert.deepEqual [{p:[0],lm:0}], type.transform [{p:[0],lm:1}], [{p:[1],lm:3}], 'left' - assert.deepEqual [{p:[3],lm:1}], type.transform [{p:[2],lm:1}], [{p:[3],lm:2}], 'left' - assert.deepEqual [{p:[3],lm:3}], type.transform [{p:[3],lm:2}], [{p:[2],lm:1}], 'left' - - it 'changes indices correctly around a move', -> - assert.deepEqual [{p:[1,0],li:{}}], type.transform [{p:[0,0],li:{}}], [{p:[1],lm:0}], 'left' - assert.deepEqual [{p:[0],lm:0}], type.transform [{p:[1],lm:0}], [{p:[0],ld:{}}], 'left' - assert.deepEqual [{p:[0],lm:0}], type.transform [{p:[0],lm:1}], [{p:[1],ld:{}}], 'left' - assert.deepEqual [{p:[5],lm:0}], type.transform [{p:[6],lm:0}], [{p:[2],ld:{}}], 'left' - assert.deepEqual [{p:[1],lm:0}], type.transform [{p:[1],lm:0}], [{p:[2],ld:{}}], 'left' - assert.deepEqual [{p:[1],lm:1}], type.transform [{p:[2],lm:1}], [{p:[1],ld:3}], 'right' - - assert.deepEqual [{p:[1],ld:{}}], type.transform [{p:[2],ld:{}}], [{p:[1],lm:2}], 'right' - assert.deepEqual [{p:[2],ld:{}}], type.transform [{p:[1],ld:{}}], [{p:[2],lm:1}], 'left' - - - assert.deepEqual [{p:[0],ld:{}}], type.transform [{p:[1],ld:{}}], [{p:[0],lm:1}], 'right' - - assert.deepEqual [{p:[0],ld:1,li:2}], type.transform [{p:[1],ld:1,li:2}], [{p:[1],lm:0}], 'left' - assert.deepEqual [{p:[0],ld:2,li:3}], type.transform [{p:[1],ld:2,li:3}], [{p:[0],lm:1}], 'left' - assert.deepEqual [{p:[1],ld:3,li:4}], type.transform [{p:[0],ld:3,li:4}], [{p:[1],lm:0}], 'left' - - it 'li vs lm', -> - li = (p) -> [{p:[p],li:[]}] - lm = (f,t) -> [{p:[f],lm:t}] - xf = type.transform - - assert.deepEqual (li 0), xf (li 0), (lm 1, 3), 'left' - assert.deepEqual (li 1), xf (li 1), (lm 1, 3), 'left' - assert.deepEqual (li 1), xf (li 2), (lm 1, 3), 'left' - assert.deepEqual (li 2), xf (li 3), (lm 1, 3), 'left' - assert.deepEqual (li 4), xf (li 4), (lm 1, 3), 'left' - - assert.deepEqual (lm 2, 4), xf (lm 1, 3), (li 0), 'right' - assert.deepEqual (lm 2, 4), xf (lm 1, 3), (li 1), 'right' - assert.deepEqual (lm 1, 4), xf (lm 1, 3), (li 2), 'right' - assert.deepEqual (lm 1, 4), xf (lm 1, 3), (li 3), 'right' - assert.deepEqual (lm 1, 3), xf (lm 1, 3), (li 4), 'right' - - assert.deepEqual (li 0), xf (li 0), (lm 1, 2), 'left' - assert.deepEqual (li 1), xf (li 1), (lm 1, 2), 'left' - assert.deepEqual (li 1), xf (li 2), (lm 1, 2), 'left' - assert.deepEqual (li 3), xf (li 3), (lm 1, 2), 'left' - - assert.deepEqual (li 0), xf (li 0), (lm 3, 1), 'left' - assert.deepEqual (li 1), xf (li 1), (lm 3, 1), 'left' - assert.deepEqual (li 3), xf (li 2), (lm 3, 1), 'left' - assert.deepEqual (li 4), xf (li 3), (lm 3, 1), 'left' - assert.deepEqual (li 4), xf (li 4), (lm 3, 1), 'left' - - assert.deepEqual (lm 4, 2), xf (lm 3, 1), (li 0), 'right' - assert.deepEqual (lm 4, 2), xf (lm 3, 1), (li 1), 'right' - assert.deepEqual (lm 4, 1), xf (lm 3, 1), (li 2), 'right' - assert.deepEqual (lm 4, 1), xf (lm 3, 1), (li 3), 'right' - assert.deepEqual (lm 3, 1), xf (lm 3, 1), (li 4), 'right' - - assert.deepEqual (li 0), xf (li 0), (lm 2, 1), 'left' - assert.deepEqual (li 1), xf (li 1), (lm 2, 1), 'left' - assert.deepEqual (li 3), xf (li 2), (lm 2, 1), 'left' - assert.deepEqual (li 3), xf (li 3), (lm 2, 1), 'left' - - - describe 'object', -> - it 'passes sanity checks', -> - assert.deepEqual {x:'a', y:'b'}, type.apply {x:'a'}, [{p:['y'], oi:'b'}] - assert.deepEqual {}, type.apply {x:'a'}, [{p:['x'], od:'a'}] - assert.deepEqual {x:'b'}, type.apply {x:'a'}, [{p:['x'], od:'a', oi:'b'}] - - it 'Ops on deleted elements become noops', -> - assert.deepEqual [], type.transform [{p:[1, 0], si:'hi'}], [{p:[1], od:'x'}], 'left' - assert.deepEqual [], type.transform [{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], od:'x'}], 'left' - assert.deepEqual [], type.transform [{p:[9],si:"bite "}], [{p:[],od:"agimble s",oi:null}], 'right' - assert.deepEqual [], type.transform [{p:[], t:'text0', o:[{p:9, i:"bite "}]}], [{p:[],od:"agimble s",oi:null}], 'right' - - it 'Ops on replaced elements become noops', -> - assert.deepEqual [], type.transform [{p:[1, 0], si:'hi'}], [{p:[1], od:'x', oi:'y'}], 'left' - assert.deepEqual [], type.transform [{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], od:'x', oi:'y'}], 'left' - - it 'Deleted data is changed to reflect edits', -> - assert.deepEqual [{p:[1], od:'abc'}], type.transform [{p:[1], od:'a'}], [{p:[1, 1], si:'bc'}], 'left' - assert.deepEqual [{p:[1], od:'abc'}], type.transform [{p:[1], od:'a'}], [{p:[1], t:'text0', o:[{p:1, i:'bc'}]}], 'left' - assert.deepEqual [{p:[],od:25,oi:[]}], type.transform [{p:[],od:22,oi:[]}], [{p:[],na:3}], 'left' - assert.deepEqual [{p:[],od:{toves:""},oi:4}], type.transform [{p:[],od:{toves:0},oi:4}], [{p:["toves"],od:0,oi:""}], 'left' - assert.deepEqual [{p:[],od:"thou an",oi:[]}], type.transform [{p:[],od:"thou and ",oi:[]}], [{p:[7],sd:"d "}], 'left' - assert.deepEqual [{p:[],od:"thou an",oi:[]}], type.transform [{p:[],od:"thou and ",oi:[]}], [{p:[], t:'text0', o:[{p:7, d:"d "}]}], 'left' - assert.deepEqual [], type.transform([{p:["bird"],na:2}], [{p:[],od:{bird:38},oi:20}], 'right') - assert.deepEqual [{p:[],od:{bird:40},oi:20}], type.transform([{p:[],od:{bird:38},oi:20}], [{p:["bird"],na:2}], 'left') - assert.deepEqual [{p:['He'],od:[]}], type.transform [{p:["He"],od:[]}], [{p:["The"],na:-3}], 'right' - assert.deepEqual [], type.transform [{p:["He"],oi:{}}], [{p:[],od:{},oi:"the"}], 'left' - - it 'If two inserts are simultaneous, the lefts insert will win', -> - assert.deepEqual [{p:[1], oi:'a', od:'b'}], type.transform [{p:[1], oi:'a'}], [{p:[1], oi:'b'}], 'left' - assert.deepEqual [], type.transform [{p:[1], oi:'b'}], [{p:[1], oi:'a'}], 'right' - - it 'parallel ops on different keys miss each other', -> - assert.deepEqual [{p:['a'], oi: 'x'}], type.transform [{p:['a'], oi:'x'}], [{p:['b'], oi:'z'}], 'left' - assert.deepEqual [{p:['a'], oi: 'x'}], type.transform [{p:['a'], oi:'x'}], [{p:['b'], od:'z'}], 'left' - assert.deepEqual [{p:["in","he"],oi:{}}], type.transform [{p:["in","he"],oi:{}}], [{p:["and"],od:{}}], 'right' - assert.deepEqual [{p:['x',0],si:"his "}], type.transform [{p:['x',0],si:"his "}], [{p:['y'],od:0,oi:1}], 'right' - assert.deepEqual [{p:['x'], t:'text0', o:[{p:0, i:"his "}]}], type.transform [{p:['x'],t:'text0', o:[{p:0, i:"his "}]}], [{p:['y'],od:0,oi:1}], 'right' - - it 'replacement vs. deletion', -> - assert.deepEqual [{p:[],oi:{}}], type.transform [{p:[],od:[''],oi:{}}], [{p:[],od:['']}], 'right' - - it 'replacement vs. replacement', -> - assert.deepEqual [], type.transform [{p:[],od:['']},{p:[],oi:{}}], [{p:[],od:['']},{p:[],oi:null}], 'right' - assert.deepEqual [{p:[],od:null,oi:{}}], type.transform [{p:[],od:['']},{p:[],oi:{}}], [{p:[],od:['']},{p:[],oi:null}], 'left' - assert.deepEqual [], type.transform [{p:[],od:[''],oi:{}}], [{p:[],od:[''],oi:null}], 'right' - assert.deepEqual [{p:[],od:null,oi:{}}], type.transform [{p:[],od:[''],oi:{}}], [{p:[],od:[''],oi:null}], 'left' - - # test diamond property - rightOps = [ {"p":[],"od":null,"oi":{}} ] - leftOps = [ {"p":[],"od":null,"oi":""} ] - rightHas = type.apply(null, rightOps) - leftHas = type.apply(null, leftOps) - - [left_, right_] = transformX type, leftOps, rightOps - assert.deepEqual leftHas, type.apply rightHas, left_ - assert.deepEqual leftHas, type.apply leftHas, right_ - - - it 'An attempt to re-delete a key becomes a no-op', -> - assert.deepEqual [], type.transform [{p:['k'], od:'x'}], [{p:['k'], od:'x'}], 'left' - assert.deepEqual [], type.transform [{p:['k'], od:'x'}], [{p:['k'], od:'x'}], 'right' - - describe 'randomizer', -> - @timeout 20000 - @slow 6000 - it 'passes', -> - fuzzer type, require('./json0-generator'), 1000 - - it 'passes with string subtype', -> - type._testStringSubtype = true # hack - fuzzer type, require('./json0-generator'), 1000 - delete type._testStringSubtype - -describe 'json', -> - describe 'native type', -> genTests nativetype - #exports.webclient = genTests require('../helpers/webclient').types.json diff --git a/test/json0.js b/test/json0.js new file mode 100644 index 0000000..4f26379 --- /dev/null +++ b/test/json0.js @@ -0,0 +1,453 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +// Tests for JSON OT type. + +const assert = require('assert'); +const nativetype = require('../lib/json0'); + +const fuzzer = require('ot-fuzzer'); + +nativetype.registerSubtype({ + name: 'mock', + transform(a, b, side) { + return { mock: true }; + }}); + +// Cross-transform helper function. Transform server by client and client by +// server. Returns [server, client]. +const transformX = (type, left, right) => [type.transform(left, right, 'left'), type.transform(right, left, 'right')]; + +const genTests = function(type) { + // The random op tester above will test that the OT functions are admissable, + // but debugging problems it detects is a pain. + // + // These tests should pick up *most* problems with a normal JSON OT + // implementation. + + describe('sanity', function() { + describe('#create()', () => it('returns null', () => assert.deepEqual(type.create(), null)) + ); + + describe('#compose()', function() { + it('od,oi --> od+oi', function() { + assert.deepEqual([{p:['foo'], od:1, oi:2}], type.compose([{p:['foo'],od:1}],[{p:['foo'],oi:2}])); + return assert.deepEqual([{p:['foo'], od:1},{p:['bar'], oi:2}], type.compose([{p:['foo'],od:1}],[{p:['bar'],oi:2}])); + }); + return it('merges od+oi, od+oi -> od+oi', () => assert.deepEqual([{p:['foo'], od:1, oi:2}], type.compose([{p:['foo'],od:1,oi:3}],[{p:['foo'],od:3,oi:2}]))); + }); + + + return describe('#transform()', () => it('returns sane values', function() { + const t = function(op1, op2) { + assert.deepEqual(op1, type.transform(op1, op2, 'left')); + return assert.deepEqual(op1, type.transform(op1, op2, 'right')); + }; + + t([], []); + t([{p:['foo'], oi:1}], []); + return t([{p:['foo'], oi:1}], [{p:['bar'], oi:2}]); + }) ); +}); + + describe('number', function() { + it('Adds a number', function() { + assert.deepEqual(3, type.apply(1, [{p:[], na:2}])); + return assert.deepEqual([3], type.apply([1], [{p:[0], na:2}])); + }); + + it('compresses two adds together in compose', function() { + assert.deepEqual([{p:['a', 'b'], na:3}], type.compose([{p:['a', 'b'], na:1}], [{p:['a', 'b'], na:2}])); + return assert.deepEqual([{p:['a'], na:1}, {p:['b'], na:2}], type.compose([{p:['a'], na:1}], [{p:['b'], na:2}])); + }); + + return it('doesn\'t overwrite values when it merges na in append', function() { + const rightHas = 21; + const leftHas = 3; + + const rightOp = [{"p":[],"od":0,"oi":15},{"p":[],"na":4},{"p":[],"na":1},{"p":[],"na":1}]; + const leftOp = [{"p":[],"na":4},{"p":[],"na":-1}]; + const [right_, left_] = Array.from(transformX(type, rightOp, leftOp)); + + const s_c = type.apply(rightHas, left_); + const c_s = type.apply(leftHas, right_); + return assert.deepEqual(s_c, c_s); + }); + }); + + + // Strings should be handled internally by the text type. We'll just do some basic sanity checks here. + describe('string', function() { + describe('#apply()', () => it('works', function() { + assert.deepEqual('abc', type.apply('a', [{p:[1], si:'bc'}])); + assert.deepEqual('bc', type.apply('abc', [{p:[0], sd:'a'}])); + return assert.deepEqual({x:'abc'}, type.apply({x:'a'}, [{p:['x', 1], si:'bc'}])); + }) ); + + return describe('#transform()', function() { + it('splits deletes', () => assert.deepEqual(type.transform([{p:[0], sd:'ab'}], [{p:[1], si:'x'}], 'left'), [{p:[0], sd:'a'}, {p:[1], sd:'b'}])); + + it('cancels out other deletes', () => assert.deepEqual(type.transform([{p:['k', 5], sd:'a'}], [{p:['k', 5], sd:'a'}], 'left'), [])); + + return it('does not throw errors with blank inserts', () => assert.deepEqual(type.transform([{p: ['k', 5], si:''}], [{p: ['k', 3], si: 'a'}], 'left'), [])); + }); +}); + + describe('string subtype', function() { + describe('#apply()', () => + it('works', function() { + assert.deepEqual('abc', type.apply('a', [{p:[], t:'text0', o:[{p:1, i:'bc'}]}])); + assert.deepEqual('bc', type.apply('abc', [{p:[], t:'text0', o:[{p:0, d:'a'}]}])); + return assert.deepEqual({x:'abc'}, type.apply({x:'a'}, [{p:['x'], t:'text0', o:[{p:1, i:'bc'}]}])); + }) + ); + + return describe('#transform()', function() { + it('splits deletes', function() { + const a = [{p:[], t:'text0', o:[{p:0, d:'ab'}]}]; + const b = [{p:[], t:'text0', o:[{p:1, i:'x'}]}]; + return assert.deepEqual(type.transform(a, b, 'left'), [{p:[], t:'text0', o:[{p:0, d:'a'}, {p:1, d:'b'}]}]); + }); + + it('cancels out other deletes', () => assert.deepEqual(type.transform([{p:['k'], t:'text0', o:[{p:5, d:'a'}]}], [{p:['k'], t:'text0', o:[{p:5, d:'a'}]}], 'left'), [])); + + return it('does not throw errors with blank inserts', () => assert.deepEqual(type.transform([{p:['k'], t:'text0', o:[{p:5, i:''}]}], [{p:['k'], t:'text0', o:[{p:3, i:'a'}]}], 'left'), [])); + }); +}); + + describe('subtype with non-array operation', () => + describe('#transform()', () => + it('works', function() { + const a = [{p:[], t:'mock', o:'foo'}]; + const b = [{p:[], t:'mock', o:'bar'}]; + return assert.deepEqual(type.transform(a, b, 'left'), [{p:[], t:'mock', o:{mock:true}}]); + }) + ) +); + + describe('list', function() { + describe('apply', function() { + it('inserts', function() { + assert.deepEqual(['a', 'b', 'c'], type.apply(['b', 'c'], [{p:[0], li:'a'}])); + assert.deepEqual(['a', 'b', 'c'], type.apply(['a', 'c'], [{p:[1], li:'b'}])); + return assert.deepEqual(['a', 'b', 'c'], type.apply(['a', 'b'], [{p:[2], li:'c'}])); + }); + + it('deletes', function() { + assert.deepEqual(['b', 'c'], type.apply(['a', 'b', 'c'], [{p:[0], ld:'a'}])); + assert.deepEqual(['a', 'c'], type.apply(['a', 'b', 'c'], [{p:[1], ld:'b'}])); + return assert.deepEqual(['a', 'b'], type.apply(['a', 'b', 'c'], [{p:[2], ld:'c'}])); + }); + + it('replaces', () => assert.deepEqual(['a', 'y', 'b'], type.apply(['a', 'x', 'b'], [{p:[1], ld:'x', li:'y'}]))); + + return it('moves', function() { + assert.deepEqual(['a', 'b', 'c'], type.apply(['b', 'a', 'c'], [{p:[1], lm:0}])); + return assert.deepEqual(['a', 'b', 'c'], type.apply(['b', 'a', 'c'], [{p:[0], lm:1}])); + }); + + /* + 'null moves compose to nops', -> + assert.deepEqual [], type.compose [], [{p:[3],lm:3}] + assert.deepEqual [], type.compose [], [{p:[0,3],lm:3}] + assert.deepEqual [], type.compose [], [{p:['x','y',0],lm:0}] + */ + }); + + describe('#transform()', function() { + it('bumps paths when list elements are inserted or removed', function() { + assert.deepEqual([{p:[2, 200], si:'hi'}], type.transform([{p:[1, 200], si:'hi'}], [{p:[0], li:'x'}], 'left')); + assert.deepEqual([{p:[1, 201], si:'hi'}], type.transform([{p:[0, 201], si:'hi'}], [{p:[0], li:'x'}], 'right')); + assert.deepEqual([{p:[0, 202], si:'hi'}], type.transform([{p:[0, 202], si:'hi'}], [{p:[1], li:'x'}], 'left')); + assert.deepEqual([{p:[2], t:'text0', o:[{p:200, i:'hi'}]}], type.transform([{p:[1], t:'text0', o:[{p:200, i:'hi'}]}], [{p:[0], li:'x'}], 'left')); + assert.deepEqual([{p:[1], t:'text0', o:[{p:201, i:'hi'}]}], type.transform([{p:[0], t:'text0', o:[{p:201, i:'hi'}]}], [{p:[0], li:'x'}], 'right')); + assert.deepEqual([{p:[0], t:'text0', o:[{p:202, i:'hi'}]}], type.transform([{p:[0], t:'text0', o:[{p:202, i:'hi'}]}], [{p:[1], li:'x'}], 'left')); + + assert.deepEqual([{p:[0, 203], si:'hi'}], type.transform([{p:[1, 203], si:'hi'}], [{p:[0], ld:'x'}], 'left')); + assert.deepEqual([{p:[0, 204], si:'hi'}], type.transform([{p:[0, 204], si:'hi'}], [{p:[1], ld:'x'}], 'left')); + assert.deepEqual([{p:['x',3], si: 'hi'}], type.transform([{p:['x',3], si:'hi'}], [{p:['x',0,'x'], li:0}], 'left')); + assert.deepEqual([{p:['x',3,'x'], si: 'hi'}], type.transform([{p:['x',3,'x'], si:'hi'}], [{p:['x',5], li:0}], 'left')); + assert.deepEqual([{p:['x',4,'x'], si: 'hi'}], type.transform([{p:['x',3,'x'], si:'hi'}], [{p:['x',0], li:0}], 'left')); + assert.deepEqual([{p:[0], t:'text0', o:[{p:203, i:'hi'}]}], type.transform([{p:[1], t:'text0', o:[{p:203, i:'hi'}]}], [{p:[0], ld:'x'}], 'left')); + assert.deepEqual([{p:[0], t:'text0', o:[{p:204, i:'hi'}]}], type.transform([{p:[0], t:'text0', o:[{p:204, i:'hi'}]}], [{p:[1], ld:'x'}], 'left')); + assert.deepEqual([{p:['x'], t:'text0', o:[{p:3,i: 'hi'}]}], type.transform([{p:['x'], t:'text0', o:[{p:3, i:'hi'}]}], [{p:['x',0,'x'], li:0}], 'left')); + + assert.deepEqual([{p:[1],ld:2}], type.transform([{p:[0],ld:2}], [{p:[0],li:1}], 'left')); + return assert.deepEqual([{p:[1],ld:2}], type.transform([{p:[0],ld:2}], [{p:[0],li:1}], 'right')); + }); + + it('converts ops on deleted elements to noops', function() { + assert.deepEqual([], type.transform([{p:[1, 0], si:'hi'}], [{p:[1], ld:'x'}], 'left')); + assert.deepEqual([], type.transform([{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], ld:'x'}], 'left')); + assert.deepEqual([{p:[0],li:'x'}], type.transform([{p:[0],li:'x'}], [{p:[0],ld:'y'}], 'left')); + return assert.deepEqual([], type.transform([{p:[0],na:-3}], [{p:[0],ld:48}], 'left')); + }); + + it('converts ops on replaced elements to noops', function() { + assert.deepEqual([], type.transform([{p:[1, 0], si:'hi'}], [{p:[1], ld:'x', li:'y'}], 'left')); + assert.deepEqual([], type.transform([{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], ld:'x', li:'y'}], 'left')); + return assert.deepEqual([{p:[0], li:'hi'}], type.transform([{p:[0], li:'hi'}], [{p:[0], ld:'x', li:'y'}], 'left')); + }); + + it('changes deleted data to reflect edits', function() { + assert.deepEqual([{p:[1], ld:'abc'}], type.transform([{p:[1], ld:'a'}], [{p:[1, 1], si:'bc'}], 'left')); + return assert.deepEqual([{p:[1], ld:'abc'}], type.transform([{p:[1], ld:'a'}], [{p:[1], t:'text0', o:[{p:1, i:'bc'}]}], 'left')); + }); + + it('Puts the left op first if two inserts are simultaneous', function() { + assert.deepEqual([{p:[1], li:'a'}], type.transform([{p:[1], li:'a'}], [{p:[1], li:'b'}], 'left')); + return assert.deepEqual([{p:[2], li:'b'}], type.transform([{p:[1], li:'b'}], [{p:[1], li:'a'}], 'right')); + }); + + return it('converts an attempt to re-delete a list element into a no-op', function() { + assert.deepEqual([], type.transform([{p:[1], ld:'x'}], [{p:[1], ld:'x'}], 'left')); + return assert.deepEqual([], type.transform([{p:[1], ld:'x'}], [{p:[1], ld:'x'}], 'right')); + }); + }); + + + describe('#compose()', function() { + it('composes insert then delete into a no-op', function() { + assert.deepEqual([], type.compose([{p:[1], li:'abc'}], [{p:[1], ld:'abc'}])); + return assert.deepEqual([{p:[1],ld:null,li:'x'}], type.transform([{p:[0],ld:null,li:"x"}], [{p:[0],li:"The"}], 'right')); + }); + + it('doesn\'t change the original object', function() { + const a = [{p:[0],ld:'abc',li:null}]; + assert.deepEqual([{p:[0],ld:'abc'}], type.compose(a, [{p:[0],ld:null}])); + return assert.deepEqual([{p:[0],ld:'abc',li:null}], a); + }); + + return it('composes together adjacent string ops', function() { + assert.deepEqual([{p:[100], si:'hi'}], type.compose([{p:[100], si:'h'}], [{p:[101], si:'i'}])); + return assert.deepEqual([{p:[], t:'text0', o:[{p:100, i:'hi'}]}], type.compose([{p:[], t:'text0', o:[{p:100, i:'h'}]}], [{p:[], t:'text0', o:[{p:101, i:'i'}]}])); + }); + }); + + it('moves ops on a moved element with the element', function() { + assert.deepEqual([{p:[10], ld:'x'}], type.transform([{p:[4], ld:'x'}], [{p:[4], lm:10}], 'left')); + assert.deepEqual([{p:[10, 1], si:'a'}], type.transform([{p:[4, 1], si:'a'}], [{p:[4], lm:10}], 'left')); + assert.deepEqual([{p:[10], t:'text0', o:[{p:1, i:'a'}]}], type.transform([{p:[4], t:'text0', o:[{p:1, i:'a'}]}], [{p:[4], lm:10}], 'left')); + assert.deepEqual([{p:[10, 1], li:'a'}], type.transform([{p:[4, 1], li:'a'}], [{p:[4], lm:10}], 'left')); + assert.deepEqual([{p:[10, 1], ld:'b', li:'a'}], type.transform([{p:[4, 1], ld:'b', li:'a'}], [{p:[4], lm:10}], 'left')); + + assert.deepEqual([{p:[0],li:null}], type.transform([{p:[0],li:null}], [{p:[0],lm:1}], 'left')); + // [_,_,_,_,5,6,7,_] + // c: [_,_,_,_,5,'x',6,7,_] p:5 li:'x' + // s: [_,6,_,_,_,5,7,_] p:5 lm:1 + // correct: [_,6,_,_,_,5,'x',7,_] + assert.deepEqual([{p:[6],li:'x'}], type.transform([{p:[5],li:'x'}], [{p:[5],lm:1}], 'left')); + // [_,_,_,_,5,6,7,_] + // c: [_,_,_,_,5,6,7,_] p:5 ld:6 + // s: [_,6,_,_,_,5,7,_] p:5 lm:1 + // correct: [_,_,_,_,5,7,_] + assert.deepEqual([{p:[1],ld:6}], type.transform([{p:[5],ld:6}], [{p:[5],lm:1}], 'left')); + //assert.deepEqual [{p:[0],li:{}}], type.transform [{p:[0],li:{}}], [{p:[0],lm:0}], 'right' + assert.deepEqual([{p:[0],li:[]}], type.transform([{p:[0],li:[]}], [{p:[1],lm:0}], 'left')); + return assert.deepEqual([{p:[2],li:'x'}], type.transform([{p:[2],li:'x'}], [{p:[0],lm:1}], 'left')); + }); + + it('moves target index on ld/li', function() { + assert.deepEqual([{p:[0],lm:1}], type.transform([{p:[0], lm: 2}], [{p:[1], ld:'x'}], 'left')); + assert.deepEqual([{p:[1],lm:3}], type.transform([{p:[2], lm: 4}], [{p:[1], ld:'x'}], 'left')); + assert.deepEqual([{p:[0],lm:3}], type.transform([{p:[0], lm: 2}], [{p:[1], li:'x'}], 'left')); + assert.deepEqual([{p:[3],lm:5}], type.transform([{p:[2], lm: 4}], [{p:[1], li:'x'}], 'left')); + return assert.deepEqual([{p:[1],lm:1}], type.transform([{p:[0], lm: 0}], [{p:[0], li:28}], 'left')); + }); + + it('tiebreaks lm vs. ld/li', function() { + assert.deepEqual([], type.transform([{p:[0], lm: 2}], [{p:[0], ld:'x'}], 'left')); + assert.deepEqual([], type.transform([{p:[0], lm: 2}], [{p:[0], ld:'x'}], 'right')); + assert.deepEqual([{p:[1], lm:3}], type.transform([{p:[0], lm: 2}], [{p:[0], li:'x'}], 'left')); + return assert.deepEqual([{p:[1], lm:3}], type.transform([{p:[0], lm: 2}], [{p:[0], li:'x'}], 'right')); + }); + + it('replacement vs. deletion', () => assert.deepEqual([{p:[0],li:'y'}], type.transform([{p:[0],ld:'x',li:'y'}], [{p:[0],ld:'x'}], 'right'))); + + it('replacement vs. insertion', () => assert.deepEqual([{p:[1],ld:{},li:"brillig"}], type.transform([{p:[0],ld:{},li:"brillig"}], [{p:[0],li:36}], 'left'))); + + it('replacement vs. replacement', function() { + assert.deepEqual([], type.transform([{p:[0],ld:null,li:[]}], [{p:[0],ld:null,li:0}], 'right')); + return assert.deepEqual([{p:[0],ld:[],li:0}], type.transform([{p:[0],ld:null,li:0}], [{p:[0],ld:null,li:[]}], 'left')); + }); + + it('composes replace with delete of replaced element results in insert', () => assert.deepEqual([{p:[2],ld:[]}], type.compose([{p:[2],ld:[],li:null}], [{p:[2],ld:null}]))); + + it('lm vs lm', function() { + assert.deepEqual([{p:[0],lm:2}], type.transform([{p:[0],lm:2}], [{p:[2],lm:1}], 'left')); + assert.deepEqual([{p:[4],lm:4}], type.transform([{p:[3],lm:3}], [{p:[5],lm:0}], 'left')); + assert.deepEqual([{p:[2],lm:0}], type.transform([{p:[2],lm:0}], [{p:[1],lm:0}], 'left')); + assert.deepEqual([{p:[2],lm:1}], type.transform([{p:[2],lm:0}], [{p:[1],lm:0}], 'right')); + assert.deepEqual([{p:[3],lm:1}], type.transform([{p:[2],lm:0}], [{p:[5],lm:0}], 'right')); + assert.deepEqual([{p:[3],lm:0}], type.transform([{p:[2],lm:0}], [{p:[5],lm:0}], 'left')); + assert.deepEqual([{p:[0],lm:5}], type.transform([{p:[2],lm:5}], [{p:[2],lm:0}], 'left')); + assert.deepEqual([{p:[0],lm:5}], type.transform([{p:[2],lm:5}], [{p:[2],lm:0}], 'left')); + assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[1],lm:0}], [{p:[0],lm:5}], 'right')); + assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[1],lm:0}], [{p:[0],lm:1}], 'right')); + assert.deepEqual([{p:[1],lm:1}], type.transform([{p:[0],lm:1}], [{p:[1],lm:0}], 'left')); + assert.deepEqual([{p:[1],lm:2}], type.transform([{p:[0],lm:1}], [{p:[5],lm:0}], 'right')); + assert.deepEqual([{p:[3],lm:2}], type.transform([{p:[2],lm:1}], [{p:[5],lm:0}], 'right')); + assert.deepEqual([{p:[2],lm:1}], type.transform([{p:[3],lm:1}], [{p:[1],lm:3}], 'left')); + assert.deepEqual([{p:[2],lm:3}], type.transform([{p:[1],lm:3}], [{p:[3],lm:1}], 'left')); + assert.deepEqual([{p:[2],lm:6}], type.transform([{p:[2],lm:6}], [{p:[0],lm:1}], 'left')); + assert.deepEqual([{p:[2],lm:6}], type.transform([{p:[2],lm:6}], [{p:[0],lm:1}], 'right')); + assert.deepEqual([{p:[2],lm:6}], type.transform([{p:[2],lm:6}], [{p:[1],lm:0}], 'left')); + assert.deepEqual([{p:[2],lm:6}], type.transform([{p:[2],lm:6}], [{p:[1],lm:0}], 'right')); + assert.deepEqual([{p:[0],lm:2}], type.transform([{p:[0],lm:1}], [{p:[2],lm:1}], 'left')); + assert.deepEqual([{p:[2],lm:0}], type.transform([{p:[2],lm:1}], [{p:[0],lm:1}], 'right')); + assert.deepEqual([{p:[1],lm:1}], type.transform([{p:[0],lm:0}], [{p:[1],lm:0}], 'left')); + assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[0],lm:1}], [{p:[1],lm:3}], 'left')); + assert.deepEqual([{p:[3],lm:1}], type.transform([{p:[2],lm:1}], [{p:[3],lm:2}], 'left')); + return assert.deepEqual([{p:[3],lm:3}], type.transform([{p:[3],lm:2}], [{p:[2],lm:1}], 'left')); + }); + + it('changes indices correctly around a move', function() { + assert.deepEqual([{p:[1,0],li:{}}], type.transform([{p:[0,0],li:{}}], [{p:[1],lm:0}], 'left')); + assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[1],lm:0}], [{p:[0],ld:{}}], 'left')); + assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[0],lm:1}], [{p:[1],ld:{}}], 'left')); + assert.deepEqual([{p:[5],lm:0}], type.transform([{p:[6],lm:0}], [{p:[2],ld:{}}], 'left')); + assert.deepEqual([{p:[1],lm:0}], type.transform([{p:[1],lm:0}], [{p:[2],ld:{}}], 'left')); + assert.deepEqual([{p:[1],lm:1}], type.transform([{p:[2],lm:1}], [{p:[1],ld:3}], 'right')); + + assert.deepEqual([{p:[1],ld:{}}], type.transform([{p:[2],ld:{}}], [{p:[1],lm:2}], 'right')); + assert.deepEqual([{p:[2],ld:{}}], type.transform([{p:[1],ld:{}}], [{p:[2],lm:1}], 'left')); + + + assert.deepEqual([{p:[0],ld:{}}], type.transform([{p:[1],ld:{}}], [{p:[0],lm:1}], 'right')); + + assert.deepEqual([{p:[0],ld:1,li:2}], type.transform([{p:[1],ld:1,li:2}], [{p:[1],lm:0}], 'left')); + assert.deepEqual([{p:[0],ld:2,li:3}], type.transform([{p:[1],ld:2,li:3}], [{p:[0],lm:1}], 'left')); + return assert.deepEqual([{p:[1],ld:3,li:4}], type.transform([{p:[0],ld:3,li:4}], [{p:[1],lm:0}], 'left')); + }); + + return it('li vs lm', function() { + const li = p => [{p:[p],li:[]}]; + const lm = (f,t) => [{p:[f],lm:t}]; + const xf = type.transform; + + assert.deepEqual((li(0)), xf((li(0)), (lm(1, 3)), 'left')); + assert.deepEqual((li(1)), xf((li(1)), (lm(1, 3)), 'left')); + assert.deepEqual((li(1)), xf((li(2)), (lm(1, 3)), 'left')); + assert.deepEqual((li(2)), xf((li(3)), (lm(1, 3)), 'left')); + assert.deepEqual((li(4)), xf((li(4)), (lm(1, 3)), 'left')); + + assert.deepEqual((lm(2, 4)), xf((lm(1, 3)), (li(0)), 'right')); + assert.deepEqual((lm(2, 4)), xf((lm(1, 3)), (li(1)), 'right')); + assert.deepEqual((lm(1, 4)), xf((lm(1, 3)), (li(2)), 'right')); + assert.deepEqual((lm(1, 4)), xf((lm(1, 3)), (li(3)), 'right')); + assert.deepEqual((lm(1, 3)), xf((lm(1, 3)), (li(4)), 'right')); + + assert.deepEqual((li(0)), xf((li(0)), (lm(1, 2)), 'left')); + assert.deepEqual((li(1)), xf((li(1)), (lm(1, 2)), 'left')); + assert.deepEqual((li(1)), xf((li(2)), (lm(1, 2)), 'left')); + assert.deepEqual((li(3)), xf((li(3)), (lm(1, 2)), 'left')); + + assert.deepEqual((li(0)), xf((li(0)), (lm(3, 1)), 'left')); + assert.deepEqual((li(1)), xf((li(1)), (lm(3, 1)), 'left')); + assert.deepEqual((li(3)), xf((li(2)), (lm(3, 1)), 'left')); + assert.deepEqual((li(4)), xf((li(3)), (lm(3, 1)), 'left')); + assert.deepEqual((li(4)), xf((li(4)), (lm(3, 1)), 'left')); + + assert.deepEqual((lm(4, 2)), xf((lm(3, 1)), (li(0)), 'right')); + assert.deepEqual((lm(4, 2)), xf((lm(3, 1)), (li(1)), 'right')); + assert.deepEqual((lm(4, 1)), xf((lm(3, 1)), (li(2)), 'right')); + assert.deepEqual((lm(4, 1)), xf((lm(3, 1)), (li(3)), 'right')); + assert.deepEqual((lm(3, 1)), xf((lm(3, 1)), (li(4)), 'right')); + + assert.deepEqual((li(0)), xf((li(0)), (lm(2, 1)), 'left')); + assert.deepEqual((li(1)), xf((li(1)), (lm(2, 1)), 'left')); + assert.deepEqual((li(3)), xf((li(2)), (lm(2, 1)), 'left')); + return assert.deepEqual((li(3)), xf((li(3)), (lm(2, 1)), 'left')); + }); + }); + + + describe('object', function() { + it('passes sanity checks', function() { + assert.deepEqual({x:'a', y:'b'}, type.apply({x:'a'}, [{p:['y'], oi:'b'}])); + assert.deepEqual({}, type.apply({x:'a'}, [{p:['x'], od:'a'}])); + return assert.deepEqual({x:'b'}, type.apply({x:'a'}, [{p:['x'], od:'a', oi:'b'}])); + }); + + it('Ops on deleted elements become noops', function() { + assert.deepEqual([], type.transform([{p:[1, 0], si:'hi'}], [{p:[1], od:'x'}], 'left')); + assert.deepEqual([], type.transform([{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], od:'x'}], 'left')); + assert.deepEqual([], type.transform([{p:[9],si:"bite "}], [{p:[],od:"agimble s",oi:null}], 'right')); + return assert.deepEqual([], type.transform([{p:[], t:'text0', o:[{p:9, i:"bite "}]}], [{p:[],od:"agimble s",oi:null}], 'right')); + }); + + it('Ops on replaced elements become noops', function() { + assert.deepEqual([], type.transform([{p:[1, 0], si:'hi'}], [{p:[1], od:'x', oi:'y'}], 'left')); + return assert.deepEqual([], type.transform([{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], od:'x', oi:'y'}], 'left')); + }); + + it('Deleted data is changed to reflect edits', function() { + assert.deepEqual([{p:[1], od:'abc'}], type.transform([{p:[1], od:'a'}], [{p:[1, 1], si:'bc'}], 'left')); + assert.deepEqual([{p:[1], od:'abc'}], type.transform([{p:[1], od:'a'}], [{p:[1], t:'text0', o:[{p:1, i:'bc'}]}], 'left')); + assert.deepEqual([{p:[],od:25,oi:[]}], type.transform([{p:[],od:22,oi:[]}], [{p:[],na:3}], 'left')); + assert.deepEqual([{p:[],od:{toves:""},oi:4}], type.transform([{p:[],od:{toves:0},oi:4}], [{p:["toves"],od:0,oi:""}], 'left')); + assert.deepEqual([{p:[],od:"thou an",oi:[]}], type.transform([{p:[],od:"thou and ",oi:[]}], [{p:[7],sd:"d "}], 'left')); + assert.deepEqual([{p:[],od:"thou an",oi:[]}], type.transform([{p:[],od:"thou and ",oi:[]}], [{p:[], t:'text0', o:[{p:7, d:"d "}]}], 'left')); + assert.deepEqual([], type.transform([{p:["bird"],na:2}], [{p:[],od:{bird:38},oi:20}], 'right')); + assert.deepEqual([{p:[],od:{bird:40},oi:20}], type.transform([{p:[],od:{bird:38},oi:20}], [{p:["bird"],na:2}], 'left')); + assert.deepEqual([{p:['He'],od:[]}], type.transform([{p:["He"],od:[]}], [{p:["The"],na:-3}], 'right')); + return assert.deepEqual([], type.transform([{p:["He"],oi:{}}], [{p:[],od:{},oi:"the"}], 'left')); + }); + + it('If two inserts are simultaneous, the lefts insert will win', function() { + assert.deepEqual([{p:[1], oi:'a', od:'b'}], type.transform([{p:[1], oi:'a'}], [{p:[1], oi:'b'}], 'left')); + return assert.deepEqual([], type.transform([{p:[1], oi:'b'}], [{p:[1], oi:'a'}], 'right')); + }); + + it('parallel ops on different keys miss each other', function() { + assert.deepEqual([{p:['a'], oi: 'x'}], type.transform([{p:['a'], oi:'x'}], [{p:['b'], oi:'z'}], 'left')); + assert.deepEqual([{p:['a'], oi: 'x'}], type.transform([{p:['a'], oi:'x'}], [{p:['b'], od:'z'}], 'left')); + assert.deepEqual([{p:["in","he"],oi:{}}], type.transform([{p:["in","he"],oi:{}}], [{p:["and"],od:{}}], 'right')); + assert.deepEqual([{p:['x',0],si:"his "}], type.transform([{p:['x',0],si:"his "}], [{p:['y'],od:0,oi:1}], 'right')); + return assert.deepEqual([{p:['x'], t:'text0', o:[{p:0, i:"his "}]}], type.transform([{p:['x'],t:'text0', o:[{p:0, i:"his "}]}], [{p:['y'],od:0,oi:1}], 'right')); + }); + + it('replacement vs. deletion', () => assert.deepEqual([{p:[],oi:{}}], type.transform([{p:[],od:[''],oi:{}}], [{p:[],od:['']}], 'right'))); + + it('replacement vs. replacement', function() { + assert.deepEqual([], type.transform([{p:[],od:['']},{p:[],oi:{}}], [{p:[],od:['']},{p:[],oi:null}], 'right')); + assert.deepEqual([{p:[],od:null,oi:{}}], type.transform([{p:[],od:['']},{p:[],oi:{}}], [{p:[],od:['']},{p:[],oi:null}], 'left')); + assert.deepEqual([], type.transform([{p:[],od:[''],oi:{}}], [{p:[],od:[''],oi:null}], 'right')); + assert.deepEqual([{p:[],od:null,oi:{}}], type.transform([{p:[],od:[''],oi:{}}], [{p:[],od:[''],oi:null}], 'left')); + + // test diamond property + const rightOps = [ {"p":[],"od":null,"oi":{}} ]; + const leftOps = [ {"p":[],"od":null,"oi":""} ]; + const rightHas = type.apply(null, rightOps); + const leftHas = type.apply(null, leftOps); + + const [left_, right_] = Array.from(transformX(type, leftOps, rightOps)); + assert.deepEqual(leftHas, type.apply(rightHas, left_)); + return assert.deepEqual(leftHas, type.apply(leftHas, right_)); + }); + + + return it('An attempt to re-delete a key becomes a no-op', function() { + assert.deepEqual([], type.transform([{p:['k'], od:'x'}], [{p:['k'], od:'x'}], 'left')); + return assert.deepEqual([], type.transform([{p:['k'], od:'x'}], [{p:['k'], od:'x'}], 'right')); + }); + }); + + return describe('randomizer', function() { + this.timeout(20000); + this.slow(6000); + it('passes', () => fuzzer(type, require('./json0-generator'), 1000)); + + return it('passes with string subtype', function() { + type._testStringSubtype = true; // hack + fuzzer(type, require('./json0-generator'), 1000); + return delete type._testStringSubtype; + }); + }); +}; + +describe('json', () => describe('native type', () => genTests(nativetype))); + //exports.webclient = genTests require('../helpers/webclient').types.json diff --git a/test/text0-generator.coffee b/test/text0-generator.coffee deleted file mode 100644 index c3df0d5..0000000 --- a/test/text0-generator.coffee +++ /dev/null @@ -1,30 +0,0 @@ -# Random op generator for the embedded text0 OT type. This is used by the fuzzer -# test. - -{randomReal, randomWord} = require 'ot-fuzzer' -text0 = require '../lib/text0' - -module.exports = genRandomOp = (docStr) -> - pct = 0.9 - - op = [] - - while randomReal() < pct -# console.log "docStr = #{i docStr}" - pct /= 2 - - if randomReal() > 0.5 - # Append an insert - pos = Math.floor(randomReal() * (docStr.length + 1)) - str = randomWord() + ' ' - text0._append op, {i:str, p:pos} - docStr = docStr[...pos] + str + docStr[pos..] - else - # Append a delete - pos = Math.floor(randomReal() * docStr.length) - length = Math.min(Math.floor(randomReal() * 4), docStr.length - pos) - text0._append op, {d:docStr[pos...(pos + length)], p:pos} - docStr = docStr[...pos] + docStr[(pos + length)..] - -# console.log "generated op #{i op} -> #{i docStr}" - [op, docStr] diff --git a/test/text0-generator.js b/test/text0-generator.js new file mode 100644 index 0000000..e86b190 --- /dev/null +++ b/test/text0-generator.js @@ -0,0 +1,40 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +// Random op generator for the embedded text0 OT type. This is used by the fuzzer +// test. + +let genRandomOp; +const {randomReal, randomWord} = require('ot-fuzzer'); +const text0 = require('../lib/text0'); + +module.exports = (genRandomOp = function(docStr) { + let pct = 0.9; + + const op = []; + + while (randomReal() < pct) { +// console.log "docStr = #{i docStr}" + var pos; + pct /= 2; + + if (randomReal() > 0.5) { + // Append an insert + pos = Math.floor(randomReal() * (docStr.length + 1)); + const str = randomWord() + ' '; + text0._append(op, {i:str, p:pos}); + docStr = docStr.slice(0, pos) + str + docStr.slice(pos); + } else { + // Append a delete + pos = Math.floor(randomReal() * docStr.length); + const length = Math.min(Math.floor(randomReal() * 4), docStr.length - pos); + text0._append(op, {d:docStr.slice(pos, (pos + length)), p:pos}); + docStr = docStr.slice(0, pos) + docStr.slice((pos + length)); + } + } + +// console.log "generated op #{i op} -> #{i docStr}" + return [op, docStr]; +}); diff --git a/test/text0.coffee b/test/text0.coffee deleted file mode 100644 index 15592dd..0000000 --- a/test/text0.coffee +++ /dev/null @@ -1,120 +0,0 @@ -# Tests for the embedded non-composable text type text0. - -assert = require 'assert' -fuzzer = require 'ot-fuzzer' -text0 = require '../lib/text0' - -describe 'text0', -> - describe 'compose', -> - # Compose is actually pretty easy - it 'is sane', -> - assert.deepEqual text0.compose([], []), [] - assert.deepEqual text0.compose([{i:'x', p:0}], []), [{i:'x', p:0}] - assert.deepEqual text0.compose([], [{i:'x', p:0}]), [{i:'x', p:0}] - assert.deepEqual text0.compose([{i:'y', p:100}], [{i:'x', p:0}]), [{i:'y', p:100}, {i:'x', p:0}] - - describe 'transform', -> - it 'is sane', -> - assert.deepEqual [], text0.transform [], [], 'left' - assert.deepEqual [], text0.transform [], [], 'right' - - assert.deepEqual [{i:'y', p:100}, {i:'x', p:0}], text0.transform [{i:'y', p:100}, {i:'x', p:0}], [], 'left' - assert.deepEqual [], text0.transform [], [{i:'y', p:100}, {i:'x', p:0}], 'right' - - it 'inserts', -> - assert.deepEqual [[{i:'x', p:10}], [{i:'a', p:1}]], text0.transformX [{i:'x', p:9}], [{i:'a', p:1}] - assert.deepEqual [[{i:'x', p:10}], [{i:'a', p:11}]], text0.transformX [{i:'x', p:10}], [{i:'a', p:10}] - - assert.deepEqual [[{i:'x', p:10}], [{d:'a', p:9}]], text0.transformX [{i:'x', p:11}], [{d:'a', p:9}] - assert.deepEqual [[{i:'x', p:10}], [{d:'a', p:10}]], text0.transformX [{i:'x', p:11}], [{d:'a', p:10}] - assert.deepEqual [[{i:'x', p:11}], [{d:'a', p:12}]], text0.transformX [{i:'x', p:11}], [{d:'a', p:11}] - - assert.deepEqual [{i:'x', p:10}], text0.transform [{i:'x', p:10}], [{d:'a', p:11}], 'left' - assert.deepEqual [{i:'x', p:10}], text0.transform [{i:'x', p:10}], [{d:'a', p:10}], 'left' - assert.deepEqual [{i:'x', p:10}], text0.transform [{i:'x', p:10}], [{d:'a', p:10}], 'right' - - it 'deletes', -> - assert.deepEqual [[{d:'abc', p:8}], [{d:'xy', p:4}]], text0.transformX [{d:'abc', p:10}], [{d:'xy', p:4}] - assert.deepEqual [[{d:'ac', p:10}], []], text0.transformX [{d:'abc', p:10}], [{d:'b', p:11}] - assert.deepEqual [[], [{d:'ac', p:10}]], text0.transformX [{d:'b', p:11}], [{d:'abc', p:10}] - assert.deepEqual [[{d:'a', p:10}], []], text0.transformX [{d:'abc', p:10}], [{d:'bc', p:11}] - assert.deepEqual [[{d:'c', p:10}], []], text0.transformX [{d:'abc', p:10}], [{d:'ab', p:10}] - assert.deepEqual [[{d:'a', p:10}], [{d:'d', p:10}]], text0.transformX [{d:'abc', p:10}], [{d:'bcd', p:11}] - assert.deepEqual [[{d:'d', p:10}], [{d:'a', p:10}]], text0.transformX [{d:'bcd', p:11}], [{d:'abc', p:10}] - assert.deepEqual [[{d:'abc', p:10}], [{d:'xy', p:10}]], text0.transformX [{d:'abc', p:10}], [{d:'xy', p:13}] - - describe 'transformCursor', -> - it 'is sane', -> - assert.strictEqual 0, text0.transformCursor 0, [], 'right' - assert.strictEqual 0, text0.transformCursor 0, [], 'left' - assert.strictEqual 100, text0.transformCursor 100, [] - - it 'works vs insert', -> - assert.strictEqual 0, text0.transformCursor 0, [{i:'asdf', p:100}], 'right' - assert.strictEqual 0, text0.transformCursor 0, [{i:'asdf', p:100}], 'left' - - assert.strictEqual 204, text0.transformCursor 200, [{i:'asdf', p:100}], 'right' - assert.strictEqual 204, text0.transformCursor 200, [{i:'asdf', p:100}], 'left' - - assert.strictEqual 104, text0.transformCursor 100, [{i:'asdf', p:100}], 'right' - assert.strictEqual 100, text0.transformCursor 100, [{i:'asdf', p:100}], 'left' - - it 'works vs delete', -> - assert.strictEqual 0, text0.transformCursor 0, [{d:'asdf', p:100}], 'right' - assert.strictEqual 0, text0.transformCursor 0, [{d:'asdf', p:100}], 'left' - assert.strictEqual 0, text0.transformCursor 0, [{d:'asdf', p:100}] - - assert.strictEqual 196, text0.transformCursor 200, [{d:'asdf', p:100}] - - assert.strictEqual 100, text0.transformCursor 100, [{d:'asdf', p:100}] - assert.strictEqual 100, text0.transformCursor 102, [{d:'asdf', p:100}] - assert.strictEqual 100, text0.transformCursor 104, [{d:'asdf', p:100}] - assert.strictEqual 101, text0.transformCursor 105, [{d:'asdf', p:100}] - - describe 'normalize', -> - it 'is sane', -> - testUnchanged = (op) -> assert.deepEqual op, text0.normalize op - testUnchanged [] - testUnchanged [{i:'asdf', p:100}] - testUnchanged [{i:'asdf', p:100}, {d:'fdsa', p:123}] - - it 'adds missing p:0', -> - assert.deepEqual [{i:'abc', p:0}], text0.normalize [{i:'abc'}] - assert.deepEqual [{d:'abc', p:0}], text0.normalize [{d:'abc'}] - assert.deepEqual [{i:'abc', p:0}, {d:'abc', p:0}], text0.normalize [{i:'abc'}, {d:'abc'}] - - it 'converts op to an array', -> - assert.deepEqual [{i:'abc', p:0}], text0.normalize {i:'abc', p:0} - assert.deepEqual [{d:'abc', p:0}], text0.normalize {d:'abc', p:0} - - it 'works with a really simple op', -> - assert.deepEqual [{i:'abc', p:0}], text0.normalize {i:'abc'} - - it 'compress inserts', -> - assert.deepEqual [{i:'xyzabc', p:10}], text0.normalize [{i:'abc', p:10}, {i:'xyz', p:10}] - assert.deepEqual [{i:'axyzbc', p:10}], text0.normalize [{i:'abc', p:10}, {i:'xyz', p:11}] - assert.deepEqual [{i:'abcxyz', p:10}], text0.normalize [{i:'abc', p:10}, {i:'xyz', p:13}] - - it 'doesnt compress separate inserts', -> - t = (op) -> assert.deepEqual op, text0.normalize op - - t [{i:'abc', p:10}, {i:'xyz', p:9}] - t [{i:'abc', p:10}, {i:'xyz', p:14}] - - it 'compress deletes', -> - assert.deepEqual [{d:'xyabc', p:8}], text0.normalize [{d:'abc', p:10}, {d:'xy', p:8}] - assert.deepEqual [{d:'xabcy', p:9}], text0.normalize [{d:'abc', p:10}, {d:'xy', p:9}] - assert.deepEqual [{d:'abcxy', p:10}], text0.normalize [{d:'abc', p:10}, {d:'xy', p:10}] - - it 'doesnt compress separate deletes', -> - t = (op) -> assert.deepEqual op, text0.normalize op - - t [{d:'abc', p:10}, {d:'xyz', p:6}] - t [{d:'abc', p:10}, {d:'xyz', p:11}] - - - describe 'randomizer', -> it 'passes', -> - @timeout 4000 - @slow 4000 - fuzzer text0, require('./text0-generator') - diff --git a/test/text0.js b/test/text0.js new file mode 100644 index 0000000..58f161c --- /dev/null +++ b/test/text0.js @@ -0,0 +1,145 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +// Tests for the embedded non-composable text type text0. + +const assert = require('assert'); +const fuzzer = require('ot-fuzzer'); +const text0 = require('../lib/text0'); + +describe('text0', function() { + describe('compose', () => + // Compose is actually pretty easy + it('is sane', function() { + assert.deepEqual(text0.compose([], []), []); + assert.deepEqual(text0.compose([{i:'x', p:0}], []), [{i:'x', p:0}]); + assert.deepEqual(text0.compose([], [{i:'x', p:0}]), [{i:'x', p:0}]); + return assert.deepEqual(text0.compose([{i:'y', p:100}], [{i:'x', p:0}]), [{i:'y', p:100}, {i:'x', p:0}]); + }) +); + + describe('transform', function() { + it('is sane', function() { + assert.deepEqual([], text0.transform([], [], 'left')); + assert.deepEqual([], text0.transform([], [], 'right')); + + assert.deepEqual([{i:'y', p:100}, {i:'x', p:0}], text0.transform([{i:'y', p:100}, {i:'x', p:0}], [], 'left')); + return assert.deepEqual([], text0.transform([], [{i:'y', p:100}, {i:'x', p:0}], 'right')); + }); + + it('inserts', function() { + assert.deepEqual([[{i:'x', p:10}], [{i:'a', p:1}]], text0.transformX([{i:'x', p:9}], [{i:'a', p:1}])); + assert.deepEqual([[{i:'x', p:10}], [{i:'a', p:11}]], text0.transformX([{i:'x', p:10}], [{i:'a', p:10}])); + + assert.deepEqual([[{i:'x', p:10}], [{d:'a', p:9}]], text0.transformX([{i:'x', p:11}], [{d:'a', p:9}])); + assert.deepEqual([[{i:'x', p:10}], [{d:'a', p:10}]], text0.transformX([{i:'x', p:11}], [{d:'a', p:10}])); + assert.deepEqual([[{i:'x', p:11}], [{d:'a', p:12}]], text0.transformX([{i:'x', p:11}], [{d:'a', p:11}])); + + assert.deepEqual([{i:'x', p:10}], text0.transform([{i:'x', p:10}], [{d:'a', p:11}], 'left')); + assert.deepEqual([{i:'x', p:10}], text0.transform([{i:'x', p:10}], [{d:'a', p:10}], 'left')); + return assert.deepEqual([{i:'x', p:10}], text0.transform([{i:'x', p:10}], [{d:'a', p:10}], 'right')); + }); + + return it('deletes', function() { + assert.deepEqual([[{d:'abc', p:8}], [{d:'xy', p:4}]], text0.transformX([{d:'abc', p:10}], [{d:'xy', p:4}])); + assert.deepEqual([[{d:'ac', p:10}], []], text0.transformX([{d:'abc', p:10}], [{d:'b', p:11}])); + assert.deepEqual([[], [{d:'ac', p:10}]], text0.transformX([{d:'b', p:11}], [{d:'abc', p:10}])); + assert.deepEqual([[{d:'a', p:10}], []], text0.transformX([{d:'abc', p:10}], [{d:'bc', p:11}])); + assert.deepEqual([[{d:'c', p:10}], []], text0.transformX([{d:'abc', p:10}], [{d:'ab', p:10}])); + assert.deepEqual([[{d:'a', p:10}], [{d:'d', p:10}]], text0.transformX([{d:'abc', p:10}], [{d:'bcd', p:11}])); + assert.deepEqual([[{d:'d', p:10}], [{d:'a', p:10}]], text0.transformX([{d:'bcd', p:11}], [{d:'abc', p:10}])); + return assert.deepEqual([[{d:'abc', p:10}], [{d:'xy', p:10}]], text0.transformX([{d:'abc', p:10}], [{d:'xy', p:13}])); + }); +}); + + describe('transformCursor', function() { + it('is sane', function() { + assert.strictEqual(0, text0.transformCursor(0, [], 'right')); + assert.strictEqual(0, text0.transformCursor(0, [], 'left')); + return assert.strictEqual(100, text0.transformCursor(100, [])); + }); + + it('works vs insert', function() { + assert.strictEqual(0, text0.transformCursor(0, [{i:'asdf', p:100}], 'right')); + assert.strictEqual(0, text0.transformCursor(0, [{i:'asdf', p:100}], 'left')); + + assert.strictEqual(204, text0.transformCursor(200, [{i:'asdf', p:100}], 'right')); + assert.strictEqual(204, text0.transformCursor(200, [{i:'asdf', p:100}], 'left')); + + assert.strictEqual(104, text0.transformCursor(100, [{i:'asdf', p:100}], 'right')); + return assert.strictEqual(100, text0.transformCursor(100, [{i:'asdf', p:100}], 'left')); + }); + + return it('works vs delete', function() { + assert.strictEqual(0, text0.transformCursor(0, [{d:'asdf', p:100}], 'right')); + assert.strictEqual(0, text0.transformCursor(0, [{d:'asdf', p:100}], 'left')); + assert.strictEqual(0, text0.transformCursor(0, [{d:'asdf', p:100}])); + + assert.strictEqual(196, text0.transformCursor(200, [{d:'asdf', p:100}])); + + assert.strictEqual(100, text0.transformCursor(100, [{d:'asdf', p:100}])); + assert.strictEqual(100, text0.transformCursor(102, [{d:'asdf', p:100}])); + assert.strictEqual(100, text0.transformCursor(104, [{d:'asdf', p:100}])); + return assert.strictEqual(101, text0.transformCursor(105, [{d:'asdf', p:100}])); + }); +}); + + describe('normalize', function() { + it('is sane', function() { + const testUnchanged = op => assert.deepEqual(op, text0.normalize(op)); + testUnchanged([]); + testUnchanged([{i:'asdf', p:100}]); + return testUnchanged([{i:'asdf', p:100}, {d:'fdsa', p:123}]); + }); + + it('adds missing p:0', function() { + assert.deepEqual([{i:'abc', p:0}], text0.normalize([{i:'abc'}])); + assert.deepEqual([{d:'abc', p:0}], text0.normalize([{d:'abc'}])); + return assert.deepEqual([{i:'abc', p:0}, {d:'abc', p:0}], text0.normalize([{i:'abc'}, {d:'abc'}])); + }); + + it('converts op to an array', function() { + assert.deepEqual([{i:'abc', p:0}], text0.normalize({i:'abc', p:0})); + return assert.deepEqual([{d:'abc', p:0}], text0.normalize({d:'abc', p:0})); + }); + + it('works with a really simple op', () => assert.deepEqual([{i:'abc', p:0}], text0.normalize({i:'abc'}))); + + it('compress inserts', function() { + assert.deepEqual([{i:'xyzabc', p:10}], text0.normalize([{i:'abc', p:10}, {i:'xyz', p:10}])); + assert.deepEqual([{i:'axyzbc', p:10}], text0.normalize([{i:'abc', p:10}, {i:'xyz', p:11}])); + return assert.deepEqual([{i:'abcxyz', p:10}], text0.normalize([{i:'abc', p:10}, {i:'xyz', p:13}])); + }); + + it('doesnt compress separate inserts', function() { + const t = op => assert.deepEqual(op, text0.normalize(op)); + + t([{i:'abc', p:10}, {i:'xyz', p:9}]); + return t([{i:'abc', p:10}, {i:'xyz', p:14}]); + }); + + it('compress deletes', function() { + assert.deepEqual([{d:'xyabc', p:8}], text0.normalize([{d:'abc', p:10}, {d:'xy', p:8}])); + assert.deepEqual([{d:'xabcy', p:9}], text0.normalize([{d:'abc', p:10}, {d:'xy', p:9}])); + return assert.deepEqual([{d:'abcxy', p:10}], text0.normalize([{d:'abc', p:10}, {d:'xy', p:10}])); + }); + + return it('doesnt compress separate deletes', function() { + const t = op => assert.deepEqual(op, text0.normalize(op)); + + t([{d:'abc', p:10}, {d:'xyz', p:6}]); + return t([{d:'abc', p:10}, {d:'xyz', p:11}]); + }); +}); + + + return describe('randomizer', () => it('passes', function() { + this.timeout(4000); + this.slow(4000); + return fuzzer(text0, require('./text0-generator')); + }) + ); +}); + From a30dcf88dbcfa100376c92f41140df2dd1ec4ef4 Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 07:22:48 +0530 Subject: [PATCH 3/7] Prettier on tests --- test/json0-generator.js | 88 +-- test/json0.js | 1223 +++++++++++++++++++++++++++++++-------- test/text0-generator.js | 25 +- test/text0.js | 290 +++++++--- 4 files changed, 1249 insertions(+), 377 deletions(-) diff --git a/test/json0-generator.js b/test/json0-generator.js index cccb542..968ef2a 100644 --- a/test/json0-generator.js +++ b/test/json0-generator.js @@ -9,7 +9,7 @@ */ let genRandomOp; const json0 = require('../lib/json0'); -const {randomInt, randomReal, randomWord} = require('ot-fuzzer'); +const { randomInt, randomReal, randomWord } = require('ot-fuzzer'); // 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 @@ -28,7 +28,9 @@ const randomKey = function(obj) { let count = 0; for (let key in obj) { - if (randomReal() < (1/++count)) { result = key; } + if (randomReal() < 1 / ++count) { + result = key; + } } return result; } @@ -39,22 +41,35 @@ const randomKey = function(obj) { const randomNewKey = function(obj) { // There's no do-while loop in coffeescript. let key = randomWord(); - while (obj[key] !== undefined) { key = randomWord(); } + while (obj[key] !== undefined) { + key = randomWord(); + } return key; }; // Generate a random object var randomThing = function() { switch (randomInt(6)) { - 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(5), asc = 1 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { obj[randomNewKey(obj)] = randomThing(); } + for ( + let i = 1, end = randomInt(5), asc = 1 <= end; + asc ? i <= end : i >= end; + asc ? i++ : i-- + ) { + obj[randomNewKey(obj)] = randomThing(); + } return obj; - case 4: return (__range__(1, randomInt(5), true).map((j) => randomThing())); - case 5: return randomInt(50); + case 4: + return __range__(1, randomInt(5), true).map(j => randomThing()); + case 5: + return randomInt(50); } }; @@ -62,9 +77,11 @@ var randomThing = function() { const randomPath = function(data) { const path = []; - while ((randomReal() > 0.85) && (typeof data === 'object')) { + while (randomReal() > 0.85 && typeof data === 'object') { const key = randomKey(data); - if (key == null) { break; } + if (key == null) { + break; + } path.push(key); data = data[key]; @@ -73,11 +90,10 @@ const randomPath = function(data) { return path; }; - -module.exports = (genRandomOp = function(data) { +module.exports = genRandomOp = function(data) { let pct = 0.95; - const container = {data: clone(data)}; + const container = { data: clone(data) }; const op = (() => { const result = []; @@ -97,7 +113,7 @@ module.exports = (genRandomOp = function(data) { } const operand = parent[key]; - if ((randomReal() < 0.4) && (parent !== container) && Array.isArray(parent)) { + if (randomReal() < 0.4 && parent !== container && Array.isArray(parent)) { // List move const newIndex = randomInt(parent.length); @@ -106,75 +122,71 @@ module.exports = (genRandomOp = function(data) { // Insert it in the new position. parent.splice(newIndex, 0, operand); - result.push({p:path, lm:newIndex}); - - } else if ((randomReal() < 0.3) || (operand === null)) { + result.push({ p: path, lm: newIndex }); + } else if (randomReal() < 0.3 || operand === null) { // Replace const newValue = randomThing(); parent[key] = newValue; if (Array.isArray(parent)) { - result.push({p:path, ld:operand, li:clone(newValue)}); + result.push({ p: path, ld: operand, li: clone(newValue) }); } else { - result.push({p:path, od:operand, oi:clone(newValue)}); + result.push({ p: path, od: operand, oi: clone(newValue) }); } - } else if (typeof operand === 'string') { // String. This code is adapted from the text op generator. var c, str; - if ((randomReal() > 0.5) || (operand.length === 0)) { + if (randomReal() > 0.5 || operand.length === 0) { // Insert pos = randomInt(operand.length + 1); str = randomWord() + ' '; path.push(pos); parent[key] = operand.slice(0, pos) + str + operand.slice(pos); - c = {p:path, si:str}; + c = { p: path, si: str }; } else { // Delete pos = randomInt(operand.length); length = Math.min(randomInt(4), operand.length - pos); - str = operand.slice(pos, (pos + length)); + str = operand.slice(pos, pos + length); path.push(pos); parent[key] = operand.slice(0, pos) + operand.slice(pos + length); - c = {p:path, sd:str}; + c = { p: path, sd: str }; } if (json0._testStringSubtype) { // Subtype - const subOp = {p:path.pop()}; + const subOp = { p: path.pop() }; if (c.si != null) { subOp.i = c.si; } else { subOp.d = c.sd; } - c = {p:path, t:'text0', o:[subOp]}; + c = { p: path, t: 'text0', o: [subOp] }; } result.push(c); - } else if (typeof operand === 'number') { // Number const inc = randomInt(10) - 3; parent[key] += inc; - result.push({p:path, na:inc}); - + result.push({ p: path, na: inc }); } else if (Array.isArray(operand)) { // Array. Replace is covered above, so we'll just randomly insert or delete. // This code looks remarkably similar to string insert, above. - if ((randomReal() > 0.5) || (operand.length === 0)) { + if (randomReal() > 0.5 || operand.length === 0) { // Insert pos = randomInt(operand.length + 1); obj = randomThing(); path.push(pos); operand.splice(pos, 0, obj); - result.push({p:path, li:clone(obj)}); + result.push({ p: path, li: clone(obj) }); } else { // Delete pos = randomInt(operand.length); @@ -182,26 +194,26 @@ module.exports = (genRandomOp = function(data) { path.push(pos); operand.splice(pos, 1); - result.push({p:path, ld:clone(obj)}); + result.push({ p: path, ld: clone(obj) }); } } else { // Object let k = randomKey(operand); - if ((randomReal() > 0.5) || (k == null)) { + if (randomReal() > 0.5 || k == null) { // Insert k = randomNewKey(operand); obj = randomThing(); path.push(k); operand[k] = obj; - result.push({p:path, oi:clone(obj)}); + result.push({ p: path, oi: clone(obj) }); } else { obj = operand[k]; path.push(k); delete operand[k]; - result.push({p:path, od:clone(obj)}); + result.push({ p: path, od: clone(obj) }); } } } @@ -209,7 +221,7 @@ module.exports = (genRandomOp = function(data) { })(); return [op, container.data]; -}); +}; function __range__(left, right, inclusive) { let range = []; @@ -219,4 +231,4 @@ function __range__(left, right, inclusive) { range.push(i); } return range; -} \ No newline at end of file +} diff --git a/test/json0.js b/test/json0.js index 4f26379..4dda65c 100644 --- a/test/json0.js +++ b/test/json0.js @@ -15,11 +15,15 @@ nativetype.registerSubtype({ name: 'mock', transform(a, b, side) { return { mock: true }; - }}); + } +}); // Cross-transform helper function. Transform server by client and client by // server. Returns [server, client]. -const transformX = (type, left, right) => [type.transform(left, right, 'left'), type.transform(right, left, 'right')]; +const transformX = (type, left, right) => [ + type.transform(left, right, 'left'), + type.transform(right, left, 'right') +]; const genTests = function(type) { // The random op tester above will test that the OT functions are admissable, @@ -29,47 +33,83 @@ const genTests = function(type) { // implementation. describe('sanity', function() { - describe('#create()', () => it('returns null', () => assert.deepEqual(type.create(), null)) - ); + describe('#create()', () => + it('returns null', () => assert.deepEqual(type.create(), null))); describe('#compose()', function() { it('od,oi --> od+oi', function() { - assert.deepEqual([{p:['foo'], od:1, oi:2}], type.compose([{p:['foo'],od:1}],[{p:['foo'],oi:2}])); - return assert.deepEqual([{p:['foo'], od:1},{p:['bar'], oi:2}], type.compose([{p:['foo'],od:1}],[{p:['bar'],oi:2}])); + assert.deepEqual( + [{ p: ['foo'], od: 1, oi: 2 }], + type.compose( + [{ p: ['foo'], od: 1 }], + [{ p: ['foo'], oi: 2 }] + ) + ); + return assert.deepEqual( + [{ p: ['foo'], od: 1 }, { p: ['bar'], oi: 2 }], + type.compose( + [{ p: ['foo'], od: 1 }], + [{ p: ['bar'], oi: 2 }] + ) + ); + }); + return it('merges od+oi, od+oi -> od+oi', () => + assert.deepEqual( + [{ p: ['foo'], od: 1, oi: 2 }], + type.compose( + [{ p: ['foo'], od: 1, oi: 3 }], + [{ p: ['foo'], od: 3, oi: 2 }] + ) + )); }); - return it('merges od+oi, od+oi -> od+oi', () => assert.deepEqual([{p:['foo'], od:1, oi:2}], type.compose([{p:['foo'],od:1,oi:3}],[{p:['foo'],od:3,oi:2}]))); - }); - - return describe('#transform()', () => it('returns sane values', function() { - const t = function(op1, op2) { - assert.deepEqual(op1, type.transform(op1, op2, 'left')); - return assert.deepEqual(op1, type.transform(op1, op2, 'right')); - }; - - t([], []); - t([{p:['foo'], oi:1}], []); - return t([{p:['foo'], oi:1}], [{p:['bar'], oi:2}]); - }) ); -}); + return describe('#transform()', () => + it('returns sane values', function() { + const t = function(op1, op2) { + assert.deepEqual(op1, type.transform(op1, op2, 'left')); + return assert.deepEqual(op1, type.transform(op1, op2, 'right')); + }; + + t([], []); + t([{ p: ['foo'], oi: 1 }], []); + return t([{ p: ['foo'], oi: 1 }], [{ p: ['bar'], oi: 2 }]); + })); + }); describe('number', function() { it('Adds a number', function() { - assert.deepEqual(3, type.apply(1, [{p:[], na:2}])); - return assert.deepEqual([3], type.apply([1], [{p:[0], na:2}])); - }); + assert.deepEqual(3, type.apply(1, [{ p: [], na: 2 }])); + return assert.deepEqual([3], type.apply([1], [{ p: [0], na: 2 }])); + }); it('compresses two adds together in compose', function() { - assert.deepEqual([{p:['a', 'b'], na:3}], type.compose([{p:['a', 'b'], na:1}], [{p:['a', 'b'], na:2}])); - return assert.deepEqual([{p:['a'], na:1}, {p:['b'], na:2}], type.compose([{p:['a'], na:1}], [{p:['b'], na:2}])); - }); + assert.deepEqual( + [{ p: ['a', 'b'], na: 3 }], + type.compose( + [{ p: ['a', 'b'], na: 1 }], + [{ p: ['a', 'b'], na: 2 }] + ) + ); + return assert.deepEqual( + [{ p: ['a'], na: 1 }, { p: ['b'], na: 2 }], + type.compose( + [{ p: ['a'], na: 1 }], + [{ p: ['b'], na: 2 }] + ) + ); + }); - return it('doesn\'t overwrite values when it merges na in append', function() { + return it("doesn't overwrite values when it merges na in append", function() { const rightHas = 21; const leftHas = 3; - const rightOp = [{"p":[],"od":0,"oi":15},{"p":[],"na":4},{"p":[],"na":1},{"p":[],"na":1}]; - const leftOp = [{"p":[],"na":4},{"p":[],"na":-1}]; + const rightOp = [ + { p: [], od: 0, oi: 15 }, + { p: [], na: 4 }, + { p: [], na: 1 }, + { p: [], na: 1 } + ]; + const leftOp = [{ p: [], na: 4 }, { p: [], na: -1 }]; const [right_, left_] = Array.from(transformX(type, rightOp, leftOp)); const s_c = type.apply(rightHas, left_); @@ -78,76 +118,155 @@ const genTests = function(type) { }); }); - // Strings should be handled internally by the text type. We'll just do some basic sanity checks here. describe('string', function() { - describe('#apply()', () => it('works', function() { - assert.deepEqual('abc', type.apply('a', [{p:[1], si:'bc'}])); - assert.deepEqual('bc', type.apply('abc', [{p:[0], sd:'a'}])); - return assert.deepEqual({x:'abc'}, type.apply({x:'a'}, [{p:['x', 1], si:'bc'}])); - }) ); + describe('#apply()', () => + it('works', function() { + assert.deepEqual('abc', type.apply('a', [{ p: [1], si: 'bc' }])); + assert.deepEqual('bc', type.apply('abc', [{ p: [0], sd: 'a' }])); + return assert.deepEqual( + { x: 'abc' }, + type.apply({ x: 'a' }, [{ p: ['x', 1], si: 'bc' }]) + ); + })); return describe('#transform()', function() { - it('splits deletes', () => assert.deepEqual(type.transform([{p:[0], sd:'ab'}], [{p:[1], si:'x'}], 'left'), [{p:[0], sd:'a'}, {p:[1], sd:'b'}])); - - it('cancels out other deletes', () => assert.deepEqual(type.transform([{p:['k', 5], sd:'a'}], [{p:['k', 5], sd:'a'}], 'left'), [])); - - return it('does not throw errors with blank inserts', () => assert.deepEqual(type.transform([{p: ['k', 5], si:''}], [{p: ['k', 3], si: 'a'}], 'left'), [])); + it('splits deletes', () => + assert.deepEqual( + type.transform([{ p: [0], sd: 'ab' }], [{ p: [1], si: 'x' }], 'left'), + [{ p: [0], sd: 'a' }, { p: [1], sd: 'b' }] + )); + + it('cancels out other deletes', () => + assert.deepEqual( + type.transform( + [{ p: ['k', 5], sd: 'a' }], + [{ p: ['k', 5], sd: 'a' }], + 'left' + ), + [] + )); + + return it('does not throw errors with blank inserts', () => + assert.deepEqual( + type.transform( + [{ p: ['k', 5], si: '' }], + [{ p: ['k', 3], si: 'a' }], + 'left' + ), + [] + )); + }); }); -}); describe('string subtype', function() { describe('#apply()', () => it('works', function() { - assert.deepEqual('abc', type.apply('a', [{p:[], t:'text0', o:[{p:1, i:'bc'}]}])); - assert.deepEqual('bc', type.apply('abc', [{p:[], t:'text0', o:[{p:0, d:'a'}]}])); - return assert.deepEqual({x:'abc'}, type.apply({x:'a'}, [{p:['x'], t:'text0', o:[{p:1, i:'bc'}]}])); - }) - ); + assert.deepEqual( + 'abc', + type.apply('a', [{ p: [], t: 'text0', o: [{ p: 1, i: 'bc' }] }]) + ); + assert.deepEqual( + 'bc', + type.apply('abc', [{ p: [], t: 'text0', o: [{ p: 0, d: 'a' }] }]) + ); + return assert.deepEqual( + { x: 'abc' }, + type.apply({ x: 'a' }, [ + { p: ['x'], t: 'text0', o: [{ p: 1, i: 'bc' }] } + ]) + ); + })); return describe('#transform()', function() { it('splits deletes', function() { - const a = [{p:[], t:'text0', o:[{p:0, d:'ab'}]}]; - const b = [{p:[], t:'text0', o:[{p:1, i:'x'}]}]; - return assert.deepEqual(type.transform(a, b, 'left'), [{p:[], t:'text0', o:[{p:0, d:'a'}, {p:1, d:'b'}]}]); - }); - - it('cancels out other deletes', () => assert.deepEqual(type.transform([{p:['k'], t:'text0', o:[{p:5, d:'a'}]}], [{p:['k'], t:'text0', o:[{p:5, d:'a'}]}], 'left'), [])); + const a = [{ p: [], t: 'text0', o: [{ p: 0, d: 'ab' }] }]; + const b = [{ p: [], t: 'text0', o: [{ p: 1, i: 'x' }] }]; + return assert.deepEqual(type.transform(a, b, 'left'), [ + { p: [], t: 'text0', o: [{ p: 0, d: 'a' }, { p: 1, d: 'b' }] } + ]); + }); - return it('does not throw errors with blank inserts', () => assert.deepEqual(type.transform([{p:['k'], t:'text0', o:[{p:5, i:''}]}], [{p:['k'], t:'text0', o:[{p:3, i:'a'}]}], 'left'), [])); + it('cancels out other deletes', () => + assert.deepEqual( + type.transform( + [{ p: ['k'], t: 'text0', o: [{ p: 5, d: 'a' }] }], + [{ p: ['k'], t: 'text0', o: [{ p: 5, d: 'a' }] }], + 'left' + ), + [] + )); + + return it('does not throw errors with blank inserts', () => + assert.deepEqual( + type.transform( + [{ p: ['k'], t: 'text0', o: [{ p: 5, i: '' }] }], + [{ p: ['k'], t: 'text0', o: [{ p: 3, i: 'a' }] }], + 'left' + ), + [] + )); + }); }); -}); describe('subtype with non-array operation', () => describe('#transform()', () => it('works', function() { - const a = [{p:[], t:'mock', o:'foo'}]; - const b = [{p:[], t:'mock', o:'bar'}]; - return assert.deepEqual(type.transform(a, b, 'left'), [{p:[], t:'mock', o:{mock:true}}]); - }) - ) -); + const a = [{ p: [], t: 'mock', o: 'foo' }]; + const b = [{ p: [], t: 'mock', o: 'bar' }]; + return assert.deepEqual(type.transform(a, b, 'left'), [ + { p: [], t: 'mock', o: { mock: true } } + ]); + }))); describe('list', function() { describe('apply', function() { it('inserts', function() { - assert.deepEqual(['a', 'b', 'c'], type.apply(['b', 'c'], [{p:[0], li:'a'}])); - assert.deepEqual(['a', 'b', 'c'], type.apply(['a', 'c'], [{p:[1], li:'b'}])); - return assert.deepEqual(['a', 'b', 'c'], type.apply(['a', 'b'], [{p:[2], li:'c'}])); - }); + assert.deepEqual( + ['a', 'b', 'c'], + type.apply(['b', 'c'], [{ p: [0], li: 'a' }]) + ); + assert.deepEqual( + ['a', 'b', 'c'], + type.apply(['a', 'c'], [{ p: [1], li: 'b' }]) + ); + return assert.deepEqual( + ['a', 'b', 'c'], + type.apply(['a', 'b'], [{ p: [2], li: 'c' }]) + ); + }); it('deletes', function() { - assert.deepEqual(['b', 'c'], type.apply(['a', 'b', 'c'], [{p:[0], ld:'a'}])); - assert.deepEqual(['a', 'c'], type.apply(['a', 'b', 'c'], [{p:[1], ld:'b'}])); - return assert.deepEqual(['a', 'b'], type.apply(['a', 'b', 'c'], [{p:[2], ld:'c'}])); - }); + assert.deepEqual( + ['b', 'c'], + type.apply(['a', 'b', 'c'], [{ p: [0], ld: 'a' }]) + ); + assert.deepEqual( + ['a', 'c'], + type.apply(['a', 'b', 'c'], [{ p: [1], ld: 'b' }]) + ); + return assert.deepEqual( + ['a', 'b'], + type.apply(['a', 'b', 'c'], [{ p: [2], ld: 'c' }]) + ); + }); - it('replaces', () => assert.deepEqual(['a', 'y', 'b'], type.apply(['a', 'x', 'b'], [{p:[1], ld:'x', li:'y'}]))); + it('replaces', () => + assert.deepEqual( + ['a', 'y', 'b'], + type.apply(['a', 'x', 'b'], [{ p: [1], ld: 'x', li: 'y' }]) + )); return it('moves', function() { - assert.deepEqual(['a', 'b', 'c'], type.apply(['b', 'a', 'c'], [{p:[1], lm:0}])); - return assert.deepEqual(['a', 'b', 'c'], type.apply(['b', 'a', 'c'], [{p:[0], lm:1}])); - }); + assert.deepEqual( + ['a', 'b', 'c'], + type.apply(['b', 'a', 'c'], [{ p: [1], lm: 0 }]) + ); + return assert.deepEqual( + ['a', 'b', 'c'], + type.apply(['b', 'a', 'c'], [{ p: [0], lm: 1 }]) + ); + }); /* 'null moves compose to nops', -> @@ -159,268 +278,855 @@ const genTests = function(type) { describe('#transform()', function() { it('bumps paths when list elements are inserted or removed', function() { - assert.deepEqual([{p:[2, 200], si:'hi'}], type.transform([{p:[1, 200], si:'hi'}], [{p:[0], li:'x'}], 'left')); - assert.deepEqual([{p:[1, 201], si:'hi'}], type.transform([{p:[0, 201], si:'hi'}], [{p:[0], li:'x'}], 'right')); - assert.deepEqual([{p:[0, 202], si:'hi'}], type.transform([{p:[0, 202], si:'hi'}], [{p:[1], li:'x'}], 'left')); - assert.deepEqual([{p:[2], t:'text0', o:[{p:200, i:'hi'}]}], type.transform([{p:[1], t:'text0', o:[{p:200, i:'hi'}]}], [{p:[0], li:'x'}], 'left')); - assert.deepEqual([{p:[1], t:'text0', o:[{p:201, i:'hi'}]}], type.transform([{p:[0], t:'text0', o:[{p:201, i:'hi'}]}], [{p:[0], li:'x'}], 'right')); - assert.deepEqual([{p:[0], t:'text0', o:[{p:202, i:'hi'}]}], type.transform([{p:[0], t:'text0', o:[{p:202, i:'hi'}]}], [{p:[1], li:'x'}], 'left')); - - assert.deepEqual([{p:[0, 203], si:'hi'}], type.transform([{p:[1, 203], si:'hi'}], [{p:[0], ld:'x'}], 'left')); - assert.deepEqual([{p:[0, 204], si:'hi'}], type.transform([{p:[0, 204], si:'hi'}], [{p:[1], ld:'x'}], 'left')); - assert.deepEqual([{p:['x',3], si: 'hi'}], type.transform([{p:['x',3], si:'hi'}], [{p:['x',0,'x'], li:0}], 'left')); - assert.deepEqual([{p:['x',3,'x'], si: 'hi'}], type.transform([{p:['x',3,'x'], si:'hi'}], [{p:['x',5], li:0}], 'left')); - assert.deepEqual([{p:['x',4,'x'], si: 'hi'}], type.transform([{p:['x',3,'x'], si:'hi'}], [{p:['x',0], li:0}], 'left')); - assert.deepEqual([{p:[0], t:'text0', o:[{p:203, i:'hi'}]}], type.transform([{p:[1], t:'text0', o:[{p:203, i:'hi'}]}], [{p:[0], ld:'x'}], 'left')); - assert.deepEqual([{p:[0], t:'text0', o:[{p:204, i:'hi'}]}], type.transform([{p:[0], t:'text0', o:[{p:204, i:'hi'}]}], [{p:[1], ld:'x'}], 'left')); - assert.deepEqual([{p:['x'], t:'text0', o:[{p:3,i: 'hi'}]}], type.transform([{p:['x'], t:'text0', o:[{p:3, i:'hi'}]}], [{p:['x',0,'x'], li:0}], 'left')); - - assert.deepEqual([{p:[1],ld:2}], type.transform([{p:[0],ld:2}], [{p:[0],li:1}], 'left')); - return assert.deepEqual([{p:[1],ld:2}], type.transform([{p:[0],ld:2}], [{p:[0],li:1}], 'right')); + assert.deepEqual( + [{ p: [2, 200], si: 'hi' }], + type.transform( + [{ p: [1, 200], si: 'hi' }], + [{ p: [0], li: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [1, 201], si: 'hi' }], + type.transform( + [{ p: [0, 201], si: 'hi' }], + [{ p: [0], li: 'x' }], + 'right' + ) + ); + assert.deepEqual( + [{ p: [0, 202], si: 'hi' }], + type.transform( + [{ p: [0, 202], si: 'hi' }], + [{ p: [1], li: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [2], t: 'text0', o: [{ p: 200, i: 'hi' }] }], + type.transform( + [{ p: [1], t: 'text0', o: [{ p: 200, i: 'hi' }] }], + [{ p: [0], li: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [1], t: 'text0', o: [{ p: 201, i: 'hi' }] }], + type.transform( + [{ p: [0], t: 'text0', o: [{ p: 201, i: 'hi' }] }], + [{ p: [0], li: 'x' }], + 'right' + ) + ); + assert.deepEqual( + [{ p: [0], t: 'text0', o: [{ p: 202, i: 'hi' }] }], + type.transform( + [{ p: [0], t: 'text0', o: [{ p: 202, i: 'hi' }] }], + [{ p: [1], li: 'x' }], + 'left' + ) + ); + + assert.deepEqual( + [{ p: [0, 203], si: 'hi' }], + type.transform( + [{ p: [1, 203], si: 'hi' }], + [{ p: [0], ld: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [0, 204], si: 'hi' }], + type.transform( + [{ p: [0, 204], si: 'hi' }], + [{ p: [1], ld: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: ['x', 3], si: 'hi' }], + type.transform( + [{ p: ['x', 3], si: 'hi' }], + [{ p: ['x', 0, 'x'], li: 0 }], + 'left' + ) + ); + assert.deepEqual( + [{ p: ['x', 3, 'x'], si: 'hi' }], + type.transform( + [{ p: ['x', 3, 'x'], si: 'hi' }], + [{ p: ['x', 5], li: 0 }], + 'left' + ) + ); + assert.deepEqual( + [{ p: ['x', 4, 'x'], si: 'hi' }], + type.transform( + [{ p: ['x', 3, 'x'], si: 'hi' }], + [{ p: ['x', 0], li: 0 }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [0], t: 'text0', o: [{ p: 203, i: 'hi' }] }], + type.transform( + [{ p: [1], t: 'text0', o: [{ p: 203, i: 'hi' }] }], + [{ p: [0], ld: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [0], t: 'text0', o: [{ p: 204, i: 'hi' }] }], + type.transform( + [{ p: [0], t: 'text0', o: [{ p: 204, i: 'hi' }] }], + [{ p: [1], ld: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: ['x'], t: 'text0', o: [{ p: 3, i: 'hi' }] }], + type.transform( + [{ p: ['x'], t: 'text0', o: [{ p: 3, i: 'hi' }] }], + [{ p: ['x', 0, 'x'], li: 0 }], + 'left' + ) + ); + + assert.deepEqual( + [{ p: [1], ld: 2 }], + type.transform([{ p: [0], ld: 2 }], [{ p: [0], li: 1 }], 'left') + ); + return assert.deepEqual( + [{ p: [1], ld: 2 }], + type.transform([{ p: [0], ld: 2 }], [{ p: [0], li: 1 }], 'right') + ); }); it('converts ops on deleted elements to noops', function() { - assert.deepEqual([], type.transform([{p:[1, 0], si:'hi'}], [{p:[1], ld:'x'}], 'left')); - assert.deepEqual([], type.transform([{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], ld:'x'}], 'left')); - assert.deepEqual([{p:[0],li:'x'}], type.transform([{p:[0],li:'x'}], [{p:[0],ld:'y'}], 'left')); - return assert.deepEqual([], type.transform([{p:[0],na:-3}], [{p:[0],ld:48}], 'left')); + assert.deepEqual( + [], + type.transform( + [{ p: [1, 0], si: 'hi' }], + [{ p: [1], ld: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [], + type.transform( + [{ p: [1], t: 'text0', o: [{ p: 0, i: 'hi' }] }], + [{ p: [1], ld: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [0], li: 'x' }], + type.transform([{ p: [0], li: 'x' }], [{ p: [0], ld: 'y' }], 'left') + ); + return assert.deepEqual( + [], + type.transform([{ p: [0], na: -3 }], [{ p: [0], ld: 48 }], 'left') + ); }); it('converts ops on replaced elements to noops', function() { - assert.deepEqual([], type.transform([{p:[1, 0], si:'hi'}], [{p:[1], ld:'x', li:'y'}], 'left')); - assert.deepEqual([], type.transform([{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], ld:'x', li:'y'}], 'left')); - return assert.deepEqual([{p:[0], li:'hi'}], type.transform([{p:[0], li:'hi'}], [{p:[0], ld:'x', li:'y'}], 'left')); + assert.deepEqual( + [], + type.transform( + [{ p: [1, 0], si: 'hi' }], + [{ p: [1], ld: 'x', li: 'y' }], + 'left' + ) + ); + assert.deepEqual( + [], + type.transform( + [{ p: [1], t: 'text0', o: [{ p: 0, i: 'hi' }] }], + [{ p: [1], ld: 'x', li: 'y' }], + 'left' + ) + ); + return assert.deepEqual( + [{ p: [0], li: 'hi' }], + type.transform( + [{ p: [0], li: 'hi' }], + [{ p: [0], ld: 'x', li: 'y' }], + 'left' + ) + ); }); it('changes deleted data to reflect edits', function() { - assert.deepEqual([{p:[1], ld:'abc'}], type.transform([{p:[1], ld:'a'}], [{p:[1, 1], si:'bc'}], 'left')); - return assert.deepEqual([{p:[1], ld:'abc'}], type.transform([{p:[1], ld:'a'}], [{p:[1], t:'text0', o:[{p:1, i:'bc'}]}], 'left')); + assert.deepEqual( + [{ p: [1], ld: 'abc' }], + type.transform( + [{ p: [1], ld: 'a' }], + [{ p: [1, 1], si: 'bc' }], + 'left' + ) + ); + return assert.deepEqual( + [{ p: [1], ld: 'abc' }], + type.transform( + [{ p: [1], ld: 'a' }], + [{ p: [1], t: 'text0', o: [{ p: 1, i: 'bc' }] }], + 'left' + ) + ); }); it('Puts the left op first if two inserts are simultaneous', function() { - assert.deepEqual([{p:[1], li:'a'}], type.transform([{p:[1], li:'a'}], [{p:[1], li:'b'}], 'left')); - return assert.deepEqual([{p:[2], li:'b'}], type.transform([{p:[1], li:'b'}], [{p:[1], li:'a'}], 'right')); + assert.deepEqual( + [{ p: [1], li: 'a' }], + type.transform([{ p: [1], li: 'a' }], [{ p: [1], li: 'b' }], 'left') + ); + return assert.deepEqual( + [{ p: [2], li: 'b' }], + type.transform([{ p: [1], li: 'b' }], [{ p: [1], li: 'a' }], 'right') + ); }); return it('converts an attempt to re-delete a list element into a no-op', function() { - assert.deepEqual([], type.transform([{p:[1], ld:'x'}], [{p:[1], ld:'x'}], 'left')); - return assert.deepEqual([], type.transform([{p:[1], ld:'x'}], [{p:[1], ld:'x'}], 'right')); + assert.deepEqual( + [], + type.transform([{ p: [1], ld: 'x' }], [{ p: [1], ld: 'x' }], 'left') + ); + return assert.deepEqual( + [], + type.transform([{ p: [1], ld: 'x' }], [{ p: [1], ld: 'x' }], 'right') + ); }); }); - describe('#compose()', function() { it('composes insert then delete into a no-op', function() { - assert.deepEqual([], type.compose([{p:[1], li:'abc'}], [{p:[1], ld:'abc'}])); - return assert.deepEqual([{p:[1],ld:null,li:'x'}], type.transform([{p:[0],ld:null,li:"x"}], [{p:[0],li:"The"}], 'right')); + assert.deepEqual( + [], + type.compose( + [{ p: [1], li: 'abc' }], + [{ p: [1], ld: 'abc' }] + ) + ); + return assert.deepEqual( + [{ p: [1], ld: null, li: 'x' }], + type.transform( + [{ p: [0], ld: null, li: 'x' }], + [{ p: [0], li: 'The' }], + 'right' + ) + ); }); - it('doesn\'t change the original object', function() { - const a = [{p:[0],ld:'abc',li:null}]; - assert.deepEqual([{p:[0],ld:'abc'}], type.compose(a, [{p:[0],ld:null}])); - return assert.deepEqual([{p:[0],ld:'abc',li:null}], a); + it("doesn't change the original object", function() { + const a = [{ p: [0], ld: 'abc', li: null }]; + assert.deepEqual( + [{ p: [0], ld: 'abc' }], + type.compose( + a, + [{ p: [0], ld: null }] + ) + ); + return assert.deepEqual([{ p: [0], ld: 'abc', li: null }], a); }); return it('composes together adjacent string ops', function() { - assert.deepEqual([{p:[100], si:'hi'}], type.compose([{p:[100], si:'h'}], [{p:[101], si:'i'}])); - return assert.deepEqual([{p:[], t:'text0', o:[{p:100, i:'hi'}]}], type.compose([{p:[], t:'text0', o:[{p:100, i:'h'}]}], [{p:[], t:'text0', o:[{p:101, i:'i'}]}])); + assert.deepEqual( + [{ p: [100], si: 'hi' }], + type.compose( + [{ p: [100], si: 'h' }], + [{ p: [101], si: 'i' }] + ) + ); + return assert.deepEqual( + [{ p: [], t: 'text0', o: [{ p: 100, i: 'hi' }] }], + type.compose( + [{ p: [], t: 'text0', o: [{ p: 100, i: 'h' }] }], + [{ p: [], t: 'text0', o: [{ p: 101, i: 'i' }] }] + ) + ); + }); }); - }); it('moves ops on a moved element with the element', function() { - assert.deepEqual([{p:[10], ld:'x'}], type.transform([{p:[4], ld:'x'}], [{p:[4], lm:10}], 'left')); - assert.deepEqual([{p:[10, 1], si:'a'}], type.transform([{p:[4, 1], si:'a'}], [{p:[4], lm:10}], 'left')); - assert.deepEqual([{p:[10], t:'text0', o:[{p:1, i:'a'}]}], type.transform([{p:[4], t:'text0', o:[{p:1, i:'a'}]}], [{p:[4], lm:10}], 'left')); - assert.deepEqual([{p:[10, 1], li:'a'}], type.transform([{p:[4, 1], li:'a'}], [{p:[4], lm:10}], 'left')); - assert.deepEqual([{p:[10, 1], ld:'b', li:'a'}], type.transform([{p:[4, 1], ld:'b', li:'a'}], [{p:[4], lm:10}], 'left')); - - assert.deepEqual([{p:[0],li:null}], type.transform([{p:[0],li:null}], [{p:[0],lm:1}], 'left')); + assert.deepEqual( + [{ p: [10], ld: 'x' }], + type.transform([{ p: [4], ld: 'x' }], [{ p: [4], lm: 10 }], 'left') + ); + assert.deepEqual( + [{ p: [10, 1], si: 'a' }], + type.transform([{ p: [4, 1], si: 'a' }], [{ p: [4], lm: 10 }], 'left') + ); + assert.deepEqual( + [{ p: [10], t: 'text0', o: [{ p: 1, i: 'a' }] }], + type.transform( + [{ p: [4], t: 'text0', o: [{ p: 1, i: 'a' }] }], + [{ p: [4], lm: 10 }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [10, 1], li: 'a' }], + type.transform([{ p: [4, 1], li: 'a' }], [{ p: [4], lm: 10 }], 'left') + ); + assert.deepEqual( + [{ p: [10, 1], ld: 'b', li: 'a' }], + type.transform( + [{ p: [4, 1], ld: 'b', li: 'a' }], + [{ p: [4], lm: 10 }], + 'left' + ) + ); + + assert.deepEqual( + [{ p: [0], li: null }], + type.transform([{ p: [0], li: null }], [{ p: [0], lm: 1 }], 'left') + ); // [_,_,_,_,5,6,7,_] // c: [_,_,_,_,5,'x',6,7,_] p:5 li:'x' // s: [_,6,_,_,_,5,7,_] p:5 lm:1 // correct: [_,6,_,_,_,5,'x',7,_] - assert.deepEqual([{p:[6],li:'x'}], type.transform([{p:[5],li:'x'}], [{p:[5],lm:1}], 'left')); + assert.deepEqual( + [{ p: [6], li: 'x' }], + type.transform([{ p: [5], li: 'x' }], [{ p: [5], lm: 1 }], 'left') + ); // [_,_,_,_,5,6,7,_] // c: [_,_,_,_,5,6,7,_] p:5 ld:6 // s: [_,6,_,_,_,5,7,_] p:5 lm:1 // correct: [_,_,_,_,5,7,_] - assert.deepEqual([{p:[1],ld:6}], type.transform([{p:[5],ld:6}], [{p:[5],lm:1}], 'left')); + assert.deepEqual( + [{ p: [1], ld: 6 }], + type.transform([{ p: [5], ld: 6 }], [{ p: [5], lm: 1 }], 'left') + ); //assert.deepEqual [{p:[0],li:{}}], type.transform [{p:[0],li:{}}], [{p:[0],lm:0}], 'right' - assert.deepEqual([{p:[0],li:[]}], type.transform([{p:[0],li:[]}], [{p:[1],lm:0}], 'left')); - return assert.deepEqual([{p:[2],li:'x'}], type.transform([{p:[2],li:'x'}], [{p:[0],lm:1}], 'left')); + assert.deepEqual( + [{ p: [0], li: [] }], + type.transform([{ p: [0], li: [] }], [{ p: [1], lm: 0 }], 'left') + ); + return assert.deepEqual( + [{ p: [2], li: 'x' }], + type.transform([{ p: [2], li: 'x' }], [{ p: [0], lm: 1 }], 'left') + ); }); it('moves target index on ld/li', function() { - assert.deepEqual([{p:[0],lm:1}], type.transform([{p:[0], lm: 2}], [{p:[1], ld:'x'}], 'left')); - assert.deepEqual([{p:[1],lm:3}], type.transform([{p:[2], lm: 4}], [{p:[1], ld:'x'}], 'left')); - assert.deepEqual([{p:[0],lm:3}], type.transform([{p:[0], lm: 2}], [{p:[1], li:'x'}], 'left')); - assert.deepEqual([{p:[3],lm:5}], type.transform([{p:[2], lm: 4}], [{p:[1], li:'x'}], 'left')); - return assert.deepEqual([{p:[1],lm:1}], type.transform([{p:[0], lm: 0}], [{p:[0], li:28}], 'left')); + assert.deepEqual( + [{ p: [0], lm: 1 }], + type.transform([{ p: [0], lm: 2 }], [{ p: [1], ld: 'x' }], 'left') + ); + assert.deepEqual( + [{ p: [1], lm: 3 }], + type.transform([{ p: [2], lm: 4 }], [{ p: [1], ld: 'x' }], 'left') + ); + assert.deepEqual( + [{ p: [0], lm: 3 }], + type.transform([{ p: [0], lm: 2 }], [{ p: [1], li: 'x' }], 'left') + ); + assert.deepEqual( + [{ p: [3], lm: 5 }], + type.transform([{ p: [2], lm: 4 }], [{ p: [1], li: 'x' }], 'left') + ); + return assert.deepEqual( + [{ p: [1], lm: 1 }], + type.transform([{ p: [0], lm: 0 }], [{ p: [0], li: 28 }], 'left') + ); }); it('tiebreaks lm vs. ld/li', function() { - assert.deepEqual([], type.transform([{p:[0], lm: 2}], [{p:[0], ld:'x'}], 'left')); - assert.deepEqual([], type.transform([{p:[0], lm: 2}], [{p:[0], ld:'x'}], 'right')); - assert.deepEqual([{p:[1], lm:3}], type.transform([{p:[0], lm: 2}], [{p:[0], li:'x'}], 'left')); - return assert.deepEqual([{p:[1], lm:3}], type.transform([{p:[0], lm: 2}], [{p:[0], li:'x'}], 'right')); + assert.deepEqual( + [], + type.transform([{ p: [0], lm: 2 }], [{ p: [0], ld: 'x' }], 'left') + ); + assert.deepEqual( + [], + type.transform([{ p: [0], lm: 2 }], [{ p: [0], ld: 'x' }], 'right') + ); + assert.deepEqual( + [{ p: [1], lm: 3 }], + type.transform([{ p: [0], lm: 2 }], [{ p: [0], li: 'x' }], 'left') + ); + return assert.deepEqual( + [{ p: [1], lm: 3 }], + type.transform([{ p: [0], lm: 2 }], [{ p: [0], li: 'x' }], 'right') + ); }); - it('replacement vs. deletion', () => assert.deepEqual([{p:[0],li:'y'}], type.transform([{p:[0],ld:'x',li:'y'}], [{p:[0],ld:'x'}], 'right'))); - - it('replacement vs. insertion', () => assert.deepEqual([{p:[1],ld:{},li:"brillig"}], type.transform([{p:[0],ld:{},li:"brillig"}], [{p:[0],li:36}], 'left'))); + it('replacement vs. deletion', () => + assert.deepEqual( + [{ p: [0], li: 'y' }], + type.transform( + [{ p: [0], ld: 'x', li: 'y' }], + [{ p: [0], ld: 'x' }], + 'right' + ) + )); + + it('replacement vs. insertion', () => + assert.deepEqual( + [{ p: [1], ld: {}, li: 'brillig' }], + type.transform( + [{ p: [0], ld: {}, li: 'brillig' }], + [{ p: [0], li: 36 }], + 'left' + ) + )); it('replacement vs. replacement', function() { - assert.deepEqual([], type.transform([{p:[0],ld:null,li:[]}], [{p:[0],ld:null,li:0}], 'right')); - return assert.deepEqual([{p:[0],ld:[],li:0}], type.transform([{p:[0],ld:null,li:0}], [{p:[0],ld:null,li:[]}], 'left')); + assert.deepEqual( + [], + type.transform( + [{ p: [0], ld: null, li: [] }], + [{ p: [0], ld: null, li: 0 }], + 'right' + ) + ); + return assert.deepEqual( + [{ p: [0], ld: [], li: 0 }], + type.transform( + [{ p: [0], ld: null, li: 0 }], + [{ p: [0], ld: null, li: [] }], + 'left' + ) + ); }); - it('composes replace with delete of replaced element results in insert', () => assert.deepEqual([{p:[2],ld:[]}], type.compose([{p:[2],ld:[],li:null}], [{p:[2],ld:null}]))); + it('composes replace with delete of replaced element results in insert', () => + assert.deepEqual( + [{ p: [2], ld: [] }], + type.compose( + [{ p: [2], ld: [], li: null }], + [{ p: [2], ld: null }] + ) + )); it('lm vs lm', function() { - assert.deepEqual([{p:[0],lm:2}], type.transform([{p:[0],lm:2}], [{p:[2],lm:1}], 'left')); - assert.deepEqual([{p:[4],lm:4}], type.transform([{p:[3],lm:3}], [{p:[5],lm:0}], 'left')); - assert.deepEqual([{p:[2],lm:0}], type.transform([{p:[2],lm:0}], [{p:[1],lm:0}], 'left')); - assert.deepEqual([{p:[2],lm:1}], type.transform([{p:[2],lm:0}], [{p:[1],lm:0}], 'right')); - assert.deepEqual([{p:[3],lm:1}], type.transform([{p:[2],lm:0}], [{p:[5],lm:0}], 'right')); - assert.deepEqual([{p:[3],lm:0}], type.transform([{p:[2],lm:0}], [{p:[5],lm:0}], 'left')); - assert.deepEqual([{p:[0],lm:5}], type.transform([{p:[2],lm:5}], [{p:[2],lm:0}], 'left')); - assert.deepEqual([{p:[0],lm:5}], type.transform([{p:[2],lm:5}], [{p:[2],lm:0}], 'left')); - assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[1],lm:0}], [{p:[0],lm:5}], 'right')); - assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[1],lm:0}], [{p:[0],lm:1}], 'right')); - assert.deepEqual([{p:[1],lm:1}], type.transform([{p:[0],lm:1}], [{p:[1],lm:0}], 'left')); - assert.deepEqual([{p:[1],lm:2}], type.transform([{p:[0],lm:1}], [{p:[5],lm:0}], 'right')); - assert.deepEqual([{p:[3],lm:2}], type.transform([{p:[2],lm:1}], [{p:[5],lm:0}], 'right')); - assert.deepEqual([{p:[2],lm:1}], type.transform([{p:[3],lm:1}], [{p:[1],lm:3}], 'left')); - assert.deepEqual([{p:[2],lm:3}], type.transform([{p:[1],lm:3}], [{p:[3],lm:1}], 'left')); - assert.deepEqual([{p:[2],lm:6}], type.transform([{p:[2],lm:6}], [{p:[0],lm:1}], 'left')); - assert.deepEqual([{p:[2],lm:6}], type.transform([{p:[2],lm:6}], [{p:[0],lm:1}], 'right')); - assert.deepEqual([{p:[2],lm:6}], type.transform([{p:[2],lm:6}], [{p:[1],lm:0}], 'left')); - assert.deepEqual([{p:[2],lm:6}], type.transform([{p:[2],lm:6}], [{p:[1],lm:0}], 'right')); - assert.deepEqual([{p:[0],lm:2}], type.transform([{p:[0],lm:1}], [{p:[2],lm:1}], 'left')); - assert.deepEqual([{p:[2],lm:0}], type.transform([{p:[2],lm:1}], [{p:[0],lm:1}], 'right')); - assert.deepEqual([{p:[1],lm:1}], type.transform([{p:[0],lm:0}], [{p:[1],lm:0}], 'left')); - assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[0],lm:1}], [{p:[1],lm:3}], 'left')); - assert.deepEqual([{p:[3],lm:1}], type.transform([{p:[2],lm:1}], [{p:[3],lm:2}], 'left')); - return assert.deepEqual([{p:[3],lm:3}], type.transform([{p:[3],lm:2}], [{p:[2],lm:1}], 'left')); + assert.deepEqual( + [{ p: [0], lm: 2 }], + type.transform([{ p: [0], lm: 2 }], [{ p: [2], lm: 1 }], 'left') + ); + assert.deepEqual( + [{ p: [4], lm: 4 }], + type.transform([{ p: [3], lm: 3 }], [{ p: [5], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [2], lm: 0 }], + type.transform([{ p: [2], lm: 0 }], [{ p: [1], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [2], lm: 1 }], + type.transform([{ p: [2], lm: 0 }], [{ p: [1], lm: 0 }], 'right') + ); + assert.deepEqual( + [{ p: [3], lm: 1 }], + type.transform([{ p: [2], lm: 0 }], [{ p: [5], lm: 0 }], 'right') + ); + assert.deepEqual( + [{ p: [3], lm: 0 }], + type.transform([{ p: [2], lm: 0 }], [{ p: [5], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [0], lm: 5 }], + type.transform([{ p: [2], lm: 5 }], [{ p: [2], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [0], lm: 5 }], + type.transform([{ p: [2], lm: 5 }], [{ p: [2], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [0], lm: 0 }], + type.transform([{ p: [1], lm: 0 }], [{ p: [0], lm: 5 }], 'right') + ); + assert.deepEqual( + [{ p: [0], lm: 0 }], + type.transform([{ p: [1], lm: 0 }], [{ p: [0], lm: 1 }], 'right') + ); + assert.deepEqual( + [{ p: [1], lm: 1 }], + type.transform([{ p: [0], lm: 1 }], [{ p: [1], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [1], lm: 2 }], + type.transform([{ p: [0], lm: 1 }], [{ p: [5], lm: 0 }], 'right') + ); + assert.deepEqual( + [{ p: [3], lm: 2 }], + type.transform([{ p: [2], lm: 1 }], [{ p: [5], lm: 0 }], 'right') + ); + assert.deepEqual( + [{ p: [2], lm: 1 }], + type.transform([{ p: [3], lm: 1 }], [{ p: [1], lm: 3 }], 'left') + ); + assert.deepEqual( + [{ p: [2], lm: 3 }], + type.transform([{ p: [1], lm: 3 }], [{ p: [3], lm: 1 }], 'left') + ); + assert.deepEqual( + [{ p: [2], lm: 6 }], + type.transform([{ p: [2], lm: 6 }], [{ p: [0], lm: 1 }], 'left') + ); + assert.deepEqual( + [{ p: [2], lm: 6 }], + type.transform([{ p: [2], lm: 6 }], [{ p: [0], lm: 1 }], 'right') + ); + assert.deepEqual( + [{ p: [2], lm: 6 }], + type.transform([{ p: [2], lm: 6 }], [{ p: [1], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [2], lm: 6 }], + type.transform([{ p: [2], lm: 6 }], [{ p: [1], lm: 0 }], 'right') + ); + assert.deepEqual( + [{ p: [0], lm: 2 }], + type.transform([{ p: [0], lm: 1 }], [{ p: [2], lm: 1 }], 'left') + ); + assert.deepEqual( + [{ p: [2], lm: 0 }], + type.transform([{ p: [2], lm: 1 }], [{ p: [0], lm: 1 }], 'right') + ); + assert.deepEqual( + [{ p: [1], lm: 1 }], + type.transform([{ p: [0], lm: 0 }], [{ p: [1], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [0], lm: 0 }], + type.transform([{ p: [0], lm: 1 }], [{ p: [1], lm: 3 }], 'left') + ); + assert.deepEqual( + [{ p: [3], lm: 1 }], + type.transform([{ p: [2], lm: 1 }], [{ p: [3], lm: 2 }], 'left') + ); + return assert.deepEqual( + [{ p: [3], lm: 3 }], + type.transform([{ p: [3], lm: 2 }], [{ p: [2], lm: 1 }], 'left') + ); }); it('changes indices correctly around a move', function() { - assert.deepEqual([{p:[1,0],li:{}}], type.transform([{p:[0,0],li:{}}], [{p:[1],lm:0}], 'left')); - assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[1],lm:0}], [{p:[0],ld:{}}], 'left')); - assert.deepEqual([{p:[0],lm:0}], type.transform([{p:[0],lm:1}], [{p:[1],ld:{}}], 'left')); - assert.deepEqual([{p:[5],lm:0}], type.transform([{p:[6],lm:0}], [{p:[2],ld:{}}], 'left')); - assert.deepEqual([{p:[1],lm:0}], type.transform([{p:[1],lm:0}], [{p:[2],ld:{}}], 'left')); - assert.deepEqual([{p:[1],lm:1}], type.transform([{p:[2],lm:1}], [{p:[1],ld:3}], 'right')); - - assert.deepEqual([{p:[1],ld:{}}], type.transform([{p:[2],ld:{}}], [{p:[1],lm:2}], 'right')); - assert.deepEqual([{p:[2],ld:{}}], type.transform([{p:[1],ld:{}}], [{p:[2],lm:1}], 'left')); - - - assert.deepEqual([{p:[0],ld:{}}], type.transform([{p:[1],ld:{}}], [{p:[0],lm:1}], 'right')); - - assert.deepEqual([{p:[0],ld:1,li:2}], type.transform([{p:[1],ld:1,li:2}], [{p:[1],lm:0}], 'left')); - assert.deepEqual([{p:[0],ld:2,li:3}], type.transform([{p:[1],ld:2,li:3}], [{p:[0],lm:1}], 'left')); - return assert.deepEqual([{p:[1],ld:3,li:4}], type.transform([{p:[0],ld:3,li:4}], [{p:[1],lm:0}], 'left')); + assert.deepEqual( + [{ p: [1, 0], li: {} }], + type.transform([{ p: [0, 0], li: {} }], [{ p: [1], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [0], lm: 0 }], + type.transform([{ p: [1], lm: 0 }], [{ p: [0], ld: {} }], 'left') + ); + assert.deepEqual( + [{ p: [0], lm: 0 }], + type.transform([{ p: [0], lm: 1 }], [{ p: [1], ld: {} }], 'left') + ); + assert.deepEqual( + [{ p: [5], lm: 0 }], + type.transform([{ p: [6], lm: 0 }], [{ p: [2], ld: {} }], 'left') + ); + assert.deepEqual( + [{ p: [1], lm: 0 }], + type.transform([{ p: [1], lm: 0 }], [{ p: [2], ld: {} }], 'left') + ); + assert.deepEqual( + [{ p: [1], lm: 1 }], + type.transform([{ p: [2], lm: 1 }], [{ p: [1], ld: 3 }], 'right') + ); + + assert.deepEqual( + [{ p: [1], ld: {} }], + type.transform([{ p: [2], ld: {} }], [{ p: [1], lm: 2 }], 'right') + ); + assert.deepEqual( + [{ p: [2], ld: {} }], + type.transform([{ p: [1], ld: {} }], [{ p: [2], lm: 1 }], 'left') + ); + + assert.deepEqual( + [{ p: [0], ld: {} }], + type.transform([{ p: [1], ld: {} }], [{ p: [0], lm: 1 }], 'right') + ); + + assert.deepEqual( + [{ p: [0], ld: 1, li: 2 }], + type.transform([{ p: [1], ld: 1, li: 2 }], [{ p: [1], lm: 0 }], 'left') + ); + assert.deepEqual( + [{ p: [0], ld: 2, li: 3 }], + type.transform([{ p: [1], ld: 2, li: 3 }], [{ p: [0], lm: 1 }], 'left') + ); + return assert.deepEqual( + [{ p: [1], ld: 3, li: 4 }], + type.transform([{ p: [0], ld: 3, li: 4 }], [{ p: [1], lm: 0 }], 'left') + ); }); return it('li vs lm', function() { - const li = p => [{p:[p],li:[]}]; - const lm = (f,t) => [{p:[f],lm:t}]; + const li = p => [{ p: [p], li: [] }]; + const lm = (f, t) => [{ p: [f], lm: t }]; const xf = type.transform; - assert.deepEqual((li(0)), xf((li(0)), (lm(1, 3)), 'left')); - assert.deepEqual((li(1)), xf((li(1)), (lm(1, 3)), 'left')); - assert.deepEqual((li(1)), xf((li(2)), (lm(1, 3)), 'left')); - assert.deepEqual((li(2)), xf((li(3)), (lm(1, 3)), 'left')); - assert.deepEqual((li(4)), xf((li(4)), (lm(1, 3)), 'left')); - - assert.deepEqual((lm(2, 4)), xf((lm(1, 3)), (li(0)), 'right')); - assert.deepEqual((lm(2, 4)), xf((lm(1, 3)), (li(1)), 'right')); - assert.deepEqual((lm(1, 4)), xf((lm(1, 3)), (li(2)), 'right')); - assert.deepEqual((lm(1, 4)), xf((lm(1, 3)), (li(3)), 'right')); - assert.deepEqual((lm(1, 3)), xf((lm(1, 3)), (li(4)), 'right')); - - assert.deepEqual((li(0)), xf((li(0)), (lm(1, 2)), 'left')); - assert.deepEqual((li(1)), xf((li(1)), (lm(1, 2)), 'left')); - assert.deepEqual((li(1)), xf((li(2)), (lm(1, 2)), 'left')); - assert.deepEqual((li(3)), xf((li(3)), (lm(1, 2)), 'left')); - - assert.deepEqual((li(0)), xf((li(0)), (lm(3, 1)), 'left')); - assert.deepEqual((li(1)), xf((li(1)), (lm(3, 1)), 'left')); - assert.deepEqual((li(3)), xf((li(2)), (lm(3, 1)), 'left')); - assert.deepEqual((li(4)), xf((li(3)), (lm(3, 1)), 'left')); - assert.deepEqual((li(4)), xf((li(4)), (lm(3, 1)), 'left')); - - assert.deepEqual((lm(4, 2)), xf((lm(3, 1)), (li(0)), 'right')); - assert.deepEqual((lm(4, 2)), xf((lm(3, 1)), (li(1)), 'right')); - assert.deepEqual((lm(4, 1)), xf((lm(3, 1)), (li(2)), 'right')); - assert.deepEqual((lm(4, 1)), xf((lm(3, 1)), (li(3)), 'right')); - assert.deepEqual((lm(3, 1)), xf((lm(3, 1)), (li(4)), 'right')); - - assert.deepEqual((li(0)), xf((li(0)), (lm(2, 1)), 'left')); - assert.deepEqual((li(1)), xf((li(1)), (lm(2, 1)), 'left')); - assert.deepEqual((li(3)), xf((li(2)), (lm(2, 1)), 'left')); - return assert.deepEqual((li(3)), xf((li(3)), (lm(2, 1)), 'left')); + assert.deepEqual(li(0), xf(li(0), lm(1, 3), 'left')); + assert.deepEqual(li(1), xf(li(1), lm(1, 3), 'left')); + assert.deepEqual(li(1), xf(li(2), lm(1, 3), 'left')); + assert.deepEqual(li(2), xf(li(3), lm(1, 3), 'left')); + assert.deepEqual(li(4), xf(li(4), lm(1, 3), 'left')); + + assert.deepEqual(lm(2, 4), xf(lm(1, 3), li(0), 'right')); + assert.deepEqual(lm(2, 4), xf(lm(1, 3), li(1), 'right')); + assert.deepEqual(lm(1, 4), xf(lm(1, 3), li(2), 'right')); + assert.deepEqual(lm(1, 4), xf(lm(1, 3), li(3), 'right')); + assert.deepEqual(lm(1, 3), xf(lm(1, 3), li(4), 'right')); + + assert.deepEqual(li(0), xf(li(0), lm(1, 2), 'left')); + assert.deepEqual(li(1), xf(li(1), lm(1, 2), 'left')); + assert.deepEqual(li(1), xf(li(2), lm(1, 2), 'left')); + assert.deepEqual(li(3), xf(li(3), lm(1, 2), 'left')); + + assert.deepEqual(li(0), xf(li(0), lm(3, 1), 'left')); + assert.deepEqual(li(1), xf(li(1), lm(3, 1), 'left')); + assert.deepEqual(li(3), xf(li(2), lm(3, 1), 'left')); + assert.deepEqual(li(4), xf(li(3), lm(3, 1), 'left')); + assert.deepEqual(li(4), xf(li(4), lm(3, 1), 'left')); + + assert.deepEqual(lm(4, 2), xf(lm(3, 1), li(0), 'right')); + assert.deepEqual(lm(4, 2), xf(lm(3, 1), li(1), 'right')); + assert.deepEqual(lm(4, 1), xf(lm(3, 1), li(2), 'right')); + assert.deepEqual(lm(4, 1), xf(lm(3, 1), li(3), 'right')); + assert.deepEqual(lm(3, 1), xf(lm(3, 1), li(4), 'right')); + + assert.deepEqual(li(0), xf(li(0), lm(2, 1), 'left')); + assert.deepEqual(li(1), xf(li(1), lm(2, 1), 'left')); + assert.deepEqual(li(3), xf(li(2), lm(2, 1), 'left')); + return assert.deepEqual(li(3), xf(li(3), lm(2, 1), 'left')); }); }); - describe('object', function() { it('passes sanity checks', function() { - assert.deepEqual({x:'a', y:'b'}, type.apply({x:'a'}, [{p:['y'], oi:'b'}])); - assert.deepEqual({}, type.apply({x:'a'}, [{p:['x'], od:'a'}])); - return assert.deepEqual({x:'b'}, type.apply({x:'a'}, [{p:['x'], od:'a', oi:'b'}])); - }); + assert.deepEqual( + { x: 'a', y: 'b' }, + type.apply({ x: 'a' }, [{ p: ['y'], oi: 'b' }]) + ); + assert.deepEqual({}, type.apply({ x: 'a' }, [{ p: ['x'], od: 'a' }])); + return assert.deepEqual( + { x: 'b' }, + type.apply({ x: 'a' }, [{ p: ['x'], od: 'a', oi: 'b' }]) + ); + }); it('Ops on deleted elements become noops', function() { - assert.deepEqual([], type.transform([{p:[1, 0], si:'hi'}], [{p:[1], od:'x'}], 'left')); - assert.deepEqual([], type.transform([{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], od:'x'}], 'left')); - assert.deepEqual([], type.transform([{p:[9],si:"bite "}], [{p:[],od:"agimble s",oi:null}], 'right')); - return assert.deepEqual([], type.transform([{p:[], t:'text0', o:[{p:9, i:"bite "}]}], [{p:[],od:"agimble s",oi:null}], 'right')); + assert.deepEqual( + [], + type.transform([{ p: [1, 0], si: 'hi' }], [{ p: [1], od: 'x' }], 'left') + ); + assert.deepEqual( + [], + type.transform( + [{ p: [1], t: 'text0', o: [{ p: 0, i: 'hi' }] }], + [{ p: [1], od: 'x' }], + 'left' + ) + ); + assert.deepEqual( + [], + type.transform( + [{ p: [9], si: 'bite ' }], + [{ p: [], od: 'agimble s', oi: null }], + 'right' + ) + ); + return assert.deepEqual( + [], + type.transform( + [{ p: [], t: 'text0', o: [{ p: 9, i: 'bite ' }] }], + [{ p: [], od: 'agimble s', oi: null }], + 'right' + ) + ); }); it('Ops on replaced elements become noops', function() { - assert.deepEqual([], type.transform([{p:[1, 0], si:'hi'}], [{p:[1], od:'x', oi:'y'}], 'left')); - return assert.deepEqual([], type.transform([{p:[1], t:'text0', o:[{p:0, i:'hi'}]}], [{p:[1], od:'x', oi:'y'}], 'left')); + assert.deepEqual( + [], + type.transform( + [{ p: [1, 0], si: 'hi' }], + [{ p: [1], od: 'x', oi: 'y' }], + 'left' + ) + ); + return assert.deepEqual( + [], + type.transform( + [{ p: [1], t: 'text0', o: [{ p: 0, i: 'hi' }] }], + [{ p: [1], od: 'x', oi: 'y' }], + 'left' + ) + ); }); it('Deleted data is changed to reflect edits', function() { - assert.deepEqual([{p:[1], od:'abc'}], type.transform([{p:[1], od:'a'}], [{p:[1, 1], si:'bc'}], 'left')); - assert.deepEqual([{p:[1], od:'abc'}], type.transform([{p:[1], od:'a'}], [{p:[1], t:'text0', o:[{p:1, i:'bc'}]}], 'left')); - assert.deepEqual([{p:[],od:25,oi:[]}], type.transform([{p:[],od:22,oi:[]}], [{p:[],na:3}], 'left')); - assert.deepEqual([{p:[],od:{toves:""},oi:4}], type.transform([{p:[],od:{toves:0},oi:4}], [{p:["toves"],od:0,oi:""}], 'left')); - assert.deepEqual([{p:[],od:"thou an",oi:[]}], type.transform([{p:[],od:"thou and ",oi:[]}], [{p:[7],sd:"d "}], 'left')); - assert.deepEqual([{p:[],od:"thou an",oi:[]}], type.transform([{p:[],od:"thou and ",oi:[]}], [{p:[], t:'text0', o:[{p:7, d:"d "}]}], 'left')); - assert.deepEqual([], type.transform([{p:["bird"],na:2}], [{p:[],od:{bird:38},oi:20}], 'right')); - assert.deepEqual([{p:[],od:{bird:40},oi:20}], type.transform([{p:[],od:{bird:38},oi:20}], [{p:["bird"],na:2}], 'left')); - assert.deepEqual([{p:['He'],od:[]}], type.transform([{p:["He"],od:[]}], [{p:["The"],na:-3}], 'right')); - return assert.deepEqual([], type.transform([{p:["He"],oi:{}}], [{p:[],od:{},oi:"the"}], 'left')); + assert.deepEqual( + [{ p: [1], od: 'abc' }], + type.transform([{ p: [1], od: 'a' }], [{ p: [1, 1], si: 'bc' }], 'left') + ); + assert.deepEqual( + [{ p: [1], od: 'abc' }], + type.transform( + [{ p: [1], od: 'a' }], + [{ p: [1], t: 'text0', o: [{ p: 1, i: 'bc' }] }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [], od: 25, oi: [] }], + type.transform([{ p: [], od: 22, oi: [] }], [{ p: [], na: 3 }], 'left') + ); + assert.deepEqual( + [{ p: [], od: { toves: '' }, oi: 4 }], + type.transform( + [{ p: [], od: { toves: 0 }, oi: 4 }], + [{ p: ['toves'], od: 0, oi: '' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [], od: 'thou an', oi: [] }], + type.transform( + [{ p: [], od: 'thou and ', oi: [] }], + [{ p: [7], sd: 'd ' }], + 'left' + ) + ); + assert.deepEqual( + [{ p: [], od: 'thou an', oi: [] }], + type.transform( + [{ p: [], od: 'thou and ', oi: [] }], + [{ p: [], t: 'text0', o: [{ p: 7, d: 'd ' }] }], + 'left' + ) + ); + assert.deepEqual( + [], + type.transform( + [{ p: ['bird'], na: 2 }], + [{ p: [], od: { bird: 38 }, oi: 20 }], + 'right' + ) + ); + assert.deepEqual( + [{ p: [], od: { bird: 40 }, oi: 20 }], + type.transform( + [{ p: [], od: { bird: 38 }, oi: 20 }], + [{ p: ['bird'], na: 2 }], + 'left' + ) + ); + assert.deepEqual( + [{ p: ['He'], od: [] }], + type.transform( + [{ p: ['He'], od: [] }], + [{ p: ['The'], na: -3 }], + 'right' + ) + ); + return assert.deepEqual( + [], + type.transform( + [{ p: ['He'], oi: {} }], + [{ p: [], od: {}, oi: 'the' }], + 'left' + ) + ); }); it('If two inserts are simultaneous, the lefts insert will win', function() { - assert.deepEqual([{p:[1], oi:'a', od:'b'}], type.transform([{p:[1], oi:'a'}], [{p:[1], oi:'b'}], 'left')); - return assert.deepEqual([], type.transform([{p:[1], oi:'b'}], [{p:[1], oi:'a'}], 'right')); + assert.deepEqual( + [{ p: [1], oi: 'a', od: 'b' }], + type.transform([{ p: [1], oi: 'a' }], [{ p: [1], oi: 'b' }], 'left') + ); + return assert.deepEqual( + [], + type.transform([{ p: [1], oi: 'b' }], [{ p: [1], oi: 'a' }], 'right') + ); }); it('parallel ops on different keys miss each other', function() { - assert.deepEqual([{p:['a'], oi: 'x'}], type.transform([{p:['a'], oi:'x'}], [{p:['b'], oi:'z'}], 'left')); - assert.deepEqual([{p:['a'], oi: 'x'}], type.transform([{p:['a'], oi:'x'}], [{p:['b'], od:'z'}], 'left')); - assert.deepEqual([{p:["in","he"],oi:{}}], type.transform([{p:["in","he"],oi:{}}], [{p:["and"],od:{}}], 'right')); - assert.deepEqual([{p:['x',0],si:"his "}], type.transform([{p:['x',0],si:"his "}], [{p:['y'],od:0,oi:1}], 'right')); - return assert.deepEqual([{p:['x'], t:'text0', o:[{p:0, i:"his "}]}], type.transform([{p:['x'],t:'text0', o:[{p:0, i:"his "}]}], [{p:['y'],od:0,oi:1}], 'right')); + assert.deepEqual( + [{ p: ['a'], oi: 'x' }], + type.transform([{ p: ['a'], oi: 'x' }], [{ p: ['b'], oi: 'z' }], 'left') + ); + assert.deepEqual( + [{ p: ['a'], oi: 'x' }], + type.transform([{ p: ['a'], oi: 'x' }], [{ p: ['b'], od: 'z' }], 'left') + ); + assert.deepEqual( + [{ p: ['in', 'he'], oi: {} }], + type.transform( + [{ p: ['in', 'he'], oi: {} }], + [{ p: ['and'], od: {} }], + 'right' + ) + ); + assert.deepEqual( + [{ p: ['x', 0], si: 'his ' }], + type.transform( + [{ p: ['x', 0], si: 'his ' }], + [{ p: ['y'], od: 0, oi: 1 }], + 'right' + ) + ); + return assert.deepEqual( + [{ p: ['x'], t: 'text0', o: [{ p: 0, i: 'his ' }] }], + type.transform( + [{ p: ['x'], t: 'text0', o: [{ p: 0, i: 'his ' }] }], + [{ p: ['y'], od: 0, oi: 1 }], + 'right' + ) + ); }); - it('replacement vs. deletion', () => assert.deepEqual([{p:[],oi:{}}], type.transform([{p:[],od:[''],oi:{}}], [{p:[],od:['']}], 'right'))); + it('replacement vs. deletion', () => + assert.deepEqual( + [{ p: [], oi: {} }], + type.transform( + [{ p: [], od: [''], oi: {} }], + [{ p: [], od: [''] }], + 'right' + ) + )); it('replacement vs. replacement', function() { - assert.deepEqual([], type.transform([{p:[],od:['']},{p:[],oi:{}}], [{p:[],od:['']},{p:[],oi:null}], 'right')); - assert.deepEqual([{p:[],od:null,oi:{}}], type.transform([{p:[],od:['']},{p:[],oi:{}}], [{p:[],od:['']},{p:[],oi:null}], 'left')); - assert.deepEqual([], type.transform([{p:[],od:[''],oi:{}}], [{p:[],od:[''],oi:null}], 'right')); - assert.deepEqual([{p:[],od:null,oi:{}}], type.transform([{p:[],od:[''],oi:{}}], [{p:[],od:[''],oi:null}], 'left')); + assert.deepEqual( + [], + type.transform( + [{ p: [], od: [''] }, { p: [], oi: {} }], + [{ p: [], od: [''] }, { p: [], oi: null }], + 'right' + ) + ); + assert.deepEqual( + [{ p: [], od: null, oi: {} }], + type.transform( + [{ p: [], od: [''] }, { p: [], oi: {} }], + [{ p: [], od: [''] }, { p: [], oi: null }], + 'left' + ) + ); + assert.deepEqual( + [], + type.transform( + [{ p: [], od: [''], oi: {} }], + [{ p: [], od: [''], oi: null }], + 'right' + ) + ); + assert.deepEqual( + [{ p: [], od: null, oi: {} }], + type.transform( + [{ p: [], od: [''], oi: {} }], + [{ p: [], od: [''], oi: null }], + 'left' + ) + ); // test diamond property - const rightOps = [ {"p":[],"od":null,"oi":{}} ]; - const leftOps = [ {"p":[],"od":null,"oi":""} ]; + const rightOps = [{ p: [], od: null, oi: {} }]; + const leftOps = [{ p: [], od: null, oi: '' }]; const rightHas = type.apply(null, rightOps); const leftHas = type.apply(null, leftOps); @@ -429,10 +1135,19 @@ const genTests = function(type) { return assert.deepEqual(leftHas, type.apply(leftHas, right_)); }); - return it('An attempt to re-delete a key becomes a no-op', function() { - assert.deepEqual([], type.transform([{p:['k'], od:'x'}], [{p:['k'], od:'x'}], 'left')); - return assert.deepEqual([], type.transform([{p:['k'], od:'x'}], [{p:['k'], od:'x'}], 'right')); + assert.deepEqual( + [], + type.transform([{ p: ['k'], od: 'x' }], [{ p: ['k'], od: 'x' }], 'left') + ); + return assert.deepEqual( + [], + type.transform( + [{ p: ['k'], od: 'x' }], + [{ p: ['k'], od: 'x' }], + 'right' + ) + ); }); }); @@ -450,4 +1165,4 @@ const genTests = function(type) { }; describe('json', () => describe('native type', () => genTests(nativetype))); - //exports.webclient = genTests require('../helpers/webclient').types.json +//exports.webclient = genTests require('../helpers/webclient').types.json diff --git a/test/text0-generator.js b/test/text0-generator.js index e86b190..be9ff07 100644 --- a/test/text0-generator.js +++ b/test/text0-generator.js @@ -7,34 +7,37 @@ // test. let genRandomOp; -const {randomReal, randomWord} = require('ot-fuzzer'); +const { randomReal, randomWord } = require('ot-fuzzer'); const text0 = require('../lib/text0'); -module.exports = (genRandomOp = function(docStr) { +module.exports = genRandomOp = function(docStr) { let pct = 0.9; const op = []; while (randomReal() < pct) { -// console.log "docStr = #{i docStr}" + // console.log "docStr = #{i docStr}" var pos; pct /= 2; - + if (randomReal() > 0.5) { // Append an insert pos = Math.floor(randomReal() * (docStr.length + 1)); const str = randomWord() + ' '; - text0._append(op, {i:str, p:pos}); + text0._append(op, { i: str, p: pos }); docStr = docStr.slice(0, pos) + str + docStr.slice(pos); } else { // Append a delete pos = Math.floor(randomReal() * docStr.length); - const length = Math.min(Math.floor(randomReal() * 4), docStr.length - pos); - text0._append(op, {d:docStr.slice(pos, (pos + length)), p:pos}); - docStr = docStr.slice(0, pos) + docStr.slice((pos + length)); + const length = Math.min( + Math.floor(randomReal() * 4), + docStr.length - pos + ); + text0._append(op, { d: docStr.slice(pos, pos + length), p: pos }); + docStr = docStr.slice(0, pos) + docStr.slice(pos + length); } } - -// console.log "generated op #{i op} -> #{i docStr}" + + // console.log "generated op #{i op} -> #{i docStr}" return [op, docStr]; -}); +}; diff --git a/test/text0.js b/test/text0.js index 58f161c..6534039 100644 --- a/test/text0.js +++ b/test/text0.js @@ -13,133 +13,275 @@ describe('text0', function() { describe('compose', () => // Compose is actually pretty easy it('is sane', function() { - assert.deepEqual(text0.compose([], []), []); - assert.deepEqual(text0.compose([{i:'x', p:0}], []), [{i:'x', p:0}]); - assert.deepEqual(text0.compose([], [{i:'x', p:0}]), [{i:'x', p:0}]); - return assert.deepEqual(text0.compose([{i:'y', p:100}], [{i:'x', p:0}]), [{i:'y', p:100}, {i:'x', p:0}]); - }) -); + assert.deepEqual( + text0.compose( + [], + [] + ), + [] + ); + assert.deepEqual( + text0.compose( + [{ i: 'x', p: 0 }], + [] + ), + [{ i: 'x', p: 0 }] + ); + assert.deepEqual( + text0.compose( + [], + [{ i: 'x', p: 0 }] + ), + [{ i: 'x', p: 0 }] + ); + return assert.deepEqual( + text0.compose( + [{ i: 'y', p: 100 }], + [{ i: 'x', p: 0 }] + ), + [{ i: 'y', p: 100 }, { i: 'x', p: 0 }] + ); + })); describe('transform', function() { it('is sane', function() { assert.deepEqual([], text0.transform([], [], 'left')); assert.deepEqual([], text0.transform([], [], 'right')); - assert.deepEqual([{i:'y', p:100}, {i:'x', p:0}], text0.transform([{i:'y', p:100}, {i:'x', p:0}], [], 'left')); - return assert.deepEqual([], text0.transform([], [{i:'y', p:100}, {i:'x', p:0}], 'right')); + assert.deepEqual( + [{ i: 'y', p: 100 }, { i: 'x', p: 0 }], + text0.transform([{ i: 'y', p: 100 }, { i: 'x', p: 0 }], [], 'left') + ); + return assert.deepEqual( + [], + text0.transform([], [{ i: 'y', p: 100 }, { i: 'x', p: 0 }], 'right') + ); }); it('inserts', function() { - assert.deepEqual([[{i:'x', p:10}], [{i:'a', p:1}]], text0.transformX([{i:'x', p:9}], [{i:'a', p:1}])); - assert.deepEqual([[{i:'x', p:10}], [{i:'a', p:11}]], text0.transformX([{i:'x', p:10}], [{i:'a', p:10}])); + assert.deepEqual( + [[{ i: 'x', p: 10 }], [{ i: 'a', p: 1 }]], + text0.transformX([{ i: 'x', p: 9 }], [{ i: 'a', p: 1 }]) + ); + assert.deepEqual( + [[{ i: 'x', p: 10 }], [{ i: 'a', p: 11 }]], + text0.transformX([{ i: 'x', p: 10 }], [{ i: 'a', p: 10 }]) + ); - assert.deepEqual([[{i:'x', p:10}], [{d:'a', p:9}]], text0.transformX([{i:'x', p:11}], [{d:'a', p:9}])); - assert.deepEqual([[{i:'x', p:10}], [{d:'a', p:10}]], text0.transformX([{i:'x', p:11}], [{d:'a', p:10}])); - assert.deepEqual([[{i:'x', p:11}], [{d:'a', p:12}]], text0.transformX([{i:'x', p:11}], [{d:'a', p:11}])); + assert.deepEqual( + [[{ i: 'x', p: 10 }], [{ d: 'a', p: 9 }]], + text0.transformX([{ i: 'x', p: 11 }], [{ d: 'a', p: 9 }]) + ); + assert.deepEqual( + [[{ i: 'x', p: 10 }], [{ d: 'a', p: 10 }]], + text0.transformX([{ i: 'x', p: 11 }], [{ d: 'a', p: 10 }]) + ); + assert.deepEqual( + [[{ i: 'x', p: 11 }], [{ d: 'a', p: 12 }]], + text0.transformX([{ i: 'x', p: 11 }], [{ d: 'a', p: 11 }]) + ); - assert.deepEqual([{i:'x', p:10}], text0.transform([{i:'x', p:10}], [{d:'a', p:11}], 'left')); - assert.deepEqual([{i:'x', p:10}], text0.transform([{i:'x', p:10}], [{d:'a', p:10}], 'left')); - return assert.deepEqual([{i:'x', p:10}], text0.transform([{i:'x', p:10}], [{d:'a', p:10}], 'right')); + assert.deepEqual( + [{ i: 'x', p: 10 }], + text0.transform([{ i: 'x', p: 10 }], [{ d: 'a', p: 11 }], 'left') + ); + assert.deepEqual( + [{ i: 'x', p: 10 }], + text0.transform([{ i: 'x', p: 10 }], [{ d: 'a', p: 10 }], 'left') + ); + return assert.deepEqual( + [{ i: 'x', p: 10 }], + text0.transform([{ i: 'x', p: 10 }], [{ d: 'a', p: 10 }], 'right') + ); }); return it('deletes', function() { - assert.deepEqual([[{d:'abc', p:8}], [{d:'xy', p:4}]], text0.transformX([{d:'abc', p:10}], [{d:'xy', p:4}])); - assert.deepEqual([[{d:'ac', p:10}], []], text0.transformX([{d:'abc', p:10}], [{d:'b', p:11}])); - assert.deepEqual([[], [{d:'ac', p:10}]], text0.transformX([{d:'b', p:11}], [{d:'abc', p:10}])); - assert.deepEqual([[{d:'a', p:10}], []], text0.transformX([{d:'abc', p:10}], [{d:'bc', p:11}])); - assert.deepEqual([[{d:'c', p:10}], []], text0.transformX([{d:'abc', p:10}], [{d:'ab', p:10}])); - assert.deepEqual([[{d:'a', p:10}], [{d:'d', p:10}]], text0.transformX([{d:'abc', p:10}], [{d:'bcd', p:11}])); - assert.deepEqual([[{d:'d', p:10}], [{d:'a', p:10}]], text0.transformX([{d:'bcd', p:11}], [{d:'abc', p:10}])); - return assert.deepEqual([[{d:'abc', p:10}], [{d:'xy', p:10}]], text0.transformX([{d:'abc', p:10}], [{d:'xy', p:13}])); + assert.deepEqual( + [[{ d: 'abc', p: 8 }], [{ d: 'xy', p: 4 }]], + text0.transformX([{ d: 'abc', p: 10 }], [{ d: 'xy', p: 4 }]) + ); + assert.deepEqual( + [[{ d: 'ac', p: 10 }], []], + text0.transformX([{ d: 'abc', p: 10 }], [{ d: 'b', p: 11 }]) + ); + assert.deepEqual( + [[], [{ d: 'ac', p: 10 }]], + text0.transformX([{ d: 'b', p: 11 }], [{ d: 'abc', p: 10 }]) + ); + assert.deepEqual( + [[{ d: 'a', p: 10 }], []], + text0.transformX([{ d: 'abc', p: 10 }], [{ d: 'bc', p: 11 }]) + ); + assert.deepEqual( + [[{ d: 'c', p: 10 }], []], + text0.transformX([{ d: 'abc', p: 10 }], [{ d: 'ab', p: 10 }]) + ); + assert.deepEqual( + [[{ d: 'a', p: 10 }], [{ d: 'd', p: 10 }]], + text0.transformX([{ d: 'abc', p: 10 }], [{ d: 'bcd', p: 11 }]) + ); + assert.deepEqual( + [[{ d: 'd', p: 10 }], [{ d: 'a', p: 10 }]], + text0.transformX([{ d: 'bcd', p: 11 }], [{ d: 'abc', p: 10 }]) + ); + return assert.deepEqual( + [[{ d: 'abc', p: 10 }], [{ d: 'xy', p: 10 }]], + text0.transformX([{ d: 'abc', p: 10 }], [{ d: 'xy', p: 13 }]) + ); + }); }); -}); describe('transformCursor', function() { it('is sane', function() { assert.strictEqual(0, text0.transformCursor(0, [], 'right')); assert.strictEqual(0, text0.transformCursor(0, [], 'left')); return assert.strictEqual(100, text0.transformCursor(100, [])); - }); + }); it('works vs insert', function() { - assert.strictEqual(0, text0.transformCursor(0, [{i:'asdf', p:100}], 'right')); - assert.strictEqual(0, text0.transformCursor(0, [{i:'asdf', p:100}], 'left')); + assert.strictEqual( + 0, + text0.transformCursor(0, [{ i: 'asdf', p: 100 }], 'right') + ); + assert.strictEqual( + 0, + text0.transformCursor(0, [{ i: 'asdf', p: 100 }], 'left') + ); - assert.strictEqual(204, text0.transformCursor(200, [{i:'asdf', p:100}], 'right')); - assert.strictEqual(204, text0.transformCursor(200, [{i:'asdf', p:100}], 'left')); + assert.strictEqual( + 204, + text0.transformCursor(200, [{ i: 'asdf', p: 100 }], 'right') + ); + assert.strictEqual( + 204, + text0.transformCursor(200, [{ i: 'asdf', p: 100 }], 'left') + ); - assert.strictEqual(104, text0.transformCursor(100, [{i:'asdf', p:100}], 'right')); - return assert.strictEqual(100, text0.transformCursor(100, [{i:'asdf', p:100}], 'left')); + assert.strictEqual( + 104, + text0.transformCursor(100, [{ i: 'asdf', p: 100 }], 'right') + ); + return assert.strictEqual( + 100, + text0.transformCursor(100, [{ i: 'asdf', p: 100 }], 'left') + ); }); return it('works vs delete', function() { - assert.strictEqual(0, text0.transformCursor(0, [{d:'asdf', p:100}], 'right')); - assert.strictEqual(0, text0.transformCursor(0, [{d:'asdf', p:100}], 'left')); - assert.strictEqual(0, text0.transformCursor(0, [{d:'asdf', p:100}])); + assert.strictEqual( + 0, + text0.transformCursor(0, [{ d: 'asdf', p: 100 }], 'right') + ); + assert.strictEqual( + 0, + text0.transformCursor(0, [{ d: 'asdf', p: 100 }], 'left') + ); + assert.strictEqual(0, text0.transformCursor(0, [{ d: 'asdf', p: 100 }])); - assert.strictEqual(196, text0.transformCursor(200, [{d:'asdf', p:100}])); + assert.strictEqual( + 196, + text0.transformCursor(200, [{ d: 'asdf', p: 100 }]) + ); - assert.strictEqual(100, text0.transformCursor(100, [{d:'asdf', p:100}])); - assert.strictEqual(100, text0.transformCursor(102, [{d:'asdf', p:100}])); - assert.strictEqual(100, text0.transformCursor(104, [{d:'asdf', p:100}])); - return assert.strictEqual(101, text0.transformCursor(105, [{d:'asdf', p:100}])); + assert.strictEqual( + 100, + text0.transformCursor(100, [{ d: 'asdf', p: 100 }]) + ); + assert.strictEqual( + 100, + text0.transformCursor(102, [{ d: 'asdf', p: 100 }]) + ); + assert.strictEqual( + 100, + text0.transformCursor(104, [{ d: 'asdf', p: 100 }]) + ); + return assert.strictEqual( + 101, + text0.transformCursor(105, [{ d: 'asdf', p: 100 }]) + ); + }); }); -}); describe('normalize', function() { it('is sane', function() { const testUnchanged = op => assert.deepEqual(op, text0.normalize(op)); testUnchanged([]); - testUnchanged([{i:'asdf', p:100}]); - return testUnchanged([{i:'asdf', p:100}, {d:'fdsa', p:123}]); - }); + testUnchanged([{ i: 'asdf', p: 100 }]); + return testUnchanged([{ i: 'asdf', p: 100 }, { d: 'fdsa', p: 123 }]); + }); it('adds missing p:0', function() { - assert.deepEqual([{i:'abc', p:0}], text0.normalize([{i:'abc'}])); - assert.deepEqual([{d:'abc', p:0}], text0.normalize([{d:'abc'}])); - return assert.deepEqual([{i:'abc', p:0}, {d:'abc', p:0}], text0.normalize([{i:'abc'}, {d:'abc'}])); - }); + assert.deepEqual([{ i: 'abc', p: 0 }], text0.normalize([{ i: 'abc' }])); + assert.deepEqual([{ d: 'abc', p: 0 }], text0.normalize([{ d: 'abc' }])); + return assert.deepEqual( + [{ i: 'abc', p: 0 }, { d: 'abc', p: 0 }], + text0.normalize([{ i: 'abc' }, { d: 'abc' }]) + ); + }); it('converts op to an array', function() { - assert.deepEqual([{i:'abc', p:0}], text0.normalize({i:'abc', p:0})); - return assert.deepEqual([{d:'abc', p:0}], text0.normalize({d:'abc', p:0})); - }); + assert.deepEqual( + [{ i: 'abc', p: 0 }], + text0.normalize({ i: 'abc', p: 0 }) + ); + return assert.deepEqual( + [{ d: 'abc', p: 0 }], + text0.normalize({ d: 'abc', p: 0 }) + ); + }); - it('works with a really simple op', () => assert.deepEqual([{i:'abc', p:0}], text0.normalize({i:'abc'}))); + it('works with a really simple op', () => + assert.deepEqual([{ i: 'abc', p: 0 }], text0.normalize({ i: 'abc' }))); it('compress inserts', function() { - assert.deepEqual([{i:'xyzabc', p:10}], text0.normalize([{i:'abc', p:10}, {i:'xyz', p:10}])); - assert.deepEqual([{i:'axyzbc', p:10}], text0.normalize([{i:'abc', p:10}, {i:'xyz', p:11}])); - return assert.deepEqual([{i:'abcxyz', p:10}], text0.normalize([{i:'abc', p:10}, {i:'xyz', p:13}])); - }); + assert.deepEqual( + [{ i: 'xyzabc', p: 10 }], + text0.normalize([{ i: 'abc', p: 10 }, { i: 'xyz', p: 10 }]) + ); + assert.deepEqual( + [{ i: 'axyzbc', p: 10 }], + text0.normalize([{ i: 'abc', p: 10 }, { i: 'xyz', p: 11 }]) + ); + return assert.deepEqual( + [{ i: 'abcxyz', p: 10 }], + text0.normalize([{ i: 'abc', p: 10 }, { i: 'xyz', p: 13 }]) + ); + }); it('doesnt compress separate inserts', function() { const t = op => assert.deepEqual(op, text0.normalize(op)); - t([{i:'abc', p:10}, {i:'xyz', p:9}]); - return t([{i:'abc', p:10}, {i:'xyz', p:14}]); - }); + t([{ i: 'abc', p: 10 }, { i: 'xyz', p: 9 }]); + return t([{ i: 'abc', p: 10 }, { i: 'xyz', p: 14 }]); + }); it('compress deletes', function() { - assert.deepEqual([{d:'xyabc', p:8}], text0.normalize([{d:'abc', p:10}, {d:'xy', p:8}])); - assert.deepEqual([{d:'xabcy', p:9}], text0.normalize([{d:'abc', p:10}, {d:'xy', p:9}])); - return assert.deepEqual([{d:'abcxy', p:10}], text0.normalize([{d:'abc', p:10}, {d:'xy', p:10}])); - }); + assert.deepEqual( + [{ d: 'xyabc', p: 8 }], + text0.normalize([{ d: 'abc', p: 10 }, { d: 'xy', p: 8 }]) + ); + assert.deepEqual( + [{ d: 'xabcy', p: 9 }], + text0.normalize([{ d: 'abc', p: 10 }, { d: 'xy', p: 9 }]) + ); + return assert.deepEqual( + [{ d: 'abcxy', p: 10 }], + text0.normalize([{ d: 'abc', p: 10 }, { d: 'xy', p: 10 }]) + ); + }); return it('doesnt compress separate deletes', function() { const t = op => assert.deepEqual(op, text0.normalize(op)); - t([{d:'abc', p:10}, {d:'xyz', p:6}]); - return t([{d:'abc', p:10}, {d:'xyz', p:11}]); + t([{ d: 'abc', p: 10 }, { d: 'xyz', p: 6 }]); + return t([{ d: 'abc', p: 10 }, { d: 'xyz', p: 11 }]); + }); }); -}); - - return describe('randomizer', () => it('passes', function() { - this.timeout(4000); - this.slow(4000); - return fuzzer(text0, require('./text0-generator')); - }) - ); + return describe('randomizer', () => + it('passes', function() { + this.timeout(4000); + this.slow(4000); + return fuzzer(text0, require('./text0-generator')); + })); }); - From c858d3594d712818c89625ab93a8a9c810d6c467 Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 07:30:03 +0530 Subject: [PATCH 4/7] Add failing tests for presence functions --- package.json | 3 +- test/json0.js | 3 +- test/mocha.opts | 1 - test/text0.js | 332 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 334 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b6c9df6..aaf49d5 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ "dependencies": {}, "devDependencies": { "ot-fuzzer": "^1.0.0", - "mocha": "^1.20.1", - "coffee-script": "^1.7.1" + "mocha": "^1.20.1" }, "scripts": { "test": "mocha" diff --git a/test/json0.js b/test/json0.js index 4dda65c..db2eca9 100644 --- a/test/json0.js +++ b/test/json0.js @@ -1151,7 +1151,8 @@ const genTests = function(type) { }); }); - return describe('randomizer', function() { + // Skip this as it takes a long time. + return describe.skip('randomizer', function() { this.timeout(20000); this.slow(6000); it('passes', () => fuzzer(type, require('./json0-generator'), 1000)); diff --git a/test/mocha.opts b/test/mocha.opts index cf3c054..4d7bcbf 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,3 +1,2 @@ ---compilers coffee:coffee-script/register --reporter spec --check-leaks diff --git a/test/text0.js b/test/text0.js index 6534039..151c54b 100644 --- a/test/text0.js +++ b/test/text0.js @@ -278,10 +278,340 @@ describe('text0', function() { }); }); - return describe('randomizer', () => + // Skip this as it takes a long time. + describe.skip('randomizer', () => it('passes', function() { this.timeout(4000); this.slow(4000); return fuzzer(text0, require('./text0-generator')); })); + + describe('createPresence', function() { + it('basic tests', function() { + const defaultPresence = { u: '', c: 0, s: [] }; + const presence = { u: '5', c: 8, s: [[1, 2], [9, 5]] }; + + assert.deepEqual(createPresence(), defaultPresence); + assert.deepEqual(createPresence(null), defaultPresence); + assert.deepEqual(createPresence(true), defaultPresence); + assert.deepEqual( + createPresence({ u: 5, c: 8, s: [1, 2] }), + defaultPresence + ); + assert.deepEqual( + createPresence({ u: '5', c: '8', s: [1, 2] }), + defaultPresence + ); + assert.deepEqual( + createPresence({ u: '5', c: 8, s: [1.5, 2] }), + defaultPresence + ); + assert.strictEqual(createPresence(presence), presence); + }); + }); + + describe('transformPresence', function() { + it('basic tests', function() { + assert.deepEqual( + transformPresence( + { + u: 'user', + c: 8, + s: [[5, 7]] + }, + [], + true + ), + { + u: 'user', + c: 8, + s: [[5, 7]] + } + ); + assert.deepEqual( + transformPresence( + { + u: 'user', + c: 8, + s: [[5, 7]] + }, + [], + false + ), + { + u: 'user', + c: 8, + s: [[5, 7]] + } + ); + + assert.deepEqual( + transformPresence( + { + u: 'user', + c: 8, + s: [[5, 7]] + }, + [createRetain(3), createDelete(2), createInsertText('a')], + true + ), + { + u: 'user', + c: 8, + s: [[4, 6]] + } + ); + assert.deepEqual( + transformPresence( + { + u: 'user', + c: 8, + s: [[5, 7]] + }, + [createRetain(3), createDelete(2), createInsertText('a')], + false + ), + { + u: 'user', + c: 8, + s: [[3, 6]] + } + ); + + assert.deepEqual( + transformPresence( + { + u: 'user', + c: 8, + s: [[5, 7]] + }, + [createRetain(5), createDelete(2), createInsertText('a')], + true + ), + { + u: 'user', + c: 8, + s: [[6, 6]] + } + ); + assert.deepEqual( + transformPresence( + { + u: 'user', + c: 8, + s: [[5, 7]] + }, + [createRetain(5), createDelete(2), createInsertText('a')], + false + ), + { + u: 'user', + c: 8, + s: [[5, 5]] + } + ); + + assert.deepEqual( + transformPresence( + { + u: 'user', + c: 8, + s: [[5, 7], [8, 2]] + }, + [createInsertText('a')], + false + ), + { + u: 'user', + c: 8, + s: [[6, 8], [9, 3]] + } + ); + + assert.deepEqual( + transformPresence( + { + u: 'user', + c: 8, + s: [[1, 1], [2, 2]] + }, + [createInsertText('a')], + false + ), + { + u: 'user', + c: 8, + s: [[2, 2], [3, 3]] + } + ); + }); + }); + + describe('comparePresence', function() { + it('basic tests', function() { + assert.strictEqual(comparePresence(), true); + assert.strictEqual(comparePresence(undefined, undefined), true); + assert.strictEqual(comparePresence(null, null), true); + assert.strictEqual(comparePresence(null, undefined), false); + assert.strictEqual(comparePresence(undefined, null), false); + assert.strictEqual( + comparePresence(undefined, { u: '', c: 0, s: [] }), + false + ); + assert.strictEqual(comparePresence(null, { u: '', c: 0, s: [] }), false); + assert.strictEqual( + comparePresence({ u: '', c: 0, s: [] }, undefined), + false + ); + assert.strictEqual(comparePresence({ u: '', c: 0, s: [] }, null), false); + + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[1, 2]] }, + { u: 'user', c: 8, s: [[1, 2]] } + ), + true + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[1, 2], [4, 6]] }, + { u: 'user', c: 8, s: [[1, 2], [4, 6]] } + ), + true + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 }, + { u: 'user', c: 8, s: [[1, 2]] } + ), + true + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[1, 2]] }, + { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 } + ), + true + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[1, 2]] }, + { u: 'userX', c: 8, s: [[1, 2]] } + ), + false + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[1, 2]] }, + { u: 'user', c: 9, s: [[1, 2]] } + ), + false + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[1, 2]] }, + { u: 'user', c: 8, s: [[3, 2]] } + ), + false + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[1, 2]] }, + { u: 'user', c: 8, s: [[1, 3]] } + ), + false + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, + { u: 'user', c: 8, s: [[9, 8], [3, 2]] } + ), + false + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, + { u: 'user', c: 8, s: [[9, 8], [1, 3]] } + ), + false + ); + assert.strictEqual( + comparePresence( + { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, + { u: 'user', c: 8, s: [[9, 8]] } + ), + false + ); + }); + }); + + describe('isValidPresence', function() { + it('basic tests', function() { + assert.strictEqual(isValidPresence(), false); + assert.strictEqual(isValidPresence(null), false); + assert.strictEqual(isValidPresence([]), false); + assert.strictEqual(isValidPresence({}), false); + assert.strictEqual(isValidPresence({ u: 5, c: 8, s: [] }), false); + assert.strictEqual(isValidPresence({ u: '5', c: '8', s: [] }), false); + assert.strictEqual(isValidPresence({ u: '5', c: 8.5, s: [] }), false); + assert.strictEqual( + isValidPresence({ u: '5', c: Infinity, s: [] }), + false + ); + assert.strictEqual(isValidPresence({ u: '5', c: NaN, s: [] }), false); + assert.strictEqual(isValidPresence({ u: '5', c: 8, s: {} }), false); + assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [] }), true); + assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[]] }), false); + assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1]] }), false); + assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1, 2]] }), true); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2, 3]] }), + false + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2], []] }), + false + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6]] }), + true + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, '6']] }), + false + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6.1]] }), + false + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, Infinity]] }), + false + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, NaN]] }), + false + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -0]] }), + true + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -1]] }), + true + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, 2], ['3', 0]] }), + false + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [[1, '2'], [4, 0]] }), + false + ); + assert.strictEqual( + isValidPresence({ u: '5', c: 8, s: [['1', 2], [4, 0]] }), + false + ); + }); + }); }); From 836ddaca7f8bc122c263374dfe2325731a7161a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stian=20H=C3=A5klev?= Date: Thu, 4 Apr 2019 13:52:55 +0200 Subject: [PATCH 5/7] Presence changes --- lib/json0.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/lib/json0.js b/lib/json0.js index dc3a405..ed603e8 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -133,6 +133,87 @@ function convertToText(c) { delete c.o; } +function isValidPresence(presence) { + if ( + presence == null || + typeof presence.u !== 'string' || + typeof presence.c !== 'number' || + !isFinite(presence.c) || + Math.floor(presence.c) !== presence.c || + !Array.isArray(presence.s) + ) { + return false; + } + + var selections = presence.s; + + for (var i = 0, l = selections.length; i < l; ++i) { + var selection = selections[i]; + + if ( + !Array.isArray(selection) || + selection.length !== 2 || + selection[0] !== (selection[0] | 0) || + selection[1] !== (selection[1] | 0) + ) { + return false; + } + } + + return true; +} + +json.createPresence = function(presence) { + return presence; +}; + +json.comparePresence = function(pres1, pres2) { + if (!pres1 || !pres2) { + return false; + } + if (!pres1.p || !pres2.p) { + return false; + } + if (pres1.t !== pres2.t) { + return false; + } + if (pres1.t && subtypes[pres1.t]) { + if (pres1.p[0] === pres2.p[0]) { + return subtypes[pres1.t].comparePresence(pres1, pres2); + } + } else return pres1 === pres2; +}; + +json.transformPresence = function(presence, op, isOwn) { + console.group('json transform', presence, op); + if (op.length < 1) { + return presence; + } + const exOp = op[0]; + const opT = op[0].t; + console.log('exop', exOp, opT, exOp.p); + if (opT && subtypes[opT] && exOp.p) { + if (!presence.p || !presence.p[0] || presence.p[0] !== exOp.p[0]) { + console.log('creating presence', presence, exOp.p[0]); + presence = { + ...subtypes[opT].createPresence(), + p: op[0].p, + u: presence.u, + t: op[0].t + }; + console.log(presence); + } + presence = { + ...subtypes[opT].transformPresence(presence, op, isOwn), + p: op[0].p, + t: op[0].t + }; + console.log('result', presence); + console.groupEnd() + } + return presence; +}; + json.apply = function(snapshot, op) { json.checkValidOp(op); From 79a77fe5c27f7ace52f84b653ff6b01171ebf07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stian=20H=C3=A5klev?= Date: Thu, 4 Apr 2019 14:01:17 +0200 Subject: [PATCH 6/7] WIP --- lib/json0.js | 66 +++++++++++++++++----------------------------------- package.json | 6 ++--- 2 files changed, 24 insertions(+), 48 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index ed603e8..31152e8 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -133,40 +133,15 @@ function convertToText(c) { delete c.o; } -function isValidPresence(presence) { - if ( - presence == null || - typeof presence.u !== 'string' || - typeof presence.c !== 'number' || - !isFinite(presence.c) || - Math.floor(presence.c) !== presence.c || - !Array.isArray(presence.s) - ) { - return false; - } - - var selections = presence.s; - - for (var i = 0, l = selections.length; i < l; ++i) { - var selection = selections[i]; - - if ( - !Array.isArray(selection) || - selection.length !== 2 || - selection[0] !== (selection[0] | 0) || - selection[1] !== (selection[1] | 0) - ) { - return false; - } - } - - return true; -} - +// not checking anything here, we should probably check that u: exists +// (only thing we care about at json0 top level), and then delegate +// to any subtypes if there is already subtype presence data json.createPresence = function(presence) { return presence; }; +// this needs more thinking/testing, looking a bit more carefully at +// how this is implemented in ot-rich-text, etc. json.comparePresence = function(pres1, pres2) { if (!pres1 || !pres2) { return false; @@ -184,32 +159,33 @@ json.comparePresence = function(pres1, pres2) { } else return pres1 === pres2; }; +// this is the key function, always run client-side, both on +// the client that creates a text-change, and on the clients +// that receive text-changes (ops). if there are no ops, just +// return presence, if there are ops, delegate to the subtype +// responsible for those ops (currently only ot-rich-text). +// I am making assumptions many places that all ops will be +// of the same subtype, not sure if this is a given. +// We're only concerned about the first level of object/array, +// not sure if the spec allows nesting of subtypes. json.transformPresence = function(presence, op, isOwn) { - console.group('json transform', presence, op); if (op.length < 1) { return presence; } - const exOp = op[0]; - const opT = op[0].t; - console.log('exop', exOp, opT, exOp.p); - if (opT && subtypes[opT] && exOp.p) { + const representativeOp = op[0]; + const opTtype = op[0].t; + const path = representativeOp.p && representativeOp.p[0] + if (opType && subtypes[opType] && path) { if (!presence.p || !presence.p[0] || presence.p[0] !== exOp.p[0]) { - console.log('creating presence', presence, exOp.p[0]); - presence = { - ...subtypes[opT].createPresence(), - p: op[0].p, - u: presence.u, - t: op[0].t - }; - console.log(presence); + return presence } + // return result of running the subtype's transformPresence, + // but add path and type, which the subtype will not include presence = { ...subtypes[opT].transformPresence(presence, op, isOwn), p: op[0].p, t: op[0].t }; - console.log('result', presence); - console.groupEnd() } return presence; }; diff --git a/package.json b/package.json index aaf49d5..d2331ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "ot-json0", - "version": "1.1.0", + "name": "@houshuang/ot-json0", + "version": "1.2.0", "description": "JSON OT type", "main": "lib/index.js", "directories": { @@ -16,7 +16,7 @@ }, "repository": { "type": "git", - "url": "git://github.com/ottypes/json0" + "url": "git://github.com/houshuang/json0" }, "keywords": [ "ot", From a193a460cc222460f0d57bae7af8a6ea87cd7889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stian=20H=C3=A5klev?= Date: Thu, 4 Apr 2019 14:10:26 +0200 Subject: [PATCH 7/7] WIP --- lib/json0.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 31152e8..4b562e7 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -173,16 +173,16 @@ json.transformPresence = function(presence, op, isOwn) { return presence; } const representativeOp = op[0]; - const opTtype = op[0].t; + const opType = op[0].t; const path = representativeOp.p && representativeOp.p[0] if (opType && subtypes[opType] && path) { - if (!presence.p || !presence.p[0] || presence.p[0] !== exOp.p[0]) { + if (!presence.p || !presence.p[0] || presence.p[0] !== path) { return presence } // return result of running the subtype's transformPresence, // but add path and type, which the subtype will not include presence = { - ...subtypes[opT].transformPresence(presence, op, isOwn), + ...subtypes[opType].transformPresence(presence, op, isOwn), p: op[0].p, t: op[0].t };