From ce14c8fafc43291101e32263f64c9ff513584610 Mon Sep 17 00:00:00 2001 From: Alec Gibson Date: Mon, 2 Mar 2020 11:43:52 +0000 Subject: [PATCH] Add support for presence (range) transformations This change adds support for presence transformations, which are being [added to ShareDB][1]. In order to support presence updates, this change adds support for the optional `transformPresence` method, which simply reuses the existing `transformCursor` method, but also: - applies changes to the `length` of a range - keeps existing metadata - returns `null` if no range has been provided [1]: https://github.com/share/sharedb/pull/322 --- lib/type.js | 17 +++++++++- package.json | 2 +- test/presence.js | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 test/presence.js diff --git a/lib/type.js b/lib/type.js index b6e1a2b..9abd912 100644 --- a/lib/type.js +++ b/lib/type.js @@ -50,6 +50,21 @@ module.exports = { deserialize: function(ops) { return new Delta(ops); - } + }, + + transformPresence: function(range, op, isOwnOp) { + if (!range) { + return null; + } + + const delta = new Delta(op); + const start = this.transformCursor(range.index, delta, isOwnOp); + const end = this.transformCursor(range.index + range.length, delta, isOwnOp); + + return Object.assign({}, range, { + index: start, + length: end - start, + }); + }, } }; diff --git a/package.json b/package.json index c5087c0..2da08bd 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "license": "MIT", "scripts": { - "test": "mocha test/fuzzer.js --timeout 5000" + "test": "mocha test/**/*.js --timeout 5000" }, "repository": { "type": "git", diff --git a/test/presence.js b/test/presence.js new file mode 100644 index 0000000..5164029 --- /dev/null +++ b/test/presence.js @@ -0,0 +1,87 @@ +const Delta = require("quill-delta"); +var richText = require('../lib/type').type; +var expect = require('chai').expect; + +describe('presence', function() { + it('transforms a zero-length range by an op before it', function() { + const range = {index: 10, length: 0}; + const op = new Delta().insert('foo'); + const transformed = richText.transformPresence(range, op); + expect(transformed).to.eql({index: 13, length: 0}); + }); + + it('does not transform a zero-length range by an op after it', function() { + const range = {index: 10, length: 0}; + const op = new Delta().retain(20).insert('foo'); + const transformed = richText.transformPresence(range, op); + expect(transformed).to.eql({index: 10, length: 0}); + }); + + it('transforms a range with length by an op before it', function() { + const range = {index: 10, length: 3}; + const op = new Delta().insert('foo'); + const transformed = richText.transformPresence(range, op); + expect(transformed).to.eql({index: 13, length: 3}); + }); + + it('transforms a range with length by an op that deletes part of it', function() { + const range = {index: 10, length: 3}; + const op = new Delta().retain(9).delete(3); + const transformed = richText.transformPresence(range, op); + expect(transformed).to.eql({index: 9, length: 1}); + }); + + it('transforms a range with length by an op that deletes the whole range', function() { + const range = {index: 10, length: 3}; + const op = new Delta().retain(9).delete(5); + const transformed = richText.transformPresence(range, op); + expect(transformed).to.eql({index: 9, length: 0}); + }); + + it('keeps extra metadata when transforming', function() { + const range = {index: 10, length: 0, meta: 'lorem ipsum'}; + const op = new Delta().insert('foo'); + const transformed = richText.transformPresence(range, op); + expect(transformed).to.eql({index: 13, length: 0, meta: 'lorem ipsum'}); + }); + + it('returns null when no presence is provided', function() { + const op = new Delta().insert('foo'); + const transformed = richText.transformPresence(undefined, op); + expect(transformed).to.be.null; + }); + + it('advances the cursor if inserting at own index', function() { + const range = {index: 10, length: 2}; + const op = new Delta().retain(10).insert('foo'); + const transformed = richText.transformPresence(range, op, true); + expect(transformed).to.eql({index: 13, length: 2}); + }); + + it('does not advance the cursor if not own op', function () { + const range = {index: 10, length: 2}; + const op = new Delta().retain(10).insert('foo'); + const transformed = richText.transformPresence(range, op, false); + expect(transformed).to.eql({index: 10, length: 5}); + }); + + it('accepts an array of ops rather than a Delta', function() { + const range = {index: 10, length: 0}; + const op = [{insert: 'foo'}]; + const transformed = richText.transformPresence(range, op); + expect(transformed).to.eql({index: 13, length: 0}); + }); + + it('does nothing if no op is provided', function() { + const range = {index: 10, length: 0}; + const transformed = richText.transformPresence(range, undefined); + expect(transformed).to.eql({index: 10, length: 0}); + }); + + it('does not mutate the original range', function() { + const range = {index: 10, length: 0}; + const op = new Delta().insert('foo'); + richText.transformPresence(range, op); + expect(range).to.eql({index: 10, length: 0}); + }); +});