From fd142b06f510603c6f1a4d4ef5c6ec575d487af9 Mon Sep 17 00:00:00 2001 From: Robert Lord Date: Wed, 28 Jun 2017 12:29:30 -0700 Subject: [PATCH 1/9] Add cursor movement tests --- test/json0.coffee | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/test/json0.coffee b/test/json0.coffee index 531f76e..32e7574 100644 --- a/test/json0.coffee +++ b/test/json0.coffee @@ -389,6 +389,68 @@ genTests = (type) -> 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 'cursor', -> + describe 'string operations', -> + it 'handles inserts before', -> + assert.deepEqual ['key', 10, 3+4], type.transformCursor(['key', 10, 3], {p: ['key', 10, 1], si: 'meow'}) + it 'handles inserts after', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 5], si: 'meow'}) + it 'handles inserts at current point with isOwnOp', -> + assert.deepEqual ['key', 10, 3+4], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], si: 'meow'}, true) + it 'handles inserts at current point without isOwnOp', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], si: 'meow'}) + it 'handles deletes before', -> + assert.deepEqual ['key', 10, 3-2], type.transformCursor(['key', 10, 3], {p: ['key', 10, 0], sd: '12'}) + it 'handles deletes after', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], sd: '12'}) + it 'handles deletes at current point', -> + assert.deepEqual ['key', 10, 1], type.transformCursor(['key', 10, 3], {p: ['key', 10, 1], sd: 'meow meow'}) + it 'ignores irrelevant operations', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 9, 1], si: 'meow'}) + describe 'number operations', -> + it 'ignores', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], na: 123}) + describe 'list operations', -> + it 'handles inserts before', -> + assert.deepEqual ['key', 10, 3+1], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], li: 'meow'}) + assert.deepEqual ['key', 10+1, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10], li: 'meow'}) + it 'handles inserts after', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 4], li: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 11], li: 'meow'}) + it 'handles replacements at current point', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow1', li: 'meow2'}) + assert.deepEqual ['key', 10], type.transformCursor(['key', 10, 3], {p: ['key', 10], ld: 'meow1', li: 'meow2'}) # move cursor up tree when parent deleted + it 'handles deletes before', -> + assert.deepEqual ['key', 10, 3-1], type.transformCursor(['key', 10, 3], {p: ['key', 10, 2], ld: 'meow'}) + assert.deepEqual ['key', 10-1, 3], type.transformCursor(['key', 10, 3], {p: ['key', 9], ld: 'meow'}) + it 'handles deletes after', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 4], ld: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 11], ld: 'meow'}) + it 'handles deletes at current point', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow'}) + assert.deepEqual ['key', 10], type.transformCursor(['key', 10, 3], {p: ['key', 10], ld: 'meow'}) + it 'handles movements of current point', -> + assert.deepEqual ['key', 10, 20], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], lm: 20}) + assert.deepEqual ['key', 20, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10], lm: 20}) + it 'ignores irrelevant operations', -> + assert.deepEqual ['key', 10, 20], type.transformCursor(['key', 10, 3], {p: ['key', 10, 2], lm: 20}) + assert.deepEqual ['key', 20, 3], type.transformCursor(['key', 10, 3], {p: ['key', 9], lm: 20}) + describe 'dict operations', -> + it 'ignores irrelevant inserts and deletes', -> + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key2'], oi: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key2'], od: 'meow'}) + it 'handles deletes at current point', -> + assert.deepEqual [], type.transformCursor(['key', 0, 3], {p: ['key'], od: ['meow123']}) + assert.deepEqual ['key', 10], type.transformCursor(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123']}) + it 'handles replacements at current point', -> + assert.deepEqual ['key'], type.transformCursor(['key', 0, 3], {p: ['key'], od: ['meow123'], oi: 'newobj'}) + assert.deepEqual ['key', 10, 'key2'], type.transformCursor(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123'], oi: 'newobj'}) + describe 'subtype operations', -> + it 'warns that they are unsupported', -> + assert.throws(-> + type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], t: 'text0', o: 'testop'}) + , /subtype.*unsupported/) + describe 'randomizer', -> @timeout 20000 @slow 6000 From 60a660cea5c4d7b55aece48a1e0ae8da861a4cc1 Mon Sep 17 00:00:00 2001 From: Robert Lord Date: Wed, 28 Jun 2017 12:55:39 -0700 Subject: [PATCH 2/9] Pass string cursor transform operations --- lib/json0.js | 34 ++++++++++++++++++++++++ test/json0.coffee | 66 +++++++++++++++++++++++------------------------ 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index dc3a405..5157048 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -651,6 +651,40 @@ json.transformComponent = function(dest, c, otherC, type) { return dest; }; +json.transformPosition = function(cursor, op, isOwnOp) { + var cursor = clone(cursor) + + var opIsAncestor = (cursor.length > op.p.length) + var opIsSibling = (cursor.length === op.p.length) + // TODO need one for ancestor sibling + for (var i = 0; i < op.p.length; i++) { + if (op.p[i] !== cursor[i]) { + opIsAncestor = false + if (i < op.p.length-1) { + opIsSibling = false + } + } + } + + if (opIsSibling) { + if (op.sd) { + cursor[cursor.length-1] = text.transformCursor(cursor[cursor.length-1], [{p: op.p[op.p.length-1], d: op.sd}], isOwnOp ? 'right' : 'left') + } + if (op.si) { + cursor[cursor.length-1] = text.transformCursor(cursor[cursor.length-1], [{p: op.p[op.p.length-1], i: op.si}], isOwnOp ? 'right': 'left') + } + } + + return cursor +} + +json.transformCursor = function(cursor, op, isOwnOp) { + for (var i = 0; i < op.length; i++) { + cursor = transformPosition(cursor, op[i], isOwnOp); + } + return cursor; +} + require('./bootstrapTransform')(json, json.transformComponent, json.checkValidOp, json.append); /** diff --git a/test/json0.coffee b/test/json0.coffee index 32e7574..d7a22ef 100644 --- a/test/json0.coffee +++ b/test/json0.coffee @@ -389,66 +389,66 @@ genTests = (type) -> 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 'cursor', -> + describe 'cursor transform', -> describe 'string operations', -> it 'handles inserts before', -> - assert.deepEqual ['key', 10, 3+4], type.transformCursor(['key', 10, 3], {p: ['key', 10, 1], si: 'meow'}) + assert.deepEqual ['key', 10, 3+4], type.transformPosition(['key', 10, 3], {p: ['key', 10, 1], si: 'meow'}) it 'handles inserts after', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 5], si: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 5], si: 'meow'}) it 'handles inserts at current point with isOwnOp', -> - assert.deepEqual ['key', 10, 3+4], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], si: 'meow'}, true) + assert.deepEqual ['key', 10, 3+4], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], si: 'meow'}, true) it 'handles inserts at current point without isOwnOp', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], si: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], si: 'meow'}) it 'handles deletes before', -> - assert.deepEqual ['key', 10, 3-2], type.transformCursor(['key', 10, 3], {p: ['key', 10, 0], sd: '12'}) + assert.deepEqual ['key', 10, 3-2], type.transformPosition(['key', 10, 3], {p: ['key', 10, 0], sd: '12'}) it 'handles deletes after', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], sd: '12'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], sd: '12'}) it 'handles deletes at current point', -> - assert.deepEqual ['key', 10, 1], type.transformCursor(['key', 10, 3], {p: ['key', 10, 1], sd: 'meow meow'}) + assert.deepEqual ['key', 10, 1], type.transformPosition(['key', 10, 3], {p: ['key', 10, 1], sd: 'meow meow'}) it 'ignores irrelevant operations', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 9, 1], si: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 9, 1], si: 'meow'}) describe 'number operations', -> it 'ignores', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], na: 123}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], na: 123}) describe 'list operations', -> it 'handles inserts before', -> - assert.deepEqual ['key', 10, 3+1], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], li: 'meow'}) - assert.deepEqual ['key', 10+1, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10], li: 'meow'}) + assert.deepEqual ['key', 10, 3+1], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], li: 'meow'}) + assert.deepEqual ['key', 10+1, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10], li: 'meow'}) it 'handles inserts after', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 4], li: 'meow'}) - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 11], li: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 4], li: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 11], li: 'meow'}) it 'handles replacements at current point', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow1', li: 'meow2'}) - assert.deepEqual ['key', 10], type.transformCursor(['key', 10, 3], {p: ['key', 10], ld: 'meow1', li: 'meow2'}) # move cursor up tree when parent deleted + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow1', li: 'meow2'}) + assert.deepEqual ['key', 10], type.transformPosition(['key', 10, 3], {p: ['key', 10], ld: 'meow1', li: 'meow2'}) # move cursor up tree when parent deleted it 'handles deletes before', -> - assert.deepEqual ['key', 10, 3-1], type.transformCursor(['key', 10, 3], {p: ['key', 10, 2], ld: 'meow'}) - assert.deepEqual ['key', 10-1, 3], type.transformCursor(['key', 10, 3], {p: ['key', 9], ld: 'meow'}) + assert.deepEqual ['key', 10, 3-1], type.transformPosition(['key', 10, 3], {p: ['key', 10, 2], ld: 'meow'}) + assert.deepEqual ['key', 10-1, 3], type.transformPosition(['key', 10, 3], {p: ['key', 9], ld: 'meow'}) it 'handles deletes after', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 4], ld: 'meow'}) - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 11], ld: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 4], ld: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 11], ld: 'meow'}) it 'handles deletes at current point', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow'}) - assert.deepEqual ['key', 10], type.transformCursor(['key', 10, 3], {p: ['key', 10], ld: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow'}) + assert.deepEqual ['key', 10], type.transformPosition(['key', 10, 3], {p: ['key', 10], ld: 'meow'}) it 'handles movements of current point', -> - assert.deepEqual ['key', 10, 20], type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], lm: 20}) - assert.deepEqual ['key', 20, 3], type.transformCursor(['key', 10, 3], {p: ['key', 10], lm: 20}) + assert.deepEqual ['key', 10, 20], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], lm: 20}) + assert.deepEqual ['key', 20, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10], lm: 20}) it 'ignores irrelevant operations', -> - assert.deepEqual ['key', 10, 20], type.transformCursor(['key', 10, 3], {p: ['key', 10, 2], lm: 20}) - assert.deepEqual ['key', 20, 3], type.transformCursor(['key', 10, 3], {p: ['key', 9], lm: 20}) + assert.deepEqual ['key', 10, 20], type.transformPosition(['key', 10, 3], {p: ['key', 10, 2], lm: 20}) + assert.deepEqual ['key', 20, 3], type.transformPosition(['key', 10, 3], {p: ['key', 9], lm: 20}) describe 'dict operations', -> it 'ignores irrelevant inserts and deletes', -> - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key2'], oi: 'meow'}) - assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], {p: ['key2'], od: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key2'], oi: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key2'], od: 'meow'}) it 'handles deletes at current point', -> - assert.deepEqual [], type.transformCursor(['key', 0, 3], {p: ['key'], od: ['meow123']}) - assert.deepEqual ['key', 10], type.transformCursor(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123']}) + assert.deepEqual [], type.transformPosition(['key', 0, 3], {p: ['key'], od: ['meow123']}) + assert.deepEqual ['key', 10], type.transformPosition(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123']}) it 'handles replacements at current point', -> - assert.deepEqual ['key'], type.transformCursor(['key', 0, 3], {p: ['key'], od: ['meow123'], oi: 'newobj'}) - assert.deepEqual ['key', 10, 'key2'], type.transformCursor(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123'], oi: 'newobj'}) + assert.deepEqual ['key'], type.transformPosition(['key', 0, 3], {p: ['key'], od: ['meow123'], oi: 'newobj'}) + assert.deepEqual ['key', 10, 'key2'], type.transformPosition(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123'], oi: 'newobj'}) describe 'subtype operations', -> it 'warns that they are unsupported', -> assert.throws(-> - type.transformCursor(['key', 10, 3], {p: ['key', 10, 3], t: 'text0', o: 'testop'}) + type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], t: 'text0', o: 'testop'}) , /subtype.*unsupported/) describe 'randomizer', -> From af1394203b6cc72fad66d4095b391e98fbedc421 Mon Sep 17 00:00:00 2001 From: Robert Lord Date: Wed, 28 Jun 2017 13:14:43 -0700 Subject: [PATCH 3/9] Pass object cursor transform operations --- lib/json0.js | 22 +++++++++++++++++++--- test/json0.coffee | 6 +++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 5157048..a51263c 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -654,16 +654,21 @@ json.transformComponent = function(dest, c, otherC, type) { json.transformPosition = function(cursor, op, isOwnOp) { var cursor = clone(cursor) - var opIsAncestor = (cursor.length > op.p.length) - var opIsSibling = (cursor.length === op.p.length) - // TODO need one for ancestor sibling + var opIsAncestor = (cursor.length >= op.p.length) // true also if op is self + var opIsSibling = (cursor.length === op.p.length) // true also if op is self + var opIsAncestorSibling = (cursor.length > op.p.length) // true also if op is self or sibling of self + var equalUpTo = -1 for (var i = 0; i < op.p.length; i++) { if (op.p[i] !== cursor[i]) { opIsAncestor = false if (i < op.p.length-1) { opIsSibling = false + opIsAncestorSibling = false } } + if (equalUpTo === i-1 && op.p[i] === cursor[i]) { + equalUpTo += 1 + } } if (opIsSibling) { @@ -675,6 +680,17 @@ json.transformPosition = function(cursor, op, isOwnOp) { } } + if (opIsAncestor) { + if (op.lm) { + cursor[equalUpTo] = op.lm + } + if (op.od && op.oi) { + cursor = op.p.slice(0, op.p.length) + } else if (op.od) { + cursor = op.p.slice(0,op.p.length-1) + } + } + return cursor } diff --git a/test/json0.coffee b/test/json0.coffee index d7a22ef..abc5e07 100644 --- a/test/json0.coffee +++ b/test/json0.coffee @@ -389,7 +389,7 @@ genTests = (type) -> 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 'cursor transform', -> + describe 'transformCursor', -> describe 'string operations', -> it 'handles inserts before', -> assert.deepEqual ['key', 10, 3+4], type.transformPosition(['key', 10, 3], {p: ['key', 10, 1], si: 'meow'}) @@ -441,10 +441,10 @@ genTests = (type) -> assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key2'], od: 'meow'}) it 'handles deletes at current point', -> assert.deepEqual [], type.transformPosition(['key', 0, 3], {p: ['key'], od: ['meow123']}) - assert.deepEqual ['key', 10], type.transformPosition(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123']}) + assert.deepEqual ['key', 0], type.transformPosition(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123']}) it 'handles replacements at current point', -> assert.deepEqual ['key'], type.transformPosition(['key', 0, 3], {p: ['key'], od: ['meow123'], oi: 'newobj'}) - assert.deepEqual ['key', 10, 'key2'], type.transformPosition(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123'], oi: 'newobj'}) + assert.deepEqual ['key', 0, 'key2'], type.transformPosition(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123'], oi: 'newobj'}) describe 'subtype operations', -> it 'warns that they are unsupported', -> assert.throws(-> From d3621405a4a35b869250051c809ca5692d7b1194 Mon Sep 17 00:00:00 2001 From: Robert Lord Date: Wed, 28 Jun 2017 13:57:07 -0700 Subject: [PATCH 4/9] Add list insert delete and movement --- lib/json0.js | 25 +++++++++++++++++++++++-- test/json0.coffee | 14 +++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index a51263c..a9ca6f7 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -656,7 +656,7 @@ json.transformPosition = function(cursor, op, isOwnOp) { var opIsAncestor = (cursor.length >= op.p.length) // true also if op is self var opIsSibling = (cursor.length === op.p.length) // true also if op is self - var opIsAncestorSibling = (cursor.length > op.p.length) // true also if op is self or sibling of self + var opIsAncestorSibling = (cursor.length >= op.p.length) // true also if op is self or sibling of self var equalUpTo = -1 for (var i = 0; i < op.p.length; i++) { if (op.p[i] !== cursor[i]) { @@ -681,13 +681,34 @@ json.transformPosition = function(cursor, op, isOwnOp) { } if (opIsAncestor) { - if (op.lm) { + if (op.lm !== undefined) { cursor[equalUpTo] = op.lm } if (op.od && op.oi) { cursor = op.p.slice(0, op.p.length) } else if (op.od) { cursor = op.p.slice(0,op.p.length-1) + } else if (op.ld && op.li) { + cursor = op.p.slice(0, op.p.length) + } else if (op.ld) { + cursor = op.p.slice(0, op.p.length-1) + } + } + + if (opIsAncestorSibling) { + var lastPathIdx = op.p.length-1 + if (!opIsAncestor && op.ld && !op.li && op.p[lastPathIdx] < cursor[lastPathIdx]) { + cursor[lastPathIdx] -= 1 + } else if (!op.ld && op.li && op.p[lastPathIdx] <= cursor[lastPathIdx]) { + cursor[lastPathIdx] += 1 + } + + // if move item in list from after to before + if (!opIsAncestor && op.lm !== undefined && op.p[lastPathIdx] > cursor[lastPathIdx] && op.lm <= cursor[lastPathIdx]) { + cursor[lastPathIdx] += 1 + // if move item in list from before to after + } else if (!opIsAncestor && op.lm !== undefined && op.p[lastPathIdx] < cursor[lastPathIdx] && op.lm >= cursor[lastPathIdx]) { + cursor[lastPathIdx] -= 1 } } diff --git a/test/json0.coffee b/test/json0.coffee index abc5e07..d9ca36b 100644 --- a/test/json0.coffee +++ b/test/json0.coffee @@ -427,14 +427,18 @@ genTests = (type) -> assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 4], ld: 'meow'}) assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 11], ld: 'meow'}) it 'handles deletes at current point', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow'}) - assert.deepEqual ['key', 10], type.transformPosition(['key', 10, 3], {p: ['key', 10], ld: 'meow'}) + assert.deepEqual ['key', 10], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow'}) + assert.deepEqual ['key'], type.transformPosition(['key', 10, 3], {p: ['key', 10], ld: 'meow'}) it 'handles movements of current point', -> assert.deepEqual ['key', 10, 20], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], lm: 20}) assert.deepEqual ['key', 20, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10], lm: 20}) - it 'ignores irrelevant operations', -> - assert.deepEqual ['key', 10, 20], type.transformPosition(['key', 10, 3], {p: ['key', 10, 2], lm: 20}) - assert.deepEqual ['key', 20, 3], type.transformPosition(['key', 10, 3], {p: ['key', 9], lm: 20}) + it 'handles movements of other points', -> + assert.deepEqual ['key', 10, 2], type.transformPosition(['key', 10, 3], {p: ['key', 10, 1], lm: 20}) + assert.deepEqual ['key', 10, 4], type.transformPosition(['key', 10, 3], {p: ['key', 10, 5], lm: 3}) + assert.deepEqual ['key', 10, 4], type.transformPosition(['key', 10, 3], {p: ['key', 10, 5], lm: 1}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 10], lm: 20}) + assert.deepEqual ['key', 10, 2], type.transformPosition(['key', 10, 3], {p: ['key', 10, 2], lm: 3}) + assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 2], lm: 1}) describe 'dict operations', -> it 'ignores irrelevant inserts and deletes', -> assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key2'], oi: 'meow'}) From c066608d889af16068708ff71441765d95b21ac8 Mon Sep 17 00:00:00 2001 From: Robert Lord Date: Wed, 28 Jun 2017 14:22:55 -0700 Subject: [PATCH 5/9] Fix transformCursor to point to correct transformPosition --- lib/json0.js | 2 +- test/json0.coffee | 72 +++++++++++++++++++++++------------------------ 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index a9ca6f7..2485954 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -651,7 +651,7 @@ json.transformComponent = function(dest, c, otherC, type) { return dest; }; -json.transformPosition = function(cursor, op, isOwnOp) { +var transformPosition = function(cursor, op, isOwnOp) { var cursor = clone(cursor) var opIsAncestor = (cursor.length >= op.p.length) // true also if op is self diff --git a/test/json0.coffee b/test/json0.coffee index d9ca36b..a7a8732 100644 --- a/test/json0.coffee +++ b/test/json0.coffee @@ -392,67 +392,67 @@ genTests = (type) -> describe 'transformCursor', -> describe 'string operations', -> it 'handles inserts before', -> - assert.deepEqual ['key', 10, 3+4], type.transformPosition(['key', 10, 3], {p: ['key', 10, 1], si: 'meow'}) + assert.deepEqual ['key', 10, 3+4], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 1], si: 'meow'}]) it 'handles inserts after', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 5], si: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 5], si: 'meow'}]) it 'handles inserts at current point with isOwnOp', -> - assert.deepEqual ['key', 10, 3+4], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], si: 'meow'}, true) + assert.deepEqual ['key', 10, 3+4], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], si: 'meow'}], true) it 'handles inserts at current point without isOwnOp', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], si: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], si: 'meow'}]) it 'handles deletes before', -> - assert.deepEqual ['key', 10, 3-2], type.transformPosition(['key', 10, 3], {p: ['key', 10, 0], sd: '12'}) + assert.deepEqual ['key', 10, 3-2], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 0], sd: '12'}]) it 'handles deletes after', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], sd: '12'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], sd: '12'}]) it 'handles deletes at current point', -> - assert.deepEqual ['key', 10, 1], type.transformPosition(['key', 10, 3], {p: ['key', 10, 1], sd: 'meow meow'}) + assert.deepEqual ['key', 10, 1], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 1], sd: 'meow meow'}]) it 'ignores irrelevant operations', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 9, 1], si: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 9, 1], si: 'meow'}]) describe 'number operations', -> it 'ignores', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], na: 123}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], na: 123}]) describe 'list operations', -> it 'handles inserts before', -> - assert.deepEqual ['key', 10, 3+1], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], li: 'meow'}) - assert.deepEqual ['key', 10+1, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10], li: 'meow'}) + assert.deepEqual ['key', 10, 3+1], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], li: 'meow'}]) + assert.deepEqual ['key', 10+1, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10], li: 'meow'}]) it 'handles inserts after', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 4], li: 'meow'}) - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 11], li: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 4], li: 'meow'}]) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 11], li: 'meow'}]) it 'handles replacements at current point', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow1', li: 'meow2'}) - assert.deepEqual ['key', 10], type.transformPosition(['key', 10, 3], {p: ['key', 10], ld: 'meow1', li: 'meow2'}) # move cursor up tree when parent deleted + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], ld: 'meow1', li: 'meow2'}]) + assert.deepEqual ['key', 10], type.transformCursor(['key', 10, 3], [{p: ['key', 10], ld: 'meow1', li: 'meow2'}]) # move cursor up tree when parent deleted it 'handles deletes before', -> - assert.deepEqual ['key', 10, 3-1], type.transformPosition(['key', 10, 3], {p: ['key', 10, 2], ld: 'meow'}) - assert.deepEqual ['key', 10-1, 3], type.transformPosition(['key', 10, 3], {p: ['key', 9], ld: 'meow'}) + assert.deepEqual ['key', 10, 3-1], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 2], ld: 'meow'}]) + assert.deepEqual ['key', 10-1, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 9], ld: 'meow'}]) it 'handles deletes after', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 4], ld: 'meow'}) - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 11], ld: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 4], ld: 'meow'}]) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 11], ld: 'meow'}]) it 'handles deletes at current point', -> - assert.deepEqual ['key', 10], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], ld: 'meow'}) - assert.deepEqual ['key'], type.transformPosition(['key', 10, 3], {p: ['key', 10], ld: 'meow'}) + assert.deepEqual ['key', 10], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], ld: 'meow'}]) + assert.deepEqual ['key'], type.transformCursor(['key', 10, 3], [{p: ['key', 10], ld: 'meow'}]) it 'handles movements of current point', -> - assert.deepEqual ['key', 10, 20], type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], lm: 20}) - assert.deepEqual ['key', 20, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10], lm: 20}) + assert.deepEqual ['key', 10, 20], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], lm: 20}]) + assert.deepEqual ['key', 20, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10], lm: 20}]) it 'handles movements of other points', -> - assert.deepEqual ['key', 10, 2], type.transformPosition(['key', 10, 3], {p: ['key', 10, 1], lm: 20}) - assert.deepEqual ['key', 10, 4], type.transformPosition(['key', 10, 3], {p: ['key', 10, 5], lm: 3}) - assert.deepEqual ['key', 10, 4], type.transformPosition(['key', 10, 3], {p: ['key', 10, 5], lm: 1}) - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 10], lm: 20}) - assert.deepEqual ['key', 10, 2], type.transformPosition(['key', 10, 3], {p: ['key', 10, 2], lm: 3}) - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key', 10, 2], lm: 1}) + assert.deepEqual ['key', 10, 2], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 1], lm: 20}]) + assert.deepEqual ['key', 10, 4], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 5], lm: 3}]) + assert.deepEqual ['key', 10, 4], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 5], lm: 1}]) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 10], lm: 20}]) + assert.deepEqual ['key', 10, 2], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 2], lm: 3}]) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key', 10, 2], lm: 1}]) describe 'dict operations', -> it 'ignores irrelevant inserts and deletes', -> - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key2'], oi: 'meow'}) - assert.deepEqual ['key', 10, 3], type.transformPosition(['key', 10, 3], {p: ['key2'], od: 'meow'}) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key2'], oi: 'meow'}]) + assert.deepEqual ['key', 10, 3], type.transformCursor(['key', 10, 3], [{p: ['key2'], od: 'meow'}]) it 'handles deletes at current point', -> - assert.deepEqual [], type.transformPosition(['key', 0, 3], {p: ['key'], od: ['meow123']}) - assert.deepEqual ['key', 0], type.transformPosition(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123']}) + assert.deepEqual [], type.transformCursor(['key', 0, 3], [{p: ['key'], od: ['meow123']}]) + assert.deepEqual ['key', 0], type.transformCursor(['key', 0, 'key2'], [{p: ['key', 0, 'key2'], od: ['meow123']}]) it 'handles replacements at current point', -> - assert.deepEqual ['key'], type.transformPosition(['key', 0, 3], {p: ['key'], od: ['meow123'], oi: 'newobj'}) - assert.deepEqual ['key', 0, 'key2'], type.transformPosition(['key', 0, 'key2'], {p: ['key', 0, 'key2'], od: ['meow123'], oi: 'newobj'}) + assert.deepEqual ['key'], type.transformCursor(['key', 0, 3], [{p: ['key'], od: ['meow123'], oi: 'newobj'}]) + assert.deepEqual ['key', 0, 'key2'], type.transformCursor(['key', 0, 'key2'], [{p: ['key', 0, 'key2'], od: ['meow123'], oi: 'newobj'}]) describe 'subtype operations', -> it 'warns that they are unsupported', -> assert.throws(-> - type.transformPosition(['key', 10, 3], {p: ['key', 10, 3], t: 'text0', o: 'testop'}) + type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], t: 'text0', o: 'testop'}]) , /subtype.*unsupported/) describe 'randomizer', -> From 99ea6ee21086f5c9a4372201ad8f90febf438c43 Mon Sep 17 00:00:00 2001 From: Robert Lord Date: Wed, 28 Jun 2017 14:23:34 -0700 Subject: [PATCH 6/9] Remove subtype test --- test/json0.coffee | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/json0.coffee b/test/json0.coffee index a7a8732..319f913 100644 --- a/test/json0.coffee +++ b/test/json0.coffee @@ -449,11 +449,6 @@ genTests = (type) -> it 'handles replacements at current point', -> assert.deepEqual ['key'], type.transformCursor(['key', 0, 3], [{p: ['key'], od: ['meow123'], oi: 'newobj'}]) assert.deepEqual ['key', 0, 'key2'], type.transformCursor(['key', 0, 'key2'], [{p: ['key', 0, 'key2'], od: ['meow123'], oi: 'newobj'}]) - describe 'subtype operations', -> - it 'warns that they are unsupported', -> - assert.throws(-> - type.transformCursor(['key', 10, 3], [{p: ['key', 10, 3], t: 'text0', o: 'testop'}]) - , /subtype.*unsupported/) describe 'randomizer', -> @timeout 20000 From 010bc3e2a2241e9382802b505b3fd5d5d3573ed8 Mon Sep 17 00:00:00 2001 From: Robert Lord Date: Sat, 30 Sep 2017 13:00:02 -0500 Subject: [PATCH 7/9] Update package to be json00 --- package.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0850266..62f3b63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "ot-json0", - "version": "1.0.1", + "name": "json00", + "version": "1.1.0", "description": "JSON OT type", "main": "lib/index.js", "directories": { @@ -25,10 +25,13 @@ "sharejs", "operational-transformation" ], - "author": "Joseph Gentle ", + "contributors": [ + "Joseph Gentle ", + "Robert Lord " + ], "license": "ISC", "bugs": { - "url": "https://github.com/ottypes/json0/issues" + "url": "https://github.com/wheatco/json00/issues" }, - "homepage": "https://github.com/ottypes/json0" + "homepage": "https://github.com/wheatco/json00" } From ec687ebccc1cc0eb1e595a0a21059ff4bca9c0e6 Mon Sep 17 00:00:00 2001 From: Robert Lord Date: Sat, 30 Sep 2017 13:06:08 -0500 Subject: [PATCH 8/9] Add logo to readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 678ec59..7623c45 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -# JSON0 OT Type +

+ JSON 00: Operational Transform Type +
+ Build Status +

The JSON OT type can be used to edit arbitrary JSON documents. From 30badcefe7970543feab3b4216e63c11550a3f87 Mon Sep 17 00:00:00 2001 From: Robert Lord Date: Sun, 8 Oct 2017 21:34:22 -0500 Subject: [PATCH 9/9] v2 add e key on ops for event data --- README.md | 11 ++++++----- lib/json0.js | 4 ++++ package.json | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7623c45..a44cc14 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ into the array. `{p:[path], t:subtype, o:subtypeOp}` | applies the subtype op `o` of type `t` to the object at `[path]` `{p:[path,offset], si:s}` | inserts the string `s` at offset `offset` into the string at `[path]` (uses subtypes internally). `{p:[path,offset], sd:s}` | deletes the string `s` at offset `offset` from the string at `[path]` (uses subtypes internally). +`{p:[path], e:data}` | no-op to data, but can be used to trigger an event with data on the server. --- @@ -100,7 +101,7 @@ Lists and objects have the same set of operations (*Insert*, *Delete*, *Replace*, *Move*) but their semantics are very different. List operations shuffle adjacent list items left or right to make space (or to remove space). Object operations do not. You should pick the data structure which will give -you the behaviour you want when you design your data model. +you the behaviour you want when you design your data model. To make it clear what the semantics of operations will be, list operations and object operations are named differently. (`li`, `ld`, `lm` for lists and `oi`, @@ -205,9 +206,9 @@ There is (unfortunately) no equivalent for list move with objects. ### Subtype operations Usage: - + {p:PATH, t:SUBTYPE, o:OPERATION} - + `PATH` is the path to the object that will be modified by the subtype. `SUBTYPE` is the name of the subtype, e.g. `"text0"`. `OPERATION` is the subtype operation itself. @@ -246,7 +247,7 @@ the subtype operation. Usage: {p:PATH, t:'text0', o:[{p:OFFSET, i:TEXT}]} - + Insert `TEXT` to the string specified by `PATH` at the position specified by `OFFSET`. ##### Delete from a string @@ -254,7 +255,7 @@ Insert `TEXT` to the string specified by `PATH` at the position specified by `OF Usage: {p:PATH, t:'text0', o:[{p:OFFSET, d:TEXT}]} - + Delete `TEXT` in the string specified by `PATH` at the position specified by `OFFSET`. --- diff --git a/lib/json0.js b/lib/json0.js index 2485954..db2858d 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -226,6 +226,10 @@ json.apply = function(snapshot, op) { delete elem[key]; } + else if (c.e !== void 0) { + // no-op change to data + } + else { throw new Error('invalid / missing instruction in op'); } diff --git a/package.json b/package.json index 62f3b63..882b8d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json00", - "version": "1.1.0", + "version": "2.0.0", "description": "JSON OT type", "main": "lib/index.js", "directories": {