From 4922eeac5671a8a39f82c9ae350a2824e3cfc192 Mon Sep 17 00:00:00 2001 From: Thomas Welter Date: Tue, 21 Sep 2021 14:23:58 +0200 Subject: [PATCH] Add support for null values in Y.Map and Y.Array --- README.md | 22 ++++++------ src/types/AbstractType.js | 73 +++++++++++++++++++++------------------ tests/y-array.tests.js | 7 +++- tests/y-map.tests.js | 8 +++-- 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 4798e0b4..ff576493 100644 --- a/README.md +++ b/README.md @@ -250,36 +250,36 @@ necessary.
parent:Y.AbstractType|null
- insert(index:number, content:Array<object|boolean|Array|string|number|Uint8Array|Y.Type>) + insert(index:number, content:Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
Insert content at index. Note that content is an array of elements. I.e. array.insert(0, [1]) splices the list and inserts 1 at position 0.
- push(Array<Object|boolean|Array|string|number|Uint8Array|Y.Type>) + push(Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
- unshift(Array<Object|boolean|Array|string|number|Uint8Array|Y.Type>) + unshift(Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
delete(index:number, length:number)
get(index:number)
- slice(start:number, end:number):Array<Object|boolean|Array|string|number|Uint8Array|Y.Type> + slice(start:number, end:number):Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>
Retrieve a range of content
length:number
-forEach(function(value:object|boolean|Array|string|number|Uint8Array|Y.Type, +forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type, index:number, array: Y.Array))
map(function(T, number, YArray):M):Array<M>
- toArray():Array<object|boolean|Array|string|number|Uint8Array|Y.Type> + toArray():Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>
Copies the content of this YArray to a new Array.
- toJSON():Array<Object|boolean|Array|string|number> + toJSON():Array<Object|boolean|Array|string|number|null>
Copies the content of this YArray to a new Array. It transforms all child types to JSON using their toJSON method. @@ -325,9 +325,9 @@ or any of its children.
size: number
Total number of key/value pairs.
- get(key:string):object|boolean|string|number|Uint8Array|Y.Type + get(key:string):object|boolean|string|number|null|Uint8Array|Y.Type
- set(key:string, value:object|boolean|string|number|Uint8Array|Y.Type) + set(key:string, value:object|boolean|string|number|null|Uint8Array|Y.Type)
delete(key:string)
@@ -339,12 +339,12 @@ or any of its children.
Removes all elements from this YMap.
clone():Y.Map
Clone this type into a fresh Yjs type.
- toJSON():Object<string, Object|boolean|Array|string|number|Uint8Array> + toJSON():Object<string, Object|boolean|Array|string|number|null|Uint8Array>
Copies the [key,value] pairs of this YMap to a new Object.It transforms all child types to JSON using their toJSON method.
- forEach(function(value:object|boolean|Array|string|number|Uint8Array|Y.Type, + forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type, key:string, map: Y.Map))
Execute the provided function once for every key-value pair. diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index bacf96c1..c25ab8fa 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -623,7 +623,7 @@ export const typeListGet = (type, index) => { * @param {Transaction} transaction * @param {AbstractType} parent * @param {Item?} referenceItem - * @param {Array|Array|boolean|number|string|Uint8Array>} content + * @param {Array|Array|boolean|number|null|string|Uint8Array>} content * * @private * @function @@ -635,7 +635,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem, const store = doc.store const right = referenceItem === null ? parent._start : referenceItem.right /** - * @type {Array|number>} + * @type {Array|number|null>} */ let jsonContent = [] const packJsonContent = () => { @@ -646,34 +646,39 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem, } } content.forEach(c => { - switch (c.constructor) { - case Number: - case Object: - case Boolean: - case Array: - case String: - jsonContent.push(c) - break - default: - packJsonContent() - switch (c.constructor) { - case Uint8Array: - case ArrayBuffer: - left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c)))) - left.integrate(transaction, 0) - break - case Doc: - left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c))) - left.integrate(transaction, 0) - break - default: - if (c instanceof AbstractType) { - left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c)) + if(c === null) { + jsonContent.push(c) + } else { + switch (c.constructor) { + case Number: + case Object: + case Boolean: + case Array: + case String: + jsonContent.push(c) + break + default: + packJsonContent() + switch (c.constructor) { + case Uint8Array: + case ArrayBuffer: + left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c)))) left.integrate(transaction, 0) - } else { - throw new Error('Unexpected content type in insert operation') - } - } + break + case Doc: + left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c))) + left.integrate(transaction, 0) + break + default: + if (c instanceof AbstractType) { + left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c)) + left.integrate(transaction, 0) + } else { + throw new Error('Unexpected content type in insert operation') + } + } + } + } }) packJsonContent() @@ -685,7 +690,7 @@ const lengthExceeded = error.create('Length exceeded!') * @param {Transaction} transaction * @param {AbstractType} parent * @param {number} index - * @param {Array|Array|number|string|Uint8Array>} content + * @param {Array|Array|number|null|string|Uint8Array>} content * * @private * @function @@ -797,7 +802,7 @@ export const typeMapDelete = (transaction, parent, key) => { * @param {Transaction} transaction * @param {AbstractType} parent * @param {string} key - * @param {Object|number|Array|string|Uint8Array|AbstractType} value + * @param {Object|number|null|Array|string|Uint8Array|AbstractType} value * * @private * @function @@ -838,7 +843,7 @@ export const typeMapSet = (transaction, parent, key, value) => { /** * @param {AbstractType} parent * @param {string} key - * @return {Object|number|Array|string|Uint8Array|AbstractType|undefined} + * @return {Object|number|null|Array|string|Uint8Array|AbstractType|undefined} * * @private * @function @@ -850,7 +855,7 @@ export const typeMapGet = (parent, key) => { /** * @param {AbstractType} parent - * @return {Object|number|Array|string|Uint8Array|AbstractType|undefined>} + * @return {Object|number|null|Array|string|Uint8Array|AbstractType|undefined>} * * @private * @function @@ -885,7 +890,7 @@ export const typeMapHas = (parent, key) => { * @param {AbstractType} parent * @param {string} key * @param {Snapshot} snapshot - * @return {Object|number|Array|string|Uint8Array|AbstractType|undefined} + * @return {Object|number|null|Array|string|Uint8Array|AbstractType|undefined} * * @private * @function diff --git a/tests/y-array.tests.js b/tests/y-array.tests.js index 8c70aa64..8cdaac5b 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -488,6 +488,11 @@ const arrayTransactions = [ map.set('someprop', 43) map.set('someprop', 44) }, + function insertTypeNull (user, gen) { + const yarray = user.getArray('array') + const pos = prng.int32(gen, 0, yarray.length) + yarray.insert(pos, [null]) + }, function _delete (user, gen) { const yarray = user.getArray('array') const length = yarray.length @@ -496,7 +501,7 @@ const arrayTransactions = [ let delLength = prng.int32(gen, 1, math.min(2, length - somePos)) if (prng.bool(gen)) { const type = yarray.get(somePos) - if (type.length > 0) { + if (type instanceof Y.Array && type.length > 0) { somePos = prng.int32(gen, 0, type.length - 1) delLength = prng.int32(gen, 0, math.min(2, type.length - somePos)) type.delete(somePos, delLength) diff --git a/tests/y-map.tests.js b/tests/y-map.tests.js index 7cbca52f..83f31a48 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -42,6 +42,7 @@ export const testBasicMapTests = tc => { const { testConnector, users, map0, map1, map2 } = init(tc, { users: 3 }) users[2].disconnect() + map0.set('null', null) map0.set('number', 1) map0.set('string', 'hello Y') map0.set('object', { key: { key2: 'value' } }) @@ -54,26 +55,29 @@ export const testBasicMapTests = tc => { array.insert(0, [0]) array.insert(0, [-1]) + t.assert(map0.get('null') === null, 'client 0 computed the change (null)') t.assert(map0.get('number') === 1, 'client 0 computed the change (number)') t.assert(map0.get('string') === 'hello Y', 'client 0 computed the change (string)') t.assert(map0.get('boolean0') === false, 'client 0 computed the change (boolean)') t.assert(map0.get('boolean1') === true, 'client 0 computed the change (boolean)') t.compare(map0.get('object'), { key: { key2: 'value' } }, 'client 0 computed the change (object)') t.assert(map0.get('y-map').get('y-array').get(0) === -1, 'client 0 computed the change (type)') - t.assert(map0.size === 6, 'client 0 map has correct size') + t.assert(map0.size === 7, 'client 0 map has correct size') users[2].connect() testConnector.flushAllMessages() + t.assert(map1.get('null') === null, 'client 1 received the update (null)') t.assert(map1.get('number') === 1, 'client 1 received the update (number)') t.assert(map1.get('string') === 'hello Y', 'client 1 received the update (string)') t.assert(map1.get('boolean0') === false, 'client 1 computed the change (boolean)') t.assert(map1.get('boolean1') === true, 'client 1 computed the change (boolean)') t.compare(map1.get('object'), { key: { key2: 'value' } }, 'client 1 received the update (object)') t.assert(map1.get('y-map').get('y-array').get(0) === -1, 'client 1 received the update (type)') - t.assert(map1.size === 6, 'client 1 map has correct size') + t.assert(map1.size === 7, 'client 1 map has correct size') // compare disconnected user + t.assert(map2.get('null') === null, 'client 2 received the update (null) - was disconnected') t.assert(map2.get('number') === 1, 'client 2 received the update (number) - was disconnected') t.assert(map2.get('string') === 'hello Y', 'client 2 received the update (string) - was disconnected') t.assert(map2.get('boolean0') === false, 'client 2 computed the change (boolean)')