diff --git a/README.md b/README.md index 5fb44e08..cc1c5136 100644 --- a/README.md +++ b/README.md @@ -334,6 +334,8 @@ or any of its children.
get(index:number)
+ clear() +
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> diff --git a/src/types/YMap.js b/src/types/YMap.js index 6cb1b309..6244f5a1 100644 --- a/src/types/YMap.js +++ b/src/types/YMap.js @@ -237,6 +237,21 @@ export class YMap extends AbstractType { return typeMapHas(this, key) } + /** + * Removes all elements from this YMap. + */ + clear () { + if (this.doc !== null) { + transact(this.doc, transaction => { + this.forEach(function (value, key, map) { + typeMapDelete(transaction, map, key) + }) + }) + } else { + /** @type {Map} */ (this._prelimContent).clear() + } + } + /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder */ diff --git a/src/types/YXmlElement.js b/src/types/YXmlElement.js index b3ce6ef9..a0369c92 100644 --- a/src/types/YXmlElement.js +++ b/src/types/YXmlElement.js @@ -3,6 +3,7 @@ import { YXmlFragment, transact, typeMapDelete, + typeMapHas, typeMapSet, typeMapGet, typeMapGetAll, @@ -160,6 +161,18 @@ export class YXmlElement extends YXmlFragment { return /** @type {any} */ (typeMapGet(this, attributeName)) } + /** + * Returns whether an attribute exists + * + * @param {String} attributeName The attribute name to check for existence. + * @return {boolean} whether the attribute exists. + * + * @public + */ + hasAttribute (attributeName) { + return /** @type {any} */ (typeMapHas(this, attributeName)) + } + /** * Returns all attribute name/value pairs in a JSON Object. * diff --git a/tests/y-map.tests.js b/tests/y-map.tests.js index 9ac259ab..7cbca52f 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -189,6 +189,49 @@ export const testGetAndSetAndDeleteOfMapProperty = tc => { compare(users) } +/** + * @param {t.TestCase} tc + */ +export const testSetAndClearOfMapProperties = tc => { + const { testConnector, users, map0 } = init(tc, { users: 1 }) + map0.set('stuff', 'c0') + map0.set('otherstuff', 'c1') + map0.clear() + testConnector.flushAllMessages() + for (const user of users) { + const u = user.getMap('map') + t.assert(u.get('stuff') === undefined) + t.assert(u.get('otherstuff') === undefined) + t.assert(u.size === 0, `map size after clear is ${u.size}, expected 0`) + } + compare(users) +} + +/** + * @param {t.TestCase} tc + */ +export const testSetAndClearOfMapPropertiesWithConflicts = tc => { + const { testConnector, users, map0, map1, map2, map3 } = init(tc, { users: 4 }) + map0.set('stuff', 'c0') + map1.set('stuff', 'c1') + map1.set('stuff', 'c2') + map2.set('stuff', 'c3') + testConnector.flushAllMessages() + map0.set('otherstuff', 'c0') + map1.set('otherstuff', 'c1') + map2.set('otherstuff', 'c2') + map3.set('otherstuff', 'c3') + map3.clear() + testConnector.flushAllMessages() + for (const user of users) { + const u = user.getMap('map') + t.assert(u.get('stuff') === undefined) + t.assert(u.get('otherstuff') === undefined) + t.assert(u.size === 0, `map size after clear is ${u.size}, expected 0`) + } + compare(users) +} + /** * @param {t.TestCase} tc */ @@ -335,6 +378,30 @@ export const testThrowsAddAndUpdateAndDeleteEvents = tc => { compare(users) } +/** + * @param {t.TestCase} tc + */ +export const testThrowsDeleteEventsOnClear = tc => { + const { users, map0 } = init(tc, { users: 2 }) + /** + * @type {Object} + */ + let event = {} + map0.observe(e => { + event = e // just put it on event, should be thrown synchronously anyway + }) + // set values + map0.set('stuff', 4) + map0.set('otherstuff', new Y.Array()) + // clear + map0.clear() + compareEvent(event, { + keysChanged: new Set(['stuff', 'otherstuff']), + target: map0 + }) + compare(users) +} + /** * @param {t.TestCase} tc */ diff --git a/tests/y-xml.tests.js b/tests/y-xml.tests.js index f0b6fd38..da460c70 100644 --- a/tests/y-xml.tests.js +++ b/tests/y-xml.tests.js @@ -15,6 +15,23 @@ export const testSetProperty = tc => { compare(users) } +/** + * @param {t.TestCase} tc + */ + export const testHasProperty = tc => { + const { testConnector, users, xml0, xml1 } = init(tc, { users: 2 }) + xml0.setAttribute('height', '10') + t.assert(xml0.hasAttribute('height'), 'Simple set+has works') + testConnector.flushAllMessages() + t.assert(xml1.hasAttribute('height'), 'Simple set+has works (remote)') + + xml0.removeAttribute('height') + t.assert(!xml0.hasAttribute('height'), 'Simple set+remove+has works') + testConnector.flushAllMessages() + t.assert(!xml1.hasAttribute('height'), 'Simple set+remove+has works (remote)') + compare(users) +} + /** * @param {t.TestCase} tc */