From 7445a9ce5f4efd323c54391288efd06cac0e83f7 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Tue, 31 Jan 2023 12:56:07 +0100 Subject: [PATCH] add whenSynced and isSynced property with refined logic --- src/utils/Doc.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ tests/doc.tests.js | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/utils/Doc.js b/src/utils/Doc.js index e248e993..4eabd7c6 100644 --- a/src/utils/Doc.js +++ b/src/utils/Doc.js @@ -72,13 +72,57 @@ export class Doc extends Observable { this.shouldLoad = shouldLoad this.autoLoad = autoLoad this.meta = meta + /** + * This is set to true when the persistence provider loaded the document from the database or when the `sync` event fires. + * Note that not all providers implement this feature. Provider authors are encouraged to fire the `load` event when the doc content is loaded from the database. + * + * @type {boolean} + */ this.isLoaded = false + /** + * This is set to true when the connection provider has successfully synced with a backend. + * Note that when using peer-to-peer providers this event may not provide very useful. + * Also note that not all providers implement this feature. Provider authors are encouraged to fire + * the `sync` event when the doc has been synced (with `true` as a parameter) or if connection is + * lost (with false as a parameter). + */ + this.isSynced = false + /** + * Promise that resolves once the document has been loaded from a presistence provider. + */ this.whenLoaded = promise.create(resolve => { this.on('load', () => { this.isLoaded = true resolve(this) }) }) + const provideSyncedPromise = () => promise.create(resolve => { + /** + * @param {boolean} isSynced + */ + const eventHandler = (isSynced) => { + if (isSynced === undefined || isSynced === true) { + this.off('sync', eventHandler) + resolve() + } + } + this.on('sync', eventHandler) + }) + this.on('sync', isSynced => { + if (isSynced === false && this.isSynced) { + this.whenSynced = provideSyncedPromise() + } + this.isSynced = isSynced === undefined || isSynced === true + if (!this.isLoaded) { + this.emit('load', []) + } + }) + /** + * Promise that resolves once the document has been synced with a backend. + * This promise is recreated when the connection is lost. + * Note the documentation about the `isSynced` property. + */ + this.whenSynced = provideSyncedPromise() } /** diff --git a/tests/doc.tests.js b/tests/doc.tests.js index dce1b204..c6211977 100644 --- a/tests/doc.tests.js +++ b/tests/doc.tests.js @@ -257,7 +257,7 @@ export const testSubdocsUndo = _tc => { /** * @param {t.TestCase} _tc */ -export const testLoadDocs = async _tc => { +export const testLoadDocsEvent = async _tc => { const ydoc = new Y.Doc() t.assert(ydoc.isLoaded === false) let loadedEvent = false @@ -269,3 +269,44 @@ export const testLoadDocs = async _tc => { t.assert(loadedEvent) t.assert(ydoc.isLoaded) } + +/** + * @param {t.TestCase} _tc + */ +export const testSyncDocsEvent = async _tc => { + const ydoc = new Y.Doc() + t.assert(ydoc.isLoaded === false) + t.assert(ydoc.isSynced === false) + let loadedEvent = false + ydoc.once('load', () => { + loadedEvent = true + }) + let syncedEvent = false + ydoc.once('sync', /** @param {any} isSynced */ (isSynced) => { + syncedEvent = true + t.assert(isSynced) + }) + ydoc.emit('sync', [true, ydoc]) + await ydoc.whenLoaded + const oldWhenSynced = ydoc.whenSynced + await ydoc.whenSynced + t.assert(loadedEvent) + t.assert(syncedEvent) + t.assert(ydoc.isLoaded) + t.assert(ydoc.isSynced) + let loadedEvent2 = false + ydoc.on('load', () => { + loadedEvent2 = true + }) + let syncedEvent2 = false + ydoc.on('sync', (isSynced) => { + syncedEvent2 = true + t.assert(isSynced === false) + }) + ydoc.emit('sync', [false, ydoc]) + t.assert(!loadedEvent2) + t.assert(syncedEvent2) + t.assert(ydoc.isLoaded) + t.assert(!ydoc.isSynced) + t.assert(ydoc.whenSynced !== oldWhenSynced) +}