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
*/