From 4154b12f14337c42e8f4be63590b1d1c5ca6bb38 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Fri, 19 Nov 2021 13:27:14 +0100 Subject: [PATCH] handle local/remote autoload edge cases --- src/structs/ContentDoc.js | 6 ++- src/utils/Doc.js | 10 +++-- tests/doc.tests.js | 91 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/src/structs/ContentDoc.js b/src/structs/ContentDoc.js index 89233a5b..1b35d785 100644 --- a/src/structs/ContentDoc.js +++ b/src/structs/ContentDoc.js @@ -132,4 +132,8 @@ export class ContentDoc { * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder * @return {ContentDoc} */ -export const readContentDoc = decoder => new ContentDoc(new Doc({ guid: decoder.readString(), ...decoder.readAny() })) +export const readContentDoc = decoder => { + const guid = decoder.readString() + const opts = decoder.readAny() + return new ContentDoc(new Doc({ guid, ...opts, shouldLoad: opts.shouldLoad || opts.autoLoad || false })) +} diff --git a/src/utils/Doc.js b/src/utils/Doc.js index 38226599..2fba3f1d 100644 --- a/src/utils/Doc.js +++ b/src/utils/Doc.js @@ -28,6 +28,7 @@ export const generateNewClientId = random.uint32 * @property {string | null} [DocOpts.collectionid] Associate this document with a collection. This only plays a role if your provider has a concept of collection. * @property {any} [DocOpts.meta] Any kind of meta information you want to associate with this document. If this is a subdocument, remote peers will store the meta information as well. * @property {boolean} [DocOpts.autoLoad] If a subdocument, automatically load document. If this is a subdocument, remote peers will load the document as well automatically. + * @property {boolean} [DocOpts.shouldLoad] Whether the document should be synced by the provider now. This is toggled to true when you call ydoc.load() */ /** @@ -38,7 +39,7 @@ export class Doc extends Observable { /** * @param {DocOpts} [opts] configuration */ - constructor ({ guid = random.uuidv4(), collectionid = null, gc = true, gcFilter = () => true, meta = null, autoLoad = false } = {}) { + constructor ({ guid = random.uuidv4(), collectionid = null, gc = true, gcFilter = () => true, meta = null, autoLoad = false, shouldLoad = true } = {}) { super() this.gc = gc this.gcFilter = gcFilter @@ -67,7 +68,7 @@ export class Doc extends Observable { * @type {Item?} */ this._item = null - this.shouldLoad = autoLoad + this.shouldLoad = shouldLoad this.autoLoad = autoLoad this.meta = meta } @@ -252,12 +253,13 @@ export class Doc extends Observable { // @ts-ignore content.doc = null } else { - content.doc = new Doc({ guid: this.guid, ...content.opts }) + content.doc = new Doc({ guid: this.guid, ...content.opts, shouldLoad: false }) content.doc._item = item } transact(/** @type {any} */ (item).parent.doc, transaction => { + const doc = content.doc if (!item.deleted) { - transaction.subdocsAdded.add(content.doc) + transaction.subdocsAdded.add(doc) } transaction.subdocsRemoved.add(this) }, null, true) diff --git a/tests/doc.tests.js b/tests/doc.tests.js index 200f5ba3..63abee87 100644 --- a/tests/doc.tests.js +++ b/tests/doc.tests.js @@ -88,7 +88,7 @@ export const testSubdoc = tc => { subdocs.get('a').load() t.compare(event, [[], [], ['a']]) - subdocs.set('b', new Y.Doc({ guid: 'a' })) + subdocs.set('b', new Y.Doc({ guid: 'a', shouldLoad: false })) t.compare(event, [['a'], [], []]) subdocs.get('b').load() t.compare(event, [[], [], ['a']]) @@ -124,3 +124,92 @@ export const testSubdoc = tc => { t.compare(Array.from(doc2.getSubdocGuids()), ['a', 'c']) } } + +/** + * @param {t.TestCase} tc + */ +export const testSubdocLoadEdgeCases = tc => { + const ydoc = new Y.Doc() + const yarray = ydoc.getArray() + const subdoc1 = new Y.Doc() + /** + * @type {any} + */ + let lastEvent = null + ydoc.on('subdocs', event => { + lastEvent = event + }) + yarray.insert(0, [subdoc1]) + t.assert(subdoc1.shouldLoad) + t.assert(subdoc1.autoLoad === false) + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc1)) + t.assert(lastEvent !== null && lastEvent.added.has(subdoc1)) + // destroy and check whether lastEvent adds it again to added (it shouldn't) + subdoc1.destroy() + const subdoc2 = yarray.get(0) + t.assert(subdoc1 !== subdoc2) + t.assert(lastEvent !== null && lastEvent.added.has(subdoc2)) + t.assert(lastEvent !== null && !lastEvent.loaded.has(subdoc2)) + // load + subdoc2.load() + t.assert(lastEvent !== null && !lastEvent.added.has(subdoc2)) + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc2)) + // apply from remote + const ydoc2 = new Y.Doc() + ydoc2.on('subdocs', event => { + lastEvent = event + }) + Y.applyUpdate(ydoc2, Y.encodeStateAsUpdate(ydoc)) + const subdoc3 = ydoc2.getArray().get(0) + t.assert(subdoc3.shouldLoad === false) + t.assert(subdoc3.autoLoad === false) + t.assert(lastEvent !== null && lastEvent.added.has(subdoc3)) + t.assert(lastEvent !== null && !lastEvent.loaded.has(subdoc3)) + // load + subdoc3.load() + t.assert(subdoc3.shouldLoad) + t.assert(lastEvent !== null && !lastEvent.added.has(subdoc3)) + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc3)) +} + +/** + * @param {t.TestCase} tc + */ +export const testSubdocLoadEdgeCasesAutoload = tc => { + const ydoc = new Y.Doc() + const yarray = ydoc.getArray() + const subdoc1 = new Y.Doc({ autoLoad: true }) + /** + * @type {any} + */ + let lastEvent = null + ydoc.on('subdocs', event => { + lastEvent = event + }) + yarray.insert(0, [subdoc1]) + t.assert(subdoc1.shouldLoad) + t.assert(subdoc1.autoLoad) + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc1)) + t.assert(lastEvent !== null && lastEvent.added.has(subdoc1)) + // destroy and check whether lastEvent adds it again to added (it shouldn't) + subdoc1.destroy() + const subdoc2 = yarray.get(0) + t.assert(subdoc1 !== subdoc2) + t.assert(lastEvent !== null && lastEvent.added.has(subdoc2)) + t.assert(lastEvent !== null && !lastEvent.loaded.has(subdoc2)) + // load + subdoc2.load() + t.assert(lastEvent !== null && !lastEvent.added.has(subdoc2)) + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc2)) + // apply from remote + const ydoc2 = new Y.Doc() + ydoc2.on('subdocs', event => { + lastEvent = event + }) + Y.applyUpdate(ydoc2, Y.encodeStateAsUpdate(ydoc)) + const subdoc3 = ydoc2.getArray().get(0) + t.assert(subdoc1.shouldLoad) + t.assert(subdoc1.autoLoad) + t.assert(lastEvent !== null && lastEvent.added.has(subdoc3)) + t.assert(lastEvent !== null && lastEvent.loaded.has(subdoc3)) +}