diff --git a/.changeset/many-turtles-try.md b/.changeset/many-turtles-try.md new file mode 100644 index 00000000000..d662109f289 --- /dev/null +++ b/.changeset/many-turtles-try.md @@ -0,0 +1,5 @@ +--- +"@firebase/database": patch +--- + +Fixed an issue that could cause `once()` to fire more than once if the value was modified inside its callback. diff --git a/packages/database/src/api/Reference.ts b/packages/database/src/api/Reference.ts index 9269118c050..4bc9b63365b 100644 --- a/packages/database/src/api/Reference.ts +++ b/packages/database/src/api/Reference.ts @@ -326,7 +326,7 @@ export class Query implements Compat { validateCallback('Query.once', 'callback', callback, true); const ret = Query.getCancelAndContextArgs_( - 'Query.on', + 'Query.once', failureCallbackOrContext, context ); diff --git a/packages/database/src/exp/Reference_impl.ts b/packages/database/src/exp/Reference_impl.ts index c5401d4bf32..e7bdcc08168 100644 --- a/packages/database/src/exp/Reference_impl.ts +++ b/packages/database/src/exp/Reference_impl.ts @@ -959,8 +959,8 @@ function addEventListener( if (options && options.onlyOnce) { const userCallback = callback; const onceCallback: UserCallback = (dataSnapshot, previousChildName) => { - userCallback(dataSnapshot, previousChildName); repoRemoveEventCallbackForQuery(query._repo, query, container); + userCallback(dataSnapshot, previousChildName); }; onceCallback.userCallback = callback.userCallback; onceCallback.context = callback.context; diff --git a/packages/database/test/helpers/EventAccumulator.ts b/packages/database/test/helpers/EventAccumulator.ts index b9fd2779f4d..798e31405d0 100644 --- a/packages/database/test/helpers/EventAccumulator.ts +++ b/packages/database/test/helpers/EventAccumulator.ts @@ -27,6 +27,23 @@ export const EventAccumulatorFactory = { count++; }); return ea; + }, + waitsForExactCount: maxCount => { + let count = 0; + const condition = () => { + if (count > maxCount) { + throw new Error('Received more events than expected'); + } + return count === maxCount; + }; + const ea = new EventAccumulator(condition); + ea.onReset(() => { + count = 0; + }); + ea.onEvent(() => { + count++; + }); + return ea; } }; diff --git a/packages/database/test/query.test.ts b/packages/database/test/query.test.ts index f8b432a7a47..87f97a65e41 100644 --- a/packages/database/test/query.test.ts +++ b/packages/database/test/query.test.ts @@ -2330,6 +2330,32 @@ describe('Query Tests', () => { await ea.promise; }); + it('Query.once() only fires once', async () => { + const node = getRandomNode() as Reference; + + let count = 1; + node.set(count); + + const valueEvent = EventAccumulatorFactory.waitsForCount(3); + node.on('value', () => { + if (count < 3) { + ++count; + node.set(count); + } + valueEvent.addEvent(); + }); + + const onceEvent = EventAccumulatorFactory.waitsForExactCount(1); + node.once('value', () => { + ++count; + node.set(count); + onceEvent.addEvent(); + }); + + await valueEvent.promise; + await onceEvent.promise; + }); + it('Ensure on() returns callback function.', () => { const node = getRandomNode() as Reference; const callback = function () {};