From 21531f26438cc34e4b86f17d58102263a73f2747 Mon Sep 17 00:00:00 2001 From: Nabeel Parkar Date: Tue, 19 Sep 2023 21:58:39 +0530 Subject: [PATCH] feat(database): Firebase V9 modular API (#7136) --- packages/database/e2e/DatabaseStatics.e2e.js | 115 +- packages/database/e2e/database.e2e.js | 534 ++++++--- .../database/e2e/internal/connected.e2e.js | 105 +- .../e2e/internal/serverTimeOffset.e2e.js | 18 +- packages/database/e2e/issues.e2e.js | 356 ++++-- .../database/e2e/onDisconnect/cancel.e2e.js | 130 ++- .../database/e2e/onDisconnect/remove.e2e.js | 121 +- packages/database/e2e/onDisconnect/set.e2e.js | 156 ++- .../e2e/onDisconnect/setWithPriority.e2e.js | 194 +++- .../database/e2e/onDisconnect/update.e2e.js | 247 ++-- packages/database/e2e/query/endAt.e2e.js | 294 +++-- packages/database/e2e/query/equalTo.e2e.js | 216 +++- packages/database/e2e/query/get.e2e.js | 64 + packages/database/e2e/query/keepSynced.e2e.js | 48 +- .../database/e2e/query/limitToFirst.e2e.js | 194 +++- .../database/e2e/query/limitToLast.e2e.js | 194 +++- .../database/e2e/query/onChildAdded.e2e.js | 88 ++ .../database/e2e/query/onChildChanged.e2e.js | 95 ++ .../database/e2e/query/onChildMoved.e2e.js | 93 ++ .../database/e2e/query/onChildRemoved.e2e.js | 83 ++ packages/database/e2e/query/onValue.e2e.js | 137 +++ .../database/e2e/query/orderByChild.e2e.js | 136 ++- packages/database/e2e/query/orderByKey.e2e.js | 76 +- .../database/e2e/query/orderByPriority.e2e.js | 92 +- .../database/e2e/query/orderByValue.e2e.js | 88 +- packages/database/e2e/query/query.e2e.js | 37 +- packages/database/e2e/query/startAt.e2e.js | 285 +++-- packages/database/e2e/query/toJSON.e2e.js | 21 +- packages/database/e2e/reference/child.e2e.js | 70 +- packages/database/e2e/reference/key.e2e.js | 37 +- .../e2e/reference/onDisconnect.e2e.js | 17 +- packages/database/e2e/reference/parent.e2e.js | 37 +- packages/database/e2e/reference/push.e2e.js | 163 ++- packages/database/e2e/reference/remove.e2e.js | 42 +- packages/database/e2e/reference/root.e2e.js | 17 +- packages/database/e2e/reference/set.e2e.js | 121 +- .../database/e2e/reference/setPriority.e2e.js | 168 ++- .../e2e/reference/setWithPriority.e2e.js | 126 +- .../database/e2e/reference/transaction.e2e.js | 393 ++++--- packages/database/e2e/reference/update.e2e.js | 166 ++- .../database/e2e/snapshot/snapshot.e2e.js | 589 +++++++--- packages/database/lib/index.d.ts | 2 + packages/database/lib/index.js | 2 + packages/database/lib/modular/index.d.ts | 217 ++++ packages/database/lib/modular/index.js | 124 ++ packages/database/lib/modular/query.d.ts | 1025 +++++++++++++++++ packages/database/lib/modular/query.js | 291 +++++ .../database/lib/modular/transaction.d.ts | 56 + packages/database/lib/modular/transaction.js | 15 + tests/app.js | 2 + tests/e2e/globals.js | 6 + 51 files changed, 6304 insertions(+), 1599 deletions(-) create mode 100644 packages/database/e2e/query/get.e2e.js create mode 100644 packages/database/e2e/query/onChildAdded.e2e.js create mode 100644 packages/database/e2e/query/onChildChanged.e2e.js create mode 100644 packages/database/e2e/query/onChildMoved.e2e.js create mode 100644 packages/database/e2e/query/onChildRemoved.e2e.js create mode 100644 packages/database/e2e/query/onValue.e2e.js create mode 100644 packages/database/lib/modular/index.d.ts create mode 100644 packages/database/lib/modular/index.js create mode 100644 packages/database/lib/modular/query.d.ts create mode 100644 packages/database/lib/modular/query.js create mode 100644 packages/database/lib/modular/transaction.d.ts create mode 100644 packages/database/lib/modular/transaction.js diff --git a/packages/database/e2e/DatabaseStatics.e2e.js b/packages/database/e2e/DatabaseStatics.e2e.js index d8b9cf073d..fefe52aaa4 100644 --- a/packages/database/e2e/DatabaseStatics.e2e.js +++ b/packages/database/e2e/DatabaseStatics.e2e.js @@ -20,48 +20,105 @@ const { PATH, wipe } = require('./helpers'); const TEST_PATH = `${PATH}/statics`; describe('database.X', function () { - after(function () { - return wipe(TEST_PATH); - }); + describe('v8 compatibility', function () { + after(function () { + return wipe(TEST_PATH); + }); + + describe('ServerValue.TIMESTAMP', function () { + it('returns a valid object', function () { + const { TIMESTAMP } = firebase.database.ServerValue; + should.equal(Object.keys(TIMESTAMP).length, 1); + TIMESTAMP.should.have.property('.sv'); + TIMESTAMP['.sv'].should.eql('timestamp'); + }); + }); + + describe('ServerValue.increment', function () { + it('returns a valid object', function () { + const incrementObject = firebase.database.ServerValue.increment(1); + should.equal(Object.keys(incrementObject).length, 1); + incrementObject.should.have.property('.sv'); + incrementObject['.sv'].should.have.property('increment'); + }); + + it('increments on the server', async function () { + const ref = firebase.database().ref(`${TEST_PATH}/increment`); + + await ref.set({ increment: 0 }); + + const res1 = await ref.once('value'); + res1.val().increment.should.equal(0); + + await ref.set({ increment: firebase.database.ServerValue.increment(1) }); + + const res2 = await ref.once('value'); + res2.val().increment.should.equal(1); + }); - describe('ServerValue.TIMESTAMP', function () { - it('returns a valid object', function () { - const { TIMESTAMP } = firebase.database.ServerValue; - should.equal(Object.keys(TIMESTAMP).length, 1); - TIMESTAMP.should.have.property('.sv'); - TIMESTAMP['.sv'].should.eql('timestamp'); + it('increments on the server when no value is present', async function () { + const ref = firebase.database().ref(`${TEST_PATH}/increment-empty`); + + await ref.set({ increment: firebase.database.ServerValue.increment(2) }); + + const res = await ref.once('value'); + res.val().increment.should.equal(2); + }); }); }); - describe('ServerValue.increment', function () { - it('returns a valid object', function () { - const incrementObject = firebase.database.ServerValue.increment(1); - should.equal(Object.keys(incrementObject).length, 1); - incrementObject.should.have.property('.sv'); - incrementObject['.sv'].should.have.property('increment'); + describe('modular', function () { + after(function () { + return wipe(TEST_PATH); }); - it('increments on the server', async function () { - const ref = firebase.database().ref(`${TEST_PATH}/increment`); + describe('serverTimestamp', function () { + it('returns a valid object', function () { + const { serverTimestamp } = databaseModular; + const timestamp = serverTimestamp(); - await ref.set({ increment: 0 }); + should.equal(Object.keys(timestamp).length, 1); + timestamp.should.have.property('.sv'); + timestamp['.sv'].should.eql('timestamp'); + }); + }); - const res1 = await ref.once('value'); - res1.val().increment.should.equal(0); + describe('increment', function () { + it('returns a valid object', function () { + const { increment } = databaseModular; - await ref.set({ increment: firebase.database.ServerValue.increment(1) }); + const incrementObject = increment(1); + should.equal(Object.keys(incrementObject).length, 1); + incrementObject.should.have.property('.sv'); + incrementObject['.sv'].should.have.property('increment'); + }); - const res2 = await ref.once('value'); - res2.val().increment.should.equal(1); - }); + it('increments on the server', async function () { + const { getDatabase, ref, set, get, increment } = databaseModular; + + const dbRef = ref(getDatabase(), `${TEST_PATH}/increment`); + + await set(dbRef, { increment: 0 }); + + const res1 = await get(dbRef); + res1.val().increment.should.equal(0); + + await set(dbRef, { increment: increment(1) }); + + const res2 = await get(dbRef); + res2.val().increment.should.equal(1); + }); + + it('increments on the server when no value is present', async function () { + const { getDatabase, ref, set, get, increment } = databaseModular; - it('increments on the server when no value is present', async function () { - const ref = firebase.database().ref(`${TEST_PATH}/increment-empty`); + const dbRef = ref(getDatabase(), `${TEST_PATH}/increment-empty`); - await ref.set({ increment: firebase.database.ServerValue.increment(2) }); + await set(dbRef, { increment: increment(2) }); - const res = await ref.once('value'); - res.val().increment.should.equal(2); + const res = await get(dbRef); + res.val().increment.should.equal(2); + }); }); }); }); diff --git a/packages/database/e2e/database.e2e.js b/packages/database/e2e/database.e2e.js index 0cb783e61e..81e69ef0ae 100644 --- a/packages/database/e2e/database.e2e.js +++ b/packages/database/e2e/database.e2e.js @@ -16,196 +16,424 @@ */ describe('database()', function () { - describe('namespace', function () { - it('accessible from firebase.app()', function () { - const app = firebase.app(); - should.exist(app.database); - app.database().app.should.eql(app); + describe('v8 compatibility', function () { + describe('namespace', function () { + it('accessible from firebase.app()', function () { + const app = firebase.app(); + should.exist(app.database); + app.database().app.should.eql(app); + }); + + it('supports multiple apps', async function () { + firebase.database().app.name.should.eql('[DEFAULT]'); + + firebase + .database(firebase.app('secondaryFromNative')) + .app.name.should.eql('secondaryFromNative'); + + firebase.app('secondaryFromNative').database().app.name.should.eql('secondaryFromNative'); + }); }); - it('supports multiple apps', async function () { - firebase.database().app.name.should.eql('[DEFAULT]'); + describe('ref()', function () { + it('throws if path is not a string', async function () { + try { + firebase.database().ref({ foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'path' must be a string value"); + return Promise.resolve(); + } + }); - firebase - .database(firebase.app('secondaryFromNative')) - .app.name.should.eql('secondaryFromNative'); + it('throws if path is not a valid string', async function () { + try { + firebase.database().ref('$$$$$'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "Paths must be non-empty strings and can't contain #, $, [, ], ' or ?", + ); + return Promise.resolve(); + } + }); + }); - firebase.app('secondaryFromNative').database().app.name.should.eql('secondaryFromNative'); + describe('refFromURL()', function () { + it('throws if url is not a url', async function () { + try { + firebase.database().refFromURL('foobar'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'url' must be a valid database URL"); + return Promise.resolve(); + } + }); + + it('throws if url from a different domain', async function () { + try { + firebase.database().refFromURL('https://foobar.firebaseio.com'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'url' must be the same domain as the current instance"); + return Promise.resolve(); + } + }); + + it('returns a reference', async function () { + const ref1 = firebase.database().refFromURL(firebase.database()._customUrlOrRegion); + const ref2 = firebase + .database() + .refFromURL(`${firebase.database()._customUrlOrRegion}/foo/bar`); + const ref3 = firebase + .database() + .refFromURL(`${firebase.database()._customUrlOrRegion}/foo/bar?baz=foo`); + should.equal(ref1.path, '/'); + should.equal(ref2.path, 'foo/bar'); + should.equal(ref3.path, 'foo/bar'); + }); }); - }); - it('supports custom database URL', async function () { - firebase.database().app.name.should.eql('[DEFAULT]'); + describe('goOnline()', function () { + it('calls goOnline successfully', async function () { + await firebase.database().goOnline(); + }); + }); - firebase - .database(firebase.app('secondaryFromNative')) - .app.name.should.eql('secondaryFromNative'); + describe('goOffline()', function () { + it('calls goOffline successfully', async function () { + // await Utils.sleep(5000); + await firebase.database().goOffline(); - firebase.app('secondaryFromNative').database().app.name.should.eql('secondaryFromNative'); - }); + await firebase.database().goOnline(); + }); + }); + + describe('setPersistenceEnabled()', function () { + it('throws if enabled is not a boolean', async function () { + try { + firebase.database().setPersistenceEnabled('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'enabled' must be a boolean value"); + return Promise.resolve(); + } + }); - describe('ref()', function () { - it('throws if path is not a string', async function () { - try { - firebase.database().ref({ foo: 'bar' }); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'path' must be a string value"); - return Promise.resolve(); - } - }); - - it('throws if path is not a valid string', async function () { - try { - firebase.database().ref('$$$$$'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - "Paths must be non-empty strings and can't contain #, $, [, ], ' or ?", - ); - return Promise.resolve(); - } + it('calls setPersistenceEnabled successfully', async function () { + firebase.database().setPersistenceEnabled(true); + firebase.database().setPersistenceEnabled(false); + }); }); - }); - describe('refFromURL()', function () { - it('throws if url is not a url', async function () { - try { - firebase.database().refFromURL('foobar'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'url' must be a valid database URL"); - return Promise.resolve(); - } - }); - - it('throws if url from a different domain', async function () { - try { - firebase.database().refFromURL('https://foobar.firebaseio.com'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'url' must be the same domain as the current instance"); - return Promise.resolve(); - } - }); - - it('returns a reference', async function () { - const ref1 = firebase.database().refFromURL(firebase.database()._customUrlOrRegion); - const ref2 = firebase - .database() - .refFromURL(`${firebase.database()._customUrlOrRegion}/foo/bar`); - const ref3 = firebase - .database() - .refFromURL(`${firebase.database()._customUrlOrRegion}/foo/bar?baz=foo`); - should.equal(ref1.path, '/'); - should.equal(ref2.path, 'foo/bar'); - should.equal(ref3.path, 'foo/bar'); + describe('setLoggingEnabled()', function () { + it('throws if enabled is not a boolean', async function () { + try { + firebase.database().setLoggingEnabled('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'enabled' must be a boolean value"); + return Promise.resolve(); + } + }); + + it('calls setLoggingEnabled successfully', async function () { + firebase.database().setLoggingEnabled(true); + firebase.database().setLoggingEnabled(false); + }); }); - }); - describe('goOnline()', function () { - it('calls goOnline successfully', async function () { - await firebase.database().goOnline(); + describe('setPersistenceCacheSizeBytes()', function () { + it('throws if bytes is not a number', async function () { + try { + firebase.database().setPersistenceCacheSizeBytes('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'bytes' must be a number value"); + return Promise.resolve(); + } + }); + + it('throws if bytes is less than 1MB', async function () { + try { + firebase.database().setPersistenceCacheSizeBytes(1234); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'bytes' must be greater than 1048576 bytes (1MB)"); + return Promise.resolve(); + } + }); + + it('throws if bytes is greater than 10MB', async function () { + try { + firebase.database().setPersistenceCacheSizeBytes(100000000000000); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'bytes' must be less than 104857600 bytes (100MB)"); + return Promise.resolve(); + } + }); + + it('calls setPersistenceCacheSizeBytes successfully', async function () { + firebase.database().setPersistenceCacheSizeBytes(1048576); // 1mb + }); }); - }); - describe('goOffline()', function () { - it('calls goOffline successfully', async function () { - // await Utils.sleep(5000); - await firebase.database().goOffline(); + describe('getServerTime()', function () { + it('returns a valid date', async function () { + const date = firebase.database().getServerTime(); + date.getDate.should.be.Function(); + }); + }); - await firebase.database().goOnline(); + describe('handles invalid references()', function () { + it('returns a valid date', async function () { + try { + firebase.database().ref('$'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "Paths must be non-empty strings and can't contain #, $, [, ], ' or ?", + ); + return Promise.resolve(); + } + }); }); }); - describe('setPersistenceEnabled()', function () { - it('throws if enabled is not a boolean', async function () { - try { - firebase.database().setPersistenceEnabled('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'enabled' must be a boolean value"); - return Promise.resolve(); - } + describe('modular', function () { + describe('namespace', function () { + it('accessible from getDatabase', function () { + const { getDatabase } = databaseModular; + + const app = firebase.app(); + const database = getDatabase(app); + should.exist(app.database); + database.app.should.eql(app); + }); + + it('supports multiple apps', async function () { + const { getDatabase } = databaseModular; + const database = getDatabase(); + const secondaryDatabase = getDatabase(firebase.app('secondaryFromNative')); + + database.app.name.should.eql('[DEFAULT]'); + + secondaryDatabase.app.name.should.eql('secondaryFromNative'); + + firebase.app('secondaryFromNative').database().app.name.should.eql('secondaryFromNative'); + }); }); - it('calls setPersistenceEnabled successfully', async function () { - firebase.database().setPersistenceEnabled(true); - firebase.database().setPersistenceEnabled(false); + describe('ref()', function () { + it('throws if path is not a string', async function () { + const { getDatabase, ref } = databaseModular; + const db = getDatabase(); + + try { + ref(db, { foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'path' must be a string value"); + return Promise.resolve(); + } + }); + + it('throws if path is not a valid string', async function () { + const { getDatabase, ref } = databaseModular; + const db = getDatabase(); + + try { + ref(db, '$$$$$'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "Paths must be non-empty strings and can't contain #, $, [, ], ' or ?", + ); + return Promise.resolve(); + } + }); }); - }); - describe('setLoggingEnabled()', function () { - it('throws if enabled is not a boolean', async function () { - try { - firebase.database().setLoggingEnabled('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'enabled' must be a boolean value"); - return Promise.resolve(); - } + describe('refFromURL()', function () { + it('throws if url is not a url', async function () { + const { getDatabase, refFromURL } = databaseModular; + const db = getDatabase(); + + try { + refFromURL(db, 'foobar'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'url' must be a valid database URL"); + return Promise.resolve(); + } + }); + + it('throws if url from a different domain', async function () { + const { getDatabase, refFromURL } = databaseModular; + const db = getDatabase(); + + try { + refFromURL(db, 'https://foobar.firebaseio.com'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'url' must be the same domain as the current instance"); + return Promise.resolve(); + } + }); + + it('returns a reference', async function () { + const { getDatabase, refFromURL } = databaseModular; + const db = getDatabase(); + + const ref1 = refFromURL(db, db._customUrlOrRegion); + const ref2 = refFromURL(db, `${db._customUrlOrRegion}/foo/bar`); + const ref3 = refFromURL(db, `${db._customUrlOrRegion}/foo/bar?baz=foo`); + + should.equal(ref1.path, '/'); + should.equal(ref2.path, 'foo/bar'); + should.equal(ref3.path, 'foo/bar'); + }); }); - it('calls setLoggingEnabled successfully', async function () { - firebase.database().setLoggingEnabled(true); - firebase.database().setLoggingEnabled(false); + describe('goOnline()', function () { + it('calls goOnline successfully', async function () { + const { getDatabase, goOnline } = databaseModular; + await goOnline(getDatabase()); + }); }); - }); - describe('setPersistenceCacheSizeBytes()', function () { - it('throws if bytes is not a number', async function () { - try { - firebase.database().setPersistenceCacheSizeBytes('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'bytes' must be a number value"); - return Promise.resolve(); - } + describe('goOffline()', function () { + it('calls goOffline successfully', async function () { + const { getDatabase, goOffline, goOnline } = databaseModular; + const db = getDatabase(); + + // await Utils.sleep(5000); + await goOffline(db); + + await goOnline(db); + }); }); - it('throws if bytes is less than 1MB', async function () { - try { - firebase.database().setPersistenceCacheSizeBytes(1234); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'bytes' must be greater than 1048576 bytes (1MB)"); - return Promise.resolve(); - } + describe('setPersistenceEnabled()', function () { + it('throws if enabled is not a boolean', async function () { + const { getDatabase, setPersistenceEnabled } = databaseModular; + const db = getDatabase(); + + try { + setPersistenceEnabled(db, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'enabled' must be a boolean value"); + return Promise.resolve(); + } + }); + + it('calls setPersistenceEnabled successfully', async function () { + const { getDatabase, setPersistenceEnabled } = databaseModular; + const db = getDatabase(); + + setPersistenceEnabled(db, true); + setPersistenceEnabled(db, false); + }); }); - it('throws if bytes is greater than 10MB', async function () { - try { - firebase.database().setPersistenceCacheSizeBytes(100000000000000); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'bytes' must be less than 104857600 bytes (100MB)"); - return Promise.resolve(); - } + describe('setLoggingEnabled()', function () { + it('throws if enabled is not a boolean', async function () { + const { getDatabase, setLoggingEnabled } = databaseModular; + const db = getDatabase(); + + try { + setLoggingEnabled(db, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'enabled' must be a boolean value"); + return Promise.resolve(); + } + }); + + it('calls setLoggingEnabled successfully', async function () { + const { getDatabase, setLoggingEnabled } = databaseModular; + const db = getDatabase(); + + setLoggingEnabled(db, true); + setLoggingEnabled(db, false); + }); }); - it('calls setPersistenceCacheSizeBytes successfully', async function () { - firebase.database().setPersistenceCacheSizeBytes(1048576); // 1mb + describe('setPersistenceCacheSizeBytes()', function () { + it('throws if bytes is not a number', async function () { + const { getDatabase, setPersistenceCacheSizeBytes } = databaseModular; + const db = getDatabase(); + + try { + setPersistenceCacheSizeBytes(db, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'bytes' must be a number value"); + return Promise.resolve(); + } + }); + + it('throws if bytes is less than 1MB', async function () { + const { getDatabase, setPersistenceCacheSizeBytes } = databaseModular; + const db = getDatabase(); + + try { + setPersistenceCacheSizeBytes(db, 1234); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'bytes' must be greater than 1048576 bytes (1MB)"); + return Promise.resolve(); + } + }); + + it('throws if bytes is greater than 10MB', async function () { + const { getDatabase, setPersistenceCacheSizeBytes } = databaseModular; + const db = getDatabase(); + + try { + setPersistenceCacheSizeBytes(db, 100000000000000); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'bytes' must be less than 104857600 bytes (100MB)"); + return Promise.resolve(); + } + }); + + it('calls setPersistenceCacheSizeBytes successfully', async function () { + const { getDatabase, setPersistenceCacheSizeBytes } = databaseModular; + const db = getDatabase(); + + setPersistenceCacheSizeBytes(db, 1048576); // 1mb + }); }); - }); - describe('getServerTime()', function () { - it('returns a valid date', async function () { - const date = firebase.database().getServerTime(); - date.getDate.should.be.Function(); + describe('getServerTime()', function () { + it('returns a valid date', async function () { + const { getDatabase, getServerTime } = databaseModular; + const db = getDatabase(); + + const date = getServerTime(db); + date.getDate.should.be.Function(); + }); }); - }); - describe('handles invalid references()', function () { - it('returns a valid date', async function () { - try { - firebase.database().ref('$'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - "Paths must be non-empty strings and can't contain #, $, [, ], ' or ?", - ); - return Promise.resolve(); - } + describe('handles invalid references()', function () { + it('returns a valid date', async function () { + const { getDatabase, ref } = databaseModular; + const db = getDatabase(); + + try { + ref(db, '$'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "Paths must be non-empty strings and can't contain #, $, [, ], ' or ?", + ); + return Promise.resolve(); + } + }); }); }); }); diff --git a/packages/database/e2e/internal/connected.e2e.js b/packages/database/e2e/internal/connected.e2e.js index dc5209a291..86faa1183a 100644 --- a/packages/database/e2e/internal/connected.e2e.js +++ b/packages/database/e2e/internal/connected.e2e.js @@ -22,38 +22,87 @@ describe("database().ref('.info/connected')", function () { before(async function () { await firebase.database().goOnline(); }); - after(async function () { - await firebase.database().goOnline(); - }); - xit('returns true when used with once', async function () { - const snapshot = await firebase.database().ref('.info/connected').once('value'); - snapshot.val().should.equal(true); - }); + describe('v8 compatibility', function () { + after(async function () { + await firebase.database().goOnline(); + }); + + xit('returns true when used with once', async function () { + const snapshot = await firebase.database().ref('.info/connected').once('value'); + snapshot.val().should.equal(true); + }); - xit('returns true when used with once with a previous call', async function () { - await firebase.database().ref(`${TEST_PATH}/foo`).once('value'); - const snapshot = await firebase.database().ref('.info/connected').once('value'); - snapshot.val().should.equal(true); + xit('returns true when used with once with a previous call', async function () { + await firebase.database().ref(`${TEST_PATH}/foo`).once('value'); + const snapshot = await firebase.database().ref('.info/connected').once('value'); + snapshot.val().should.equal(true); + }); + + // FIXME on android this can work against the emulator + // on iOS it doesn't work at all ? + xit('subscribes to online state', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref('.info/connected'); + const handler = $ => { + callback($.val()); + }; + + ref.on('value', handler); + await firebase.database().goOffline(); + await Utils.sleep(1000); // FIXME why is this sleep needed here? callback is called immediately + await firebase.database().goOnline(); + ref.off('value', handler); + + await Utils.spyToBeCalledTimesAsync(callback, 2); + callback.getCall(0).args[0].should.equal(false); + callback.getCall(1).args[0].should.equal(true); + }); }); - // FIXME on android this can work against the emulator - // on iOS it doesn't work at all ? - xit('subscribes to online state', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref('.info/connected'); - const handler = $ => { - callback($.val()); - }; - - ref.on('value', handler); - await firebase.database().goOffline(); - await Utils.sleep(1000); // FIXME why is this sleep needed here? callback is called immediately - await firebase.database().goOnline(); - ref.off('value', handler); + describe('modular', function () { + after(async function () { + const { getDatabase, goOnline } = databaseModular; + + await goOnline(getDatabase()); + }); + + xit('returns true when used with once', async function () { + const { getDatabase, ref, get } = databaseModular; + + const snapshot = await get(ref(getDatabase(), '.info/connected'), dbRef); + snapshot.val().should.equal(true); + }); + + xit('returns true when used with once with a previous call', async function () { + const { getDatabase, ref, get } = databaseModular; + + await get(ref(getDatabase(), `${TEST_PATH}/foo`)); + const snapshot = await firebase.database().ref('.info/connected').once('value'); + snapshot.val().should.equal(true); + }); + + // FIXME on android this can work against the emulator + // on iOS it doesn't work at all ? + xit('subscribes to online state', async function () { + const { getDatabase, ref, onValue, goOffline, goOnline, off } = databaseModular; + const db = getDatabase(); + + const callback = sinon.spy(); + const dbRef = ref(db, '.info/connected'); + const handler = $ => { + callback($.val()); + }; + + onValue(dbRef, handler); + await goOffline(db); + await Utils.sleep(1000); // FIXME why is this sleep needed here? callback is called immediately + await goOnline(db); + off('value', handler); - await Utils.spyToBeCalledTimesAsync(callback, 2); - callback.getCall(0).args[0].should.equal(false); - callback.getCall(1).args[0].should.equal(true); + await Utils.spyToBeCalledTimesAsync(callback, 2); + callback.getCall(0).args[0].should.equal(false); + callback.getCall(1).args[0].should.equal(true); + }); }); }); diff --git a/packages/database/e2e/internal/serverTimeOffset.e2e.js b/packages/database/e2e/internal/serverTimeOffset.e2e.js index d4b7f86b20..32954fe28f 100644 --- a/packages/database/e2e/internal/serverTimeOffset.e2e.js +++ b/packages/database/e2e/internal/serverTimeOffset.e2e.js @@ -16,9 +16,21 @@ */ describe("database().ref('.info/serverTimeOffset')", function () { - it('returns a valid number value', async function () { - const snapshot = await firebase.database().ref('.info/serverTimeOffset').once('value'); + describe('v8 compatibility', function () { + it('returns a valid number value', async function () { + const snapshot = await firebase.database().ref('.info/serverTimeOffset').once('value'); - should.equal(typeof snapshot.val(), 'number'); + should.equal(typeof snapshot.val(), 'number'); + }); + }); + + describe('modular', function () { + it('returns a valid number value', async function () { + const { getDatabase, ref, get } = databaseModular; + + const snapshot = await get(ref(getDatabase(), '.info/serverTimeOffset')); + + should.equal(typeof snapshot.val(), 'number'); + }); }); }); diff --git a/packages/database/e2e/issues.e2e.js b/packages/database/e2e/issues.e2e.js index 4945a6cbea..85af01eebc 100644 --- a/packages/database/e2e/issues.e2e.js +++ b/packages/database/e2e/issues.e2e.js @@ -27,137 +27,289 @@ describe('database issues', function () { await wipe(TEST_PATH); }); - // FIXME requires a second database set up locally, full app initialization etc - xit('#2813 should return a null snapshot key if path is root', async function () { - firebase.database('https://react-native-firebase-testing-db2.firebaseio.com'); - const ref = firebase - .app() - .database('https://react-native-firebase-testing-db2.firebaseio.com') - .ref(); - const snapshot = await ref.once('value'); - should.equal(snapshot.key, null); - }); + describe('v8 compatibility', function () { + // FIXME requires a second database set up locally, full app initialization etc + xit('#2813 should return a null snapshot key if path is root', async function () { + firebase.database('https://react-native-firebase-testing-db2.firebaseio.com'); + const ref = firebase + .app() + .database('https://react-native-firebase-testing-db2.firebaseio.com') + .ref(); + const snapshot = await ref.once('value'); + should.equal(snapshot.key, null); + }); - it('#2833 should not mutate modifiers ordering', async function () { - const callback = sinon.spy(); - const testRef = firebase - .database() - .ref() - .child(TEST_PATH) - .orderByChild('disabled') - .equalTo(false); + it('#2833 should not mutate modifiers ordering', async function () { + const callback = sinon.spy(); + const testRef = firebase + .database() + .ref() + .child(TEST_PATH) + .orderByChild('disabled') + .equalTo(false); - testRef._modifiers.toString().should.be.a.String(); - testRef._modifiers.toArray()[0].name.should.equal('orderByChild'); + testRef._modifiers.toString().should.be.a.String(); + testRef._modifiers.toArray()[0].name.should.equal('orderByChild'); - testRef.on('value', snapshot => { - callback(snapshot.val()); - }); + testRef.on('value', snapshot => { + callback(snapshot.val()); + }); - await Utils.spyToBeCalledOnceAsync(callback, 3000); + await Utils.spyToBeCalledOnceAsync(callback, 3000); - testRef.off('value'); - }); - - it('#100 array should return null where key is missing', async function () { - const ref = firebase.database().ref(`${TEST_PATH}/issue_100`); - - const data = { - 1: { - someKey: 'someValue', - someOtherKey: 'someOtherValue', - }, - 2: { - someKey: 'someValue', - someOtherKey: 'someOtherValue', - }, - 3: { - someKey: 'someValue', - someOtherKey: 'someOtherValue', - }, - }; - - await ref.set(data); - const snapshot = await ref.once('value'); - - snapshot - .val() - .should.eql( - jet.contextify([ - null, - jet.contextify(data[1]), - jet.contextify(data[2]), - jet.contextify(data[3]), - ]), - ); - }); + testRef.off('value'); + }); - describe('#108 filters correctly by float values', function () { - it('returns filtered results', async function () { - const ref = firebase.database().ref(`${TEST_PATH}/issue_108/filter`); + it('#100 array should return null where key is missing', async function () { + const ref = firebase.database().ref(`${TEST_PATH}/issue_100`); const data = { - foobar: { - name: 'Foobar Pizzas', - latitude: 34.1013717, + 1: { + someKey: 'someValue', + someOtherKey: 'someOtherValue', }, - notTheFoobar: { - name: "Not the pizza you're looking for", - latitude: 34.456787, + 2: { + someKey: 'someValue', + someOtherKey: 'someOtherValue', }, - notAFloat: { - name: 'Not a float', - latitude: 37, + 3: { + someKey: 'someValue', + someOtherKey: 'someOtherValue', }, }; await ref.set(data); - const snapshot = await ref - .orderByChild('latitude') - .startAt(34.00867000999119) - .endAt(34.17462960866099) - .once('value'); + const snapshot = await ref.once('value'); + + snapshot + .val() + .should.eql( + jet.contextify([ + null, + jet.contextify(data[1]), + jet.contextify(data[2]), + jet.contextify(data[3]), + ]), + ); + }); + + describe('#108 filters correctly by float values', function () { + it('returns filtered results', async function () { + const ref = firebase.database().ref(`${TEST_PATH}/issue_108/filter`); + + const data = { + foobar: { + name: 'Foobar Pizzas', + latitude: 34.1013717, + }, + notTheFoobar: { + name: "Not the pizza you're looking for", + latitude: 34.456787, + }, + notAFloat: { + name: 'Not a float', + latitude: 37, + }, + }; + + await ref.set(data); + const snapshot = await ref + .orderByChild('latitude') + .startAt(34.00867000999119) + .endAt(34.17462960866099) + .once('value'); - const val = snapshot.val(); - val.foobar.should.eql(jet.contextify(data.foobar)); + const val = snapshot.val(); + val.foobar.should.eql(jet.contextify(data.foobar)); - should.equal(Object.keys(val).length, 1); + should.equal(Object.keys(val).length, 1); + }); + + it('returns correct results when not using float values', async function () { + const ref = firebase.database().ref(`${TEST_PATH}/issue_108/integer`); + + const data = { + foobar: { + name: 'Foobar Pizzas', + latitude: 34.1013717, + }, + notTheFoobar: { + name: "Not the pizza you're looking for", + latitude: 34.456787, + }, + notAFloat: { + name: 'Not a float', + latitude: 37, + }, + }; + + await ref.set(data); + const snapshot = await ref.orderByChild('latitude').equalTo(37).once('value'); + + const val = snapshot.val(); + + val.notAFloat.should.eql(jet.contextify(data.notAFloat)); + + should.equal(Object.keys(val).length, 1); + }); + }); + + it('#489 reutrns long numbers correctly', async function () { + const LONG = 1508777379000; + const ref = firebase.database().ref(`${TEST_PATH}/issue_489`); + await ref.set(LONG); + const snapshot = await ref.once('value'); + snapshot.val().should.eql(LONG); + }); + }); + + describe('modular', function () { + // FIXME requires a second database set up locally, full app initialization etc + xit('#2813 should return a null snapshot key if path is root', async function () { + const { getDatabase, ref, get } = databaseModular; + + const db = getDatabase( + /* takes default firebase.app() */ null, + 'https://react-native-firebase-testing-db2.firebaseio.com', + ); + const dbRef = ref(db); + const snapshot = await get(dbRef); + should.equal(snapshot.key, null); }); - it('returns correct results when not using float values', async function () { - const ref = firebase.database().ref(`${TEST_PATH}/issue_108/integer`); + it('#2833 should not mutate modifiers ordering', async function () { + const { getDatabase, ref, child, query, equalTo, orderByChild, onValue } = databaseModular; + + const callback = sinon.spy(); + const testRef = query( + child(ref(getDatabase()), TEST_PATH), + orderByChild('disabled'), + equalTo(false), + ); + + testRef._modifiers.toString().should.be.a.String(); + testRef._modifiers.toArray()[0].name.should.equal('orderByChild'); + + const unsubscribe = onValue(testRef, snapshot => { + callback(snapshot.val()); + }); + + await Utils.spyToBeCalledOnceAsync(callback, 3000); + + unsubscribe(); + }); + + it('#100 array should return null where key is missing', async function () { + const { getDatabase, ref, set, get } = databaseModular; + + const dbRef = ref(getDatabase(), `${TEST_PATH}/issue_100`); const data = { - foobar: { - name: 'Foobar Pizzas', - latitude: 34.1013717, + 1: { + someKey: 'someValue', + someOtherKey: 'someOtherValue', }, - notTheFoobar: { - name: "Not the pizza you're looking for", - latitude: 34.456787, + 2: { + someKey: 'someValue', + someOtherKey: 'someOtherValue', }, - notAFloat: { - name: 'Not a float', - latitude: 37, + 3: { + someKey: 'someValue', + someOtherKey: 'someOtherValue', }, }; - await ref.set(data); - const snapshot = await ref.orderByChild('latitude').equalTo(37).once('value'); + await set(dbRef, data); + const snapshot = await get(dbRef); + + snapshot + .val() + .should.eql( + jet.contextify([ + null, + jet.contextify(data[1]), + jet.contextify(data[2]), + jet.contextify(data[3]), + ]), + ); + }); + + describe('#108 filters correctly by float values', function () { + it('returns filtered results', async function () { + const { getDatabase, ref, set, get, query, orderByChild, startAt, endAt } = databaseModular; - const val = snapshot.val(); + const dbRef = ref(getDatabase(), `${TEST_PATH}/issue_108/filter`); - val.notAFloat.should.eql(jet.contextify(data.notAFloat)); + const data = { + foobar: { + name: 'Foobar Pizzas', + latitude: 34.1013717, + }, + notTheFoobar: { + name: "Not the pizza you're looking for", + latitude: 34.456787, + }, + notAFloat: { + name: 'Not a float', + latitude: 37, + }, + }; - should.equal(Object.keys(val).length, 1); + await set(dbRef, data); + const snapshot = await get( + query( + dbRef, + orderByChild('latitude'), + startAt(34.00867000999119), + endAt(34.17462960866099), + ), + ); + + const val = snapshot.val(); + val.foobar.should.eql(jet.contextify(data.foobar)); + + should.equal(Object.keys(val).length, 1); + }); + + it('returns correct results when not using float values', async function () { + const { getDatabase, ref, set, get, query, orderByChild, equalTo } = databaseModular; + + const dbRef = ref(getDatabase(), `${TEST_PATH}/issue_108/integer`); + + const data = { + foobar: { + name: 'Foobar Pizzas', + latitude: 34.1013717, + }, + notTheFoobar: { + name: "Not the pizza you're looking for", + latitude: 34.456787, + }, + notAFloat: { + name: 'Not a float', + latitude: 37, + }, + }; + + await set(dbRef, data); + const snapshot = await get(query(dbRef, orderByChild('latitude'), equalTo(37))); + + const val = snapshot.val(); + + val.notAFloat.should.eql(jet.contextify(data.notAFloat)); + + should.equal(Object.keys(val).length, 1); + }); }); - }); - it('#489 reutrns long numbers correctly', async function () { - const LONG = 1508777379000; - const ref = firebase.database().ref(`${TEST_PATH}/issue_489`); - await ref.set(LONG); - const snapshot = await ref.once('value'); - snapshot.val().should.eql(LONG); + it('#489 reutrns long numbers correctly', async function () { + const { getDatabase, ref, set, get } = databaseModular; + + const LONG = 1508777379000; + const dbRef = ref(getDatabase(), `${TEST_PATH}/issue_489`); + await set(dbRef, LONG); + const snapshot = await get(dbRef); + snapshot.val().should.eql(LONG); + }); }); }); diff --git a/packages/database/e2e/onDisconnect/cancel.e2e.js b/packages/database/e2e/onDisconnect/cancel.e2e.js index 8de9da3c01..bcde6b2d7d 100644 --- a/packages/database/e2e/onDisconnect/cancel.e2e.js +++ b/packages/database/e2e/onDisconnect/cancel.e2e.js @@ -24,49 +24,111 @@ describe('database().ref().onDisconnect().cancel()', function () { await wipe(TEST_PATH); }); - afterEach(async function () { - // Ensures the db is online before running each test - await firebase.database().goOnline(); - }); + describe('v8 compatibility', function () { + afterEach(async function () { + // Ensures the db is online before running each test + await firebase.database().goOnline(); + }); - it('throws if onComplete is not a function', function () { - const ref = firebase.database().ref(TEST_PATH).onDisconnect(); - try { - ref.cancel('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } - }); + it('throws if onComplete is not a function', function () { + const ref = firebase.database().ref(TEST_PATH).onDisconnect(); + try { + ref.cancel('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + it('cancels all previously queued events', async function () { + const ref = firebase.database().ref(TEST_PATH); + + await ref.set('foobar'); + const value = Date.now(); - it('cancels all previously queued events', async function () { - const ref = firebase.database().ref(TEST_PATH); + await ref.onDisconnect().set(value); + await ref.onDisconnect().cancel(); + await firebase.database().goOffline(); + await firebase.database().goOnline(); - await ref.set('foobar'); - const value = Date.now(); + const snapshot = await ref.once('value'); + snapshot.val().should.eql('foobar'); + }); - await ref.onDisconnect().set(value); - await ref.onDisconnect().cancel(); - await firebase.database().goOffline(); - await firebase.database().goOnline(); + it('calls back to the onComplete function', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH); - const snapshot = await ref.once('value'); - snapshot.val().should.eql('foobar'); + // Set an initial value + await ref.set('foo'); + + await ref.onDisconnect().set('bar'); + await ref.onDisconnect().cancel(callback); + await firebase.database().goOffline(); + await firebase.database().goOnline(); + + callback.should.be.calledOnce(); + }); }); - it('calls back to the onComplete function', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + afterEach(async function () { + const { getDatabase, goOnline } = databaseModular; + const db = getDatabase(); + + // Ensures the db is online before running each test + await goOnline(db); + }); + + it('throws if onComplete is not a function', function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const disconnect = onDisconnect(dbRef); + try { + disconnect.cancel('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + it('cancels all previously queued events', async function () { + const { getDatabase, ref, onDisconnect, goOffline, goOnline, get } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + await dbRef.set('foobar'); + const value = Date.now(); + + await onDisconnect(dbRef).set(value); + await onDisconnect(dbRef).cancel(); + await goOffline(db); + await goOnline(db); + + const snapshot = await get(dbRef); + snapshot.val().should.eql('foobar'); + }); + + it('calls back to the onComplete function', async function () { + const { getDatabase, ref, onDisconnect, set, goOffline, goOnline } = databaseModular; + const db = getDatabase(); + + const callback = sinon.spy(); + const dbRef = ref(db, TEST_PATH); - // Set an initial value - await ref.set('foo'); + // Set an initial value + await set(dbRef, 'foo'); - await ref.onDisconnect().set('bar'); - await ref.onDisconnect().cancel(callback); - await firebase.database().goOffline(); - await firebase.database().goOnline(); + await onDisconnect(dbRef).set('bar'); + await onDisconnect(dbRef).cancel(callback); + await goOffline(db); + await goOnline(db); - callback.should.be.calledOnce(); + callback.should.be.calledOnce(); + }); }); }); diff --git a/packages/database/e2e/onDisconnect/remove.e2e.js b/packages/database/e2e/onDisconnect/remove.e2e.js index 6e0e9ab796..93963abb48 100644 --- a/packages/database/e2e/onDisconnect/remove.e2e.js +++ b/packages/database/e2e/onDisconnect/remove.e2e.js @@ -24,43 +24,102 @@ describe('database().ref().onDisconnect().remove()', function () { await wipe(TEST_PATH); }); - afterEach(async function () { - // Ensures the db is online before running each test - await firebase.database().goOnline(); - }); + describe('v8 compatibility', function () { + afterEach(async function () { + // Ensures the db is online before running each test + await firebase.database().goOnline(); + }); - it('throws if onComplete is not a function', function () { - const ref = firebase.database().ref(TEST_PATH).onDisconnect(); - try { - ref.remove('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } - }); + it('throws if onComplete is not a function', function () { + const ref = firebase.database().ref(TEST_PATH).onDisconnect(); + try { + ref.remove('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + xit('removes a node whilst offline', async function () { + const ref = firebase.database().ref(TEST_PATH).child('removeMe'); + await ref.set('foobar'); + await ref.onDisconnect().remove(); + await firebase.database().goOffline(); + await firebase.database().goOnline(); + const snapshot = await ref.once('value'); + snapshot.exists().should.eql(false); + }); + + it('calls back to the onComplete function', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH).child('removeMe'); - xit('removes a node whilst offline', async function () { - const ref = firebase.database().ref(TEST_PATH).child('removeMe'); - await ref.set('foobar'); - await ref.onDisconnect().remove(); - await firebase.database().goOffline(); - await firebase.database().goOnline(); - const snapshot = await ref.once('value'); - snapshot.exists().should.eql(false); + // Set an initial value + await ref.set('foo'); + + await ref.onDisconnect().remove(callback); + await firebase.database().goOffline(); + await firebase.database().goOnline(); + + callback.should.be.calledOnce(); + }); }); - it('calls back to the onComplete function', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH).child('removeMe'); + describe('modular', function () { + afterEach(async function () { + const { getDatabase, goOnline } = databaseModular; + const db = getDatabase(); + + // Ensures the db is online before running each test + await goOnline(db); + }); + + it('throws if onComplete is not a function', function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const disconnect = onDisconnect(dbRef); + try { + disconnect.remove('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + xit('removes a node whilst offline', async function () { + const { getDatabase, ref, child, onDisconnect, goOffline, goOnline, get } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + const childRef = child(dbRef, 'removeMe'); + + await childRef.set('foobar'); + await onDisconnect(childRef).remove(); + await goOffline(db); + await goOnline(db); + const snapshot = await get(childRef); + snapshot.exists().should.eql(false); + }); + + it('calls back to the onComplete function', async function () { + const callback = sinon.spy(); + + const { getDatabase, ref, child, onDisconnect, goOffline, goOnline } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + const childRef = child(dbRef, 'removeMe'); - // Set an initial value - await ref.set('foo'); + // Set an initial value + await childRef.set('foo'); - await ref.onDisconnect().remove(callback); - await firebase.database().goOffline(); - await firebase.database().goOnline(); + await onDisconnect(childRef).remove(callback); + await goOffline(db); + await goOnline(db); - callback.should.be.calledOnce(); + callback.should.be.calledOnce(); + }); }); }); diff --git a/packages/database/e2e/onDisconnect/set.e2e.js b/packages/database/e2e/onDisconnect/set.e2e.js index d0a034db65..ce5cc1d488 100644 --- a/packages/database/e2e/onDisconnect/set.e2e.js +++ b/packages/database/e2e/onDisconnect/set.e2e.js @@ -24,57 +24,131 @@ describe('database().ref().onDisconnect().set()', function () { await wipe(TEST_PATH); }); - afterEach(async function () { - // Ensures the db is online before running each test - await firebase.database().goOnline(); - }); + describe('v8 compatibility', function () { + afterEach(async function () { + // Ensures the db is online before running each test + await firebase.database().goOnline(); + }); - it('throws if value is not a defined', function () { - const ref = firebase.database().ref(TEST_PATH).onDisconnect(); - try { - ref.set(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'value' must be defined"); - return Promise.resolve(); - } - }); + it('throws if value is not a defined', function () { + const ref = firebase.database().ref(TEST_PATH).onDisconnect(); + try { + ref.set(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be defined"); + return Promise.resolve(); + } + }); - it('throws if onComplete is not a function', function () { - const ref = firebase.database().ref(TEST_PATH).onDisconnect(); - try { - ref.set(null, 'foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } - }); + it('throws if onComplete is not a function', function () { + const ref = firebase.database().ref(TEST_PATH).onDisconnect(); + try { + ref.set(null, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + xit('sets value when disconnected', async function () { + const ref = firebase.database().ref(TEST_PATH); + + const value = Date.now(); - xit('sets value when disconnected', async function () { - const ref = firebase.database().ref(TEST_PATH); + await ref.onDisconnect().set(value); + await firebase.database().goOffline(); + await firebase.database().goOnline(); - const value = Date.now(); + const snapshot = await ref.once('value'); + snapshot.val().should.eql(value); + }); - await ref.onDisconnect().set(value); - await firebase.database().goOffline(); - await firebase.database().goOnline(); + it('calls back to the onComplete function', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH); - const snapshot = await ref.once('value'); - snapshot.val().should.eql(value); + // Set an initial value + await ref.set('foo'); + + await ref.onDisconnect().set('bar', callback); + await firebase.database().goOffline(); + await firebase.database().goOnline(); + + callback.should.be.calledOnce(); + }); }); - it('calls back to the onComplete function', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + afterEach(async function () { + const { getDatabase, goOnline } = databaseModular; + const db = getDatabase(); + + // Ensures the db is online before running each test + await goOnline(db); + }); + + it('throws if value is not a defined', function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const disconnect = onDisconnect(dbRef); + try { + disconnect.set(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be defined"); + return Promise.resolve(); + } + }); + + it('throws if onComplete is not a function', function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const disconnect = onDisconnect(dbRef); + try { + disconnect.set(null, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + xit('sets value when disconnected', async function () { + const { getDatabase, ref, onDisconnect, goOffline, goOnline, get } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const value = Date.now(); + + await onDisconnect(dbRef).set(value); + await goOffline(db); + await goOnline(db); + + const snapshot = await get(dbRef); + snapshot.val().should.eql(value); + }); + + it('calls back to the onComplete function', async function () { + const { getDatabase, ref, onDisconnect, set, goOnline, goOffline } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const callback = sinon.spy(); - // Set an initial value - await ref.set('foo'); + // Set an initial value + await set(dbRef, 'foo'); - await ref.onDisconnect().set('bar', callback); - await firebase.database().goOffline(); - await firebase.database().goOnline(); + await onDisconnect(dbRef).set('bar', callback); + await goOffline(db); + await goOnline(db); - callback.should.be.calledOnce(); + callback.should.be.calledOnce(); + }); }); }); diff --git a/packages/database/e2e/onDisconnect/setWithPriority.e2e.js b/packages/database/e2e/onDisconnect/setWithPriority.e2e.js index fbf4b94e95..ffb8f9d3d3 100644 --- a/packages/database/e2e/onDisconnect/setWithPriority.e2e.js +++ b/packages/database/e2e/onDisconnect/setWithPriority.e2e.js @@ -24,69 +24,159 @@ describe('database().ref().onDisconnect().setWithPriority()', function () { await wipe(TEST_PATH); }); - afterEach(async function () { - // Ensures the db is online before running each test - await firebase.database().goOnline(); - }); + describe('v8 compatibility', function () { + afterEach(async function () { + // Ensures the db is online before running each test + await firebase.database().goOnline(); + }); - it('throws if value is not a defined', function () { - const ref = firebase.database().ref(TEST_PATH).onDisconnect(); - try { - ref.setWithPriority(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'value' must be defined"); - return Promise.resolve(); - } - }); + it('throws if value is not a defined', function () { + const ref = firebase.database().ref(TEST_PATH).onDisconnect(); + try { + ref.setWithPriority(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be defined"); + return Promise.resolve(); + } + }); - it('throws if priority is not a valid type', function () { - const ref = firebase.database().ref(TEST_PATH).onDisconnect(); - try { - ref.setWithPriority(null, { foo: 'bar' }); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'priority' must be a number, string or null value"); - return Promise.resolve(); - } - }); + it('throws if priority is not a valid type', function () { + const ref = firebase.database().ref(TEST_PATH).onDisconnect(); + try { + ref.setWithPriority(null, { foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'priority' must be a number, string or null value"); + return Promise.resolve(); + } + }); - it('throws if onComplete is not a function', function () { - const ref = firebase.database().ref(TEST_PATH).onDisconnect(); - try { - ref.setWithPriority(null, 1, 'foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } - }); + it('throws if onComplete is not a function', function () { + const ref = firebase.database().ref(TEST_PATH).onDisconnect(); + try { + ref.setWithPriority(null, 1, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + xit('sets value with priority when disconnected', async function () { + const ref = firebase.database().ref(TEST_PATH); + + const value = Date.now(); - xit('sets value with priority when disconnected', async function () { - const ref = firebase.database().ref(TEST_PATH); + await ref.onDisconnect().setWithPriority(value, 3); + await firebase.database().goOffline(); + await firebase.database().goOnline(); - const value = Date.now(); + const snapshot = await ref.once('value'); + snapshot.exportVal()['.value'].should.eql(value); + snapshot.exportVal()['.priority'].should.eql(3); + }); - await ref.onDisconnect().setWithPriority(value, 3); - await firebase.database().goOffline(); - await firebase.database().goOnline(); + it('calls back to the onComplete function', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH); - const snapshot = await ref.once('value'); - snapshot.exportVal()['.value'].should.eql(value); - snapshot.exportVal()['.priority'].should.eql(3); + // Set an initial value + await ref.set('foo'); + + await ref.onDisconnect().setWithPriority('bar', 2, callback); + await firebase.database().goOffline(); + await firebase.database().goOnline(); + + callback.should.be.calledOnce(); + }); }); - it('calls back to the onComplete function', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + afterEach(async function () { + const { getDatabase, goOnline } = databaseModular; + const db = getDatabase(); + + // Ensures the db is online before running each test + await goOnline(db); + }); + + it('throws if value is not a defined', function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const disconnect = onDisconnect(dbRef); + try { + disconnect.setWithPriority(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be defined"); + return Promise.resolve(); + } + }); + + it('throws if priority is not a valid type', function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const disconnect = onDisconnect(dbRef); + try { + disconnect.setWithPriority(null, { foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'priority' must be a number, string or null value"); + return Promise.resolve(); + } + }); + + it('throws if onComplete is not a function', function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const disconnect = onDisconnect(dbRef); + try { + disconnect.setWithPriority(null, 1, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + xit('sets value with priority when disconnected', async function () { + const { getDatabase, ref, onDisconnect, goOffline, goOnline, get } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const value = Date.now(); + + await onDisconnect(dbRef).setWithPriority(value, 3); + await goOffline(db); + await goOnline(db); + + const snapshot = await get(dbRef); + snapshot.exportVal()['.value'].should.eql(value); + snapshot.exportVal()['.priority'].should.eql(3); + }); + + it('calls back to the onComplete function', async function () { + const { getDatabase, ref, onDisconnect, set, goOffline, goOnline } = databaseModular; + const db = getDatabase(); + + const callback = sinon.spy(); + const dbRef = ref(db, TEST_PATH); - // Set an initial value - await ref.set('foo'); + // Set an initial value + await set(dbRef, 'foo'); - await ref.onDisconnect().setWithPriority('bar', 2, callback); - await firebase.database().goOffline(); - await firebase.database().goOnline(); + await onDisconnect(dbRef).setWithPriority('bar', 2, callback); + await goOffline(db); + await goOnline(db); - callback.should.be.calledOnce(); + callback.should.be.calledOnce(); + }); }); }); diff --git a/packages/database/e2e/onDisconnect/update.e2e.js b/packages/database/e2e/onDisconnect/update.e2e.js index 8873571cf5..aea53e7526 100644 --- a/packages/database/e2e/onDisconnect/update.e2e.js +++ b/packages/database/e2e/onDisconnect/update.e2e.js @@ -24,88 +24,201 @@ describe('database().ref().onDisconnect().update()', function () { await wipe(TEST_PATH); }); - afterEach(async function () { - // Ensures the db is online before running each test - await firebase.database().goOnline(); - }); + describe('v8 compatibility', function () { + afterEach(async function () { + // Ensures the db is online before running each test + await firebase.database().goOnline(); + }); - it('throws if values is not an object', async function () { - try { - await firebase.database().ref(TEST_PATH).onDisconnect().update('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'values' must be an object"); - return Promise.resolve(); - } - }); + it('throws if values is not an object', async function () { + try { + await firebase.database().ref(TEST_PATH).onDisconnect().update('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' must be an object"); + return Promise.resolve(); + } + }); - it('throws if values does not contain any values', async function () { - try { - await firebase.database().ref(TEST_PATH).onDisconnect().update({}); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'values' must be an object containing multiple values"); - return Promise.resolve(); - } - }); + it('throws if values does not contain any values', async function () { + try { + await firebase.database().ref(TEST_PATH).onDisconnect().update({}); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' must be an object containing multiple values"); + return Promise.resolve(); + } + }); + + it('throws if update paths are not valid', async function () { + try { + await firebase.database().ref(TEST_PATH).onDisconnect().update({ + $$$$: 'foo', + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' contains an invalid path."); + return Promise.resolve(); + } + }); + + it('throws if onComplete is not a function', function () { + const ref = firebase.database().ref(TEST_PATH).onDisconnect(); + try { + ref.update({ foo: 'bar' }, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); - it('throws if update paths are not valid', async function () { - try { - await firebase.database().ref(TEST_PATH).onDisconnect().update({ - $$$$: 'foo', + xit('updates value when disconnected', async function () { + const ref = firebase.database().ref(TEST_PATH); + + const value = Date.now(); + await ref.set({ + foo: { + bar: 'baz', + }, }); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'values' contains an invalid path."); - return Promise.resolve(); - } - }); - it('throws if onComplete is not a function', function () { - const ref = firebase.database().ref(TEST_PATH).onDisconnect(); - try { - ref.update({ foo: 'bar' }, 'foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } + await ref.child('foo').onDisconnect().update({ + bar: value, + }); + await firebase.database().goOffline(); + await firebase.database().goOnline(); + + const snapshot = await ref.child('foo').once('value'); + snapshot.val().should.eql( + jet.contextify({ + bar: value, + }), + ); + }); + + it('calls back to the onComplete function', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH); + + // Set an initial value + await ref.set('foo'); + await ref.onDisconnect().update({ foo: 'bar' }, callback); + await firebase.database().goOffline(); + await firebase.database().goOnline(); + + callback.should.be.calledOnce(); + }); }); - xit('updates value when disconnected', async function () { - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + afterEach(async function () { + const { getDatabase, goOnline } = databaseModular; + const db = getDatabase(); - const value = Date.now(); - await ref.set({ - foo: { - bar: 'baz', - }, + // Ensures the db is online before running each test + await goOnline(db); }); - await ref.child('foo').onDisconnect().update({ - bar: value, + it('throws if values is not an object', async function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + try { + await onDisconnect(dbRef).update('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' must be an object"); + return Promise.resolve(); + } + }); + + it('throws if values does not contain any values', async function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + try { + await onDisconnect(dbRef).update({}); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' must be an object containing multiple values"); + return Promise.resolve(); + } + }); + + it('throws if update paths are not valid', async function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + try { + await onDisconnect(dbRef).update({ + $$$$: 'foo', + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' contains an invalid path."); + return Promise.resolve(); + } + }); + + it('throws if onComplete is not a function', function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const disconnect = onDisconnect(dbRef); + try { + disconnect.update({ foo: 'bar' }, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } }); - await firebase.database().goOffline(); - await firebase.database().goOnline(); - const snapshot = await ref.child('foo').once('value'); - snapshot.val().should.eql( - jet.contextify({ + xit('updates value when disconnected', async function () { + const { getDatabase, ref, onDisconnect, child, goOnline, goOffline, get } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const value = Date.now(); + await dbRef.set({ + foo: { + bar: 'baz', + }, + }); + + await onDisconnect(child(dbRef, 'foo')).update({ bar: value, - }), - ); - }); + }); + await goOffline(db); + await goOnline(db); - it('calls back to the onComplete function', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH); + const snapshot = await get(child(dbRef, 'foo'), 'value'); + snapshot.val().should.eql( + jet.contextify({ + bar: value, + }), + ); + }); + + it('calls back to the onComplete function', async function () { + const { getDatabase, ref, onDisconnect, goOffline, goOnline } = databaseModular; + const db = getDatabase(); - // Set an initial value - await ref.set('foo'); - await ref.onDisconnect().update({ foo: 'bar' }, callback); - await firebase.database().goOffline(); - await firebase.database().goOnline(); + const callback = sinon.spy(); + const dbRef = ref(db, TEST_PATH); - callback.should.be.calledOnce(); + // Set an initial value + await dbRef.set('foo'); + await onDisconnect(dbRef).update({ foo: 'bar' }, callback); + await goOffline(db); + await goOnline(db); + + callback.should.be.calledOnce(); + }); }); }); diff --git a/packages/database/e2e/query/endAt.e2e.js b/packages/database/e2e/query/endAt.e2e.js index 160ae5802a..619a18e250 100644 --- a/packages/database/e2e/query/endAt.e2e.js +++ b/packages/database/e2e/query/endAt.e2e.js @@ -27,109 +27,235 @@ describe('database().ref().endAt()', function () { await wipe(TEST_PATH); }); - it('throws if an value is undefined', async function () { - try { - await firebase.database().ref().endAt(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'value' must be a number, string, boolean or null value"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if an value is undefined', async function () { + try { + await firebase.database().ref().endAt(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be a number, string, boolean or null value"); + return Promise.resolve(); + } + }); - it('throws if an key is not a string', async function () { - try { - await firebase.database().ref().endAt('foo', 1234); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'key' must be a string value if defined"); - return Promise.resolve(); - } - }); + it('throws if an key is not a string', async function () { + try { + await firebase.database().ref().endAt('foo', 1234); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'key' must be a string value if defined"); + return Promise.resolve(); + } + }); - it('throws if a ending point has already been set', async function () { - try { - await firebase.database().ref().equalTo('foo').endAt('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'Ending point was already set (by another call to endAt or equalTo)', - ); - return Promise.resolve(); - } - }); + it('throws if a ending point has already been set', async function () { + try { + await firebase.database().ref().equalTo('foo').endAt('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Ending point was already set (by another call to endAt or equalTo)', + ); + return Promise.resolve(); + } + }); - it('throws if ordering by key and the key param is set', async function () { - try { - await firebase.database().ref().orderByKey('foo').endAt('foo', 'bar'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'When ordering by key, you may only pass a value argument to startAt(), endAt(), or equalTo()', - ); - return Promise.resolve(); - } - }); + it('throws if ordering by key and the key param is set', async function () { + try { + await firebase.database().ref().orderByKey('foo').endAt('foo', 'bar'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by key, you may only pass a value argument to startAt(), endAt(), or equalTo()', + ); + return Promise.resolve(); + } + }); - it('throws if ordering by key and the value param is not a string', async function () { - try { - await firebase.database().ref().orderByKey('foo').endAt(123); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'When ordering by key, the value of startAt(), endAt(), or equalTo() must be a string', - ); - return Promise.resolve(); - } - }); + it('throws if ordering by key and the value param is not a string', async function () { + try { + await firebase.database().ref().orderByKey('foo').endAt(123); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by key, the value of startAt(), endAt(), or equalTo() must be a string', + ); + return Promise.resolve(); + } + }); - it('throws if ordering by priority and the value param is not priority type', async function () { - try { - await firebase.database().ref().orderByPriority().endAt(true); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'When ordering by priority, the first value of startAt(), endAt(), or equalTo() must be a valid priority value (null, a number, or a string)', - ); - return Promise.resolve(); - } - }); + it('throws if ordering by priority and the value param is not priority type', async function () { + try { + await firebase.database().ref().orderByPriority().endAt(true); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by priority, the first value of startAt(), endAt(), or equalTo() must be a valid priority value (null, a number, or a string)', + ); + return Promise.resolve(); + } + }); + + it('snapshot value returns all when no ordering modifier is applied', async function () { + const ref = firebase.database().ref(TEST_PATH); + + await ref.set({ + a: 1, + b: 2, + c: 3, + d: 4, + }); - it('snapshot value returns all when no ordering modifier is applied', async function () { - const ref = firebase.database().ref(TEST_PATH); + const expected = ['a', 'b', 'c', 'd']; - await ref.set({ - a: 1, - b: 2, - c: 3, - d: 4, + const snapshot = await ref.endAt(2).once('value'); + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); }); - const expected = ['a', 'b', 'c', 'd']; + it('ends at the correct value', async function () { + const ref = firebase.database().ref(TEST_PATH); + + await ref.set({ + a: 1, + b: 2, + c: 3, + d: 4, + }); - const snapshot = await ref.endAt(2).once('value'); + const snapshot = await ref.orderByValue().endAt(2).once('value'); - snapshot.forEach((childSnapshot, i) => { - childSnapshot.key.should.eql(expected[i]); + const expected = ['a', 'b']; + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); }); }); - it('ends at the correct value', async function () { - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + it('throws if an value is undefined', async function () { + const { getDatabase, ref, query, endAt } = databaseModular; + + try { + query(ref(getDatabase()), endAt()); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be a number, string, boolean or null value"); + return Promise.resolve(); + } + }); + + it('throws if an key is not a string', async function () { + const { getDatabase, ref, query, endAt } = databaseModular; + + try { + query(ref(getDatabase()), endAt('foo', 1234)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'key' must be a string value if defined"); + return Promise.resolve(); + } + }); + + it('throws if a ending point has already been set', async function () { + const { getDatabase, ref, query, equalTo, endAt } = databaseModular; + + try { + query(ref(getDatabase()), equalTo('foo'), endAt('foo')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Ending point was already set (by another call to endAt or equalTo)', + ); + return Promise.resolve(); + } + }); + + it('throws if ordering by key and the key param is set', async function () { + const { getDatabase, ref, query, orderByKey, endAt } = databaseModular; + + try { + query(ref(getDatabase()), orderByKey('foo'), endAt('foo', 'bar')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by key, you may only pass a value argument to startAt(), endAt(), or equalTo()', + ); + return Promise.resolve(); + } + }); + + it('throws if ordering by key and the value param is not a string', async function () { + const { getDatabase, ref, query, orderByKey, endAt } = databaseModular; + + try { + query(ref(getDatabase()), orderByKey('foo'), endAt(123)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by key, the value of startAt(), endAt(), or equalTo() must be a string', + ); + return Promise.resolve(); + } + }); + + it('throws if ordering by priority and the value param is not priority type', async function () { + const { getDatabase, ref, query, orderByPriority, endAt } = databaseModular; - await ref.set({ - a: 1, - b: 2, - c: 3, - d: 4, + try { + query(ref(getDatabase()), orderByPriority(), endAt(true)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by priority, the first value of startAt(), endAt(), or equalTo() must be a valid priority value (null, a number, or a string)', + ); + return Promise.resolve(); + } }); - const snapshot = await ref.orderByValue().endAt(2).once('value'); + it('snapshot value returns all when no ordering modifier is applied', async function () { + const { getDatabase, ref, set, endAt, query, get } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + await set(dbRef, { + a: 1, + b: 2, + c: 3, + d: 4, + }); + + const expected = ['a', 'b', 'c', 'd']; + + const snapshot = await get(query(dbRef, endAt(2))); + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); + }); + + it('ends at the correct value', async function () { + const { getDatabase, ref, set, endAt, query, get, orderByValue } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + await set(dbRef, { + a: 1, + b: 2, + c: 3, + d: 4, + }); + + const expected = ['a', 'b']; - const expected = ['a', 'b']; + const snapshot = await get(query(dbRef, orderByValue(), endAt(2))); - snapshot.forEach((childSnapshot, i) => { - childSnapshot.key.should.eql(expected[i]); + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); }); }); }); diff --git a/packages/database/e2e/query/equalTo.e2e.js b/packages/database/e2e/query/equalTo.e2e.js index 772be4cffb..4d68854d41 100644 --- a/packages/database/e2e/query/equalTo.e2e.js +++ b/packages/database/e2e/query/equalTo.e2e.js @@ -27,81 +27,175 @@ describe('database().ref().equalTo()', function () { await wipe(TEST_PATH); }); - it('throws if value is not a valid type', async function () { - try { - await firebase.database().ref().equalTo({ foo: 'bar' }); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'value' must be a number, string, boolean or null value"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if value is not a valid type', async function () { + try { + await firebase.database().ref().equalTo({ foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be a number, string, boolean or null value"); + return Promise.resolve(); + } + }); - it('throws if key is not a string', async function () { - try { - await firebase.database().ref().equalTo('bar', 123); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'key' must be a string value if defined"); - return Promise.resolve(); - } - }); + it('throws if key is not a string', async function () { + try { + await firebase.database().ref().equalTo('bar', 123); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'key' must be a string value if defined"); + return Promise.resolve(); + } + }); - it('throws if a starting point has already been set', async function () { - try { - await firebase.database().ref().startAt('foo').equalTo('bar'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'Starting point was already set (by another call to startAt or equalTo)', - ); - return Promise.resolve(); - } - }); + it('throws if a starting point has already been set', async function () { + try { + await firebase.database().ref().startAt('foo').equalTo('bar'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Starting point was already set (by another call to startAt or equalTo)', + ); + return Promise.resolve(); + } + }); - it('throws if a ending point has already been set', async function () { - try { - await firebase.database().ref().endAt('foo').equalTo('bar'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'Ending point was already set (by another call to endAt or equalTo)', - ); - return Promise.resolve(); - } - }); + it('throws if a ending point has already been set', async function () { + try { + await firebase.database().ref().endAt('foo').equalTo('bar'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Ending point was already set (by another call to endAt or equalTo)', + ); + return Promise.resolve(); + } + }); - it('snapshot value is null when no ordering modifier is applied', async function () { - const ref = firebase.database().ref(TEST_PATH); + it('snapshot value is null when no ordering modifier is applied', async function () { + const ref = firebase.database().ref(TEST_PATH); - await ref.set({ - a: 1, - b: 2, - c: 3, - d: 4, + await ref.set({ + a: 1, + b: 2, + c: 3, + d: 4, + }); + + const snapshot = await ref.equalTo(2).once('value'); + should.equal(snapshot.val(), null); }); - const snapshot = await ref.equalTo(2).once('value'); - should.equal(snapshot.val(), null); + it('returns the correct equal to values', async function () { + const ref = firebase.database().ref(TEST_PATH); + + await ref.set({ + a: 1, + b: 2, + c: 3, + d: 4, + e: 2, + }); + + const snapshot = await ref.orderByValue().equalTo(2).once('value'); + + const expected = ['b', 'e']; + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); + }); }); - it('returns the correct equal to values', async function () { - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + it('throws if value is not a valid type', async function () { + const { getDatabase, ref, equalTo, query } = databaseModular; - await ref.set({ - a: 1, - b: 2, - c: 3, - d: 4, - e: 2, + try { + query(ref(getDatabase()), equalTo({ foo: 'bar' })); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be a number, string, boolean or null value"); + return Promise.resolve(); + } }); - const snapshot = await ref.orderByValue().equalTo(2).once('value'); + it('throws if key is not a string', async function () { + const { getDatabase, ref, equalTo, query } = databaseModular; + + try { + query(ref(getDatabase()), equalTo('bar', 123)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'key' must be a string value if defined"); + return Promise.resolve(); + } + }); + + it('throws if a starting point has already been set', async function () { + const { getDatabase, ref, startAt, equalTo, query } = databaseModular; + + try { + query(ref(getDatabase()), startAt('foo'), equalTo('bar')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Starting point was already set (by another call to startAt or equalTo)', + ); + return Promise.resolve(); + } + }); + + it('throws if a ending point has already been set', async function () { + const { getDatabase, ref, endAt, equalTo, query } = databaseModular; + + try { + query(ref(getDatabase()), endAt('foo'), equalTo('bar')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Ending point was already set (by another call to endAt or equalTo)', + ); + return Promise.resolve(); + } + }); + + it('snapshot value is null when no ordering modifier is applied', async function () { + const { getDatabase, ref, set, equalTo, get, query } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + await set(dbRef, { + a: 1, + b: 2, + c: 3, + d: 4, + }); + + const snapshot = await get(query(dbRef, equalTo(2))); + should.equal(snapshot.val(), null); + }); + + it('returns the correct equal to values', async function () { + const { getDatabase, ref, set, orderByValue, equalTo, get, query } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + await set(dbRef, { + a: 1, + b: 2, + c: 3, + d: 4, + e: 2, + }); + + const snapshot = await get(query(dbRef, orderByValue(), equalTo(2))); - const expected = ['b', 'e']; + const expected = ['b', 'e']; - snapshot.forEach((childSnapshot, i) => { - childSnapshot.key.should.eql(expected[i]); + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); }); }); }); diff --git a/packages/database/e2e/query/get.e2e.js b/packages/database/e2e/query/get.e2e.js new file mode 100644 index 0000000000..ce9ef7e81e --- /dev/null +++ b/packages/database/e2e/query/get.e2e.js @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const { PATH, CONTENT, seed, wipe } = require('../helpers'); + +const TEST_PATH = `${PATH}/once`; + +describe('get()', function () { + before(function () { + return seed(TEST_PATH); + }); + after(function () { + return wipe(TEST_PATH); + }); + + it('returns a promise', async function () { + const { getDatabase, ref, get } = databaseModular; + + const dbRef = ref(getDatabase(), 'tests/types/number'); + const returnValue = get(dbRef); + returnValue.should.be.Promise(); + }); + + it('resolves with the correct values', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const dbRef = ref(getDatabase(), `${TEST_PATH}/types`); + + await Promise.all( + Object.keys(CONTENT.TYPES).map(async key => { + const value = CONTENT.TYPES[key]; + const snapsnot = await get(child(dbRef, key)); + snapsnot.val().should.eql(jet.contextify(value)); + }), + ); + }); + + it('errors if permission denied', async function () { + const { getDatabase, ref, get } = databaseModular; + + const dbRef = ref(getDatabase(), 'nope'); + try { + await get(dbRef); + return Promise.reject(new Error('No permission denied error')); + } catch (error) { + error.code.includes('database/permission-denied').should.be.true(); + return Promise.resolve(); + } + }); +}); diff --git a/packages/database/e2e/query/keepSynced.e2e.js b/packages/database/e2e/query/keepSynced.e2e.js index c567b54d35..ce7e5ba68e 100644 --- a/packages/database/e2e/query/keepSynced.e2e.js +++ b/packages/database/e2e/query/keepSynced.e2e.js @@ -16,19 +16,43 @@ */ describe('database().ref().keepSynced()', function () { - it('throws if bool is not a valid type', async function () { - try { - await firebase.database().ref().keepSynced('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'bool' value must be a boolean value."); - return Promise.resolve(); - } + describe('v8 compatibility', function () { + it('throws if bool is not a valid type', async function () { + try { + await firebase.database().ref().keepSynced('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'bool' value must be a boolean value."); + return Promise.resolve(); + } + }); + + it('toggles keepSynced on and off without throwing', async function () { + const ref = firebase.database().ref('noop').orderByValue(); + await ref.keepSynced(true); + await ref.keepSynced(false); + }); }); - it('toggles keepSynced on and off without throwing', async function () { - const ref = firebase.database().ref('noop').orderByValue(); - await ref.keepSynced(true); - await ref.keepSynced(false); + describe('modular', function () { + it('throws if bool is not a valid type', async function () { + const { getDatabase, ref, keepSynced } = databaseModular; + + try { + await keepSynced(ref(getDatabase()), 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'bool' value must be a boolean value."); + return Promise.resolve(); + } + }); + + it('toggles keepSynced on and off without throwing', async function () { + const { getDatabase, ref, orderByValue, query, keepSynced } = databaseModular; + + const dbRef = query(ref(getDatabase(), 'noop'), orderByValue()); + await keepSynced(dbRef, true); + await keepSynced(dbRef, false); + }); }); }); diff --git a/packages/database/e2e/query/limitToFirst.e2e.js b/packages/database/e2e/query/limitToFirst.e2e.js index 278a256ef6..25e558680f 100644 --- a/packages/database/e2e/query/limitToFirst.e2e.js +++ b/packages/database/e2e/query/limitToFirst.e2e.js @@ -27,63 +27,151 @@ describe('database().ref().limitToFirst()', function () { await wipe(TEST_PATH); }); - it('throws if limit is invalid', async function () { - try { - await firebase.database().ref().limitToFirst('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'limit' must be a positive integer value"); - return Promise.resolve(); - } + describe('v8 compatibility', function () { + it('throws if limit is invalid', async function () { + try { + await firebase.database().ref().limitToFirst('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'limit' must be a positive integer value"); + return Promise.resolve(); + } + }); + + it('throws if limit has already been set', async function () { + try { + await firebase.database().ref().limitToLast(2).limitToFirst(3); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Limit was already set (by another call to limitToFirst, or limitToLast)', + ); + return Promise.resolve(); + } + }); + + it('returns a limited array data set', async function () { + const ref = firebase.database().ref(`${TEST_PATH}`); + + const initial = { + 0: 'foo', + 1: 'bar', + 2: 'baz', + }; + + await ref.set(initial); + + return ref + .limitToFirst(2) + .once('value') + .then(snapshot => { + snapshot.val().should.eql(jet.contextify(['foo', 'bar'])); + return Promise.resolve(); + }); + }); + + it('returns a limited object data set', async function () { + const ref = firebase.database().ref(`${TEST_PATH}`); + + const initial = { + a: 'foo', + b: 'bar', + c: 'baz', + }; + + await ref.set(initial); + + return ref + .limitToFirst(2) + .once('value') + .then(snapshot => { + snapshot.val().should.eql( + jet.contextify({ + a: 'foo', + b: 'bar', + }), + ); + return Promise.resolve(); + }); + }); + + it('returns a null value when not possible to limit', async function () { + const ref = firebase.database().ref(`${TEST_PATH}`); + + const initial = 'foo'; + + await ref.set(initial); + + return ref + .limitToFirst(2) + .once('value') + .then(snapshot => { + should.equal(snapshot.val(), null); + return Promise.resolve(); + }); + }); }); - it('throws if limit has already been set', async function () { - try { - await firebase.database().ref().limitToLast(2).limitToFirst(3); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'Limit was already set (by another call to limitToFirst, or limitToLast)', - ); - return Promise.resolve(); - } - }); + describe('modular', function () { + it('throws if limit is invalid', async function () { + const { getDatabase, ref, limitToFirst, query } = databaseModular; - it('returns a limited array data set', async function () { - const ref = firebase.database().ref(`${TEST_PATH}`); + try { + query(ref(getDatabase()), limitToFirst('foo')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'limit' must be a positive integer value"); + return Promise.resolve(); + } + }); + + it('throws if limit has already been set', async function () { + const { getDatabase, ref, limitToFirst, limitToLast, query } = databaseModular; + + try { + query(ref(getDatabase()), limitToLast(2), limitToFirst(3)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Limit was already set (by another call to limitToFirst, or limitToLast)', + ); + return Promise.resolve(); + } + }); - const initial = { - 0: 'foo', - 1: 'bar', - 2: 'baz', - }; + it('returns a limited array data set', async function () { + const { getDatabase, ref, set, get, limitToFirst, query } = databaseModular; - await ref.set(initial); + const dbRef = ref(getDatabase(), `${TEST_PATH}`); - return ref - .limitToFirst(2) - .once('value') - .then(snapshot => { + const initial = { + 0: 'foo', + 1: 'bar', + 2: 'baz', + }; + + await set(dbRef, initial); + + return get(query(dbRef, limitToFirst(2))).then(snapshot => { snapshot.val().should.eql(jet.contextify(['foo', 'bar'])); return Promise.resolve(); }); - }); + }); - it('returns a limited object data set', async function () { - const ref = firebase.database().ref(`${TEST_PATH}`); + it('returns a limited object data set', async function () { + const { getDatabase, ref, set, get, limitToFirst, query } = databaseModular; - const initial = { - a: 'foo', - b: 'bar', - c: 'baz', - }; + const dbRef = ref(getDatabase(), `${TEST_PATH}`); - await ref.set(initial); + const initial = { + a: 'foo', + b: 'bar', + c: 'baz', + }; - return ref - .limitToFirst(2) - .once('value') - .then(snapshot => { + await set(dbRef, initial); + + return get(query(dbRef, limitToFirst(2))).then(snapshot => { snapshot.val().should.eql( jet.contextify({ a: 'foo', @@ -92,21 +180,21 @@ describe('database().ref().limitToFirst()', function () { ); return Promise.resolve(); }); - }); + }); + + it('returns a null value when not possible to limit', async function () { + const { getDatabase, ref, set, get, limitToFirst, query } = databaseModular; - it('returns a null value when not possible to limit', async function () { - const ref = firebase.database().ref(`${TEST_PATH}`); + const dbRef = ref(getDatabase(), `${TEST_PATH}`); - const initial = 'foo'; + const initial = 'foo'; - await ref.set(initial); + await set(dbRef, initial); - return ref - .limitToFirst(2) - .once('value') - .then(snapshot => { + return get(query(dbRef, limitToFirst(2))).then(snapshot => { should.equal(snapshot.val(), null); return Promise.resolve(); }); + }); }); }); diff --git a/packages/database/e2e/query/limitToLast.e2e.js b/packages/database/e2e/query/limitToLast.e2e.js index 616060a8fe..4fd2c227e8 100644 --- a/packages/database/e2e/query/limitToLast.e2e.js +++ b/packages/database/e2e/query/limitToLast.e2e.js @@ -27,63 +27,151 @@ describe('database().ref().limitToLast()', function () { await wipe(TEST_PATH); }); - it('throws if limit is invalid', async function () { - try { - await firebase.database().ref().limitToLast('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'limit' must be a positive integer value"); - return Promise.resolve(); - } + describe('v8 compatibility', function () { + it('throws if limit is invalid', async function () { + try { + await firebase.database().ref().limitToLast('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'limit' must be a positive integer value"); + return Promise.resolve(); + } + }); + + it('throws if limit has already been set', async function () { + try { + await firebase.database().ref().limitToFirst(3).limitToLast(2); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Limit was already set (by another call to limitToFirst, or limitToLast)', + ); + return Promise.resolve(); + } + }); + + it('returns a limited array data set', async function () { + const ref = firebase.database().ref(`${TEST_PATH}`); + + const initial = { + 0: 'foo', + 1: 'bar', + 2: 'baz', + }; + + await ref.set(initial); + + return ref + .limitToLast(2) + .once('value') + .then(snapshot => { + snapshot.val().should.eql(jet.contextify([null, 'bar', 'baz'])); + return Promise.resolve(); + }); + }); + + it('returns a limited object data set', async function () { + const ref = firebase.database().ref(`${TEST_PATH}`); + + const initial = { + a: 'foo', + b: 'bar', + c: 'baz', + }; + + await ref.set(initial); + + return ref + .limitToLast(2) + .once('value') + .then(snapshot => { + snapshot.val().should.eql( + jet.contextify({ + b: 'bar', + c: 'baz', + }), + ); + return Promise.resolve(); + }); + }); + + it('returns a null value when not possible to limit', async function () { + const ref = firebase.database().ref(`${TEST_PATH}`); + + const initial = 'foo'; + + await ref.set(initial); + + return ref + .limitToFirst(2) + .once('value') + .then(snapshot => { + should.equal(snapshot.val(), null); + return Promise.resolve(); + }); + }); }); - it('throws if limit has already been set', async function () { - try { - await firebase.database().ref().limitToFirst(3).limitToLast(2); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'Limit was already set (by another call to limitToFirst, or limitToLast)', - ); - return Promise.resolve(); - } - }); + describe('modular', function () { + it('throws if limit is invalid', async function () { + const { getDatabase, ref, limitToLast, query } = databaseModular; - it('returns a limited array data set', async function () { - const ref = firebase.database().ref(`${TEST_PATH}`); + try { + query(ref(getDatabase()), limitToLast('foo')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'limit' must be a positive integer value"); + return Promise.resolve(); + } + }); + + it('throws if limit has already been set', async function () { + const { getDatabase, ref, limitToLast, limitToFirst, query } = databaseModular; + + try { + query(ref(getDatabase()), limitToFirst(3), limitToLast(2)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Limit was already set (by another call to limitToFirst, or limitToLast)', + ); + return Promise.resolve(); + } + }); - const initial = { - 0: 'foo', - 1: 'bar', - 2: 'baz', - }; + it('returns a limited array data set', async function () { + const { getDatabase, ref, set, get, limitToLast, query } = databaseModular; - await ref.set(initial); + const dbRef = ref(getDatabase(), `${TEST_PATH}`); - return ref - .limitToLast(2) - .once('value') - .then(snapshot => { + const initial = { + 0: 'foo', + 1: 'bar', + 2: 'baz', + }; + + await set(dbRef, initial); + + return get(query(dbRef, limitToLast(2))).then(snapshot => { snapshot.val().should.eql(jet.contextify([null, 'bar', 'baz'])); return Promise.resolve(); }); - }); + }); - it('returns a limited object data set', async function () { - const ref = firebase.database().ref(`${TEST_PATH}`); + it('returns a limited object data set', async function () { + const { getDatabase, ref, set, get, limitToLast, query } = databaseModular; - const initial = { - a: 'foo', - b: 'bar', - c: 'baz', - }; + const dbRef = ref(getDatabase(), `${TEST_PATH}`); - await ref.set(initial); + const initial = { + a: 'foo', + b: 'bar', + c: 'baz', + }; - return ref - .limitToLast(2) - .once('value') - .then(snapshot => { + await set(dbRef, initial); + + return get(query(dbRef, limitToLast(2))).then(snapshot => { snapshot.val().should.eql( jet.contextify({ b: 'bar', @@ -92,21 +180,21 @@ describe('database().ref().limitToLast()', function () { ); return Promise.resolve(); }); - }); + }); + + it('returns a null value when not possible to limit', async function () { + const { getDatabase, ref, set, get, limitToLast, query } = databaseModular; - it('returns a null value when not possible to limit', async function () { - const ref = firebase.database().ref(`${TEST_PATH}`); + const dbRef = ref(getDatabase(), `${TEST_PATH}`); - const initial = 'foo'; + const initial = 'foo'; - await ref.set(initial); + await set(dbRef, initial); - return ref - .limitToFirst(2) - .once('value') - .then(snapshot => { + return get(query(dbRef, limitToLast(2))).then(snapshot => { should.equal(snapshot.val(), null); return Promise.resolve(); }); + }); }); }); diff --git a/packages/database/e2e/query/onChildAdded.e2e.js b/packages/database/e2e/query/onChildAdded.e2e.js new file mode 100644 index 0000000000..a3c6f67199 --- /dev/null +++ b/packages/database/e2e/query/onChildAdded.e2e.js @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const { PATH, seed, wipe } = require('../helpers'); + +const TEST_PATH = `${PATH}/on`; + +describe('onChildAdded', function () { + before(async function () { + await seed(TEST_PATH); + }); + after(async function () { + await wipe(TEST_PATH); + }); + + // FIXME super flaky on ios simulator + it('should stop listening if ListeningOptions.onlyOnce is true', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { getDatabase, ref, set, child, onChildAdded } = databaseModular; + const dbRef = ref(getDatabase(), `${TEST_PATH}/childAdded`); + + const callback = sinon.spy(); + + onChildAdded( + dbRef, + $ => { + callback($.val()); + }, + { onlyOnce: true }, + ); + + set(child(dbRef, 'child1'), 'foo'); + await Utils.spyToBeCalledOnceAsync(callback, 5000); + callback.should.be.calledWith('foo'); + + set(child(dbRef, 'child1'), 'bar'); + callback.should.not.be.calledWith('bar'); + }); + + // FIXME super flaky on android emulator + it('subscribe to child added events', async function () { + if (device.getPlatform() === 'ios') { + const { getDatabase, ref, set, child, onChildAdded } = databaseModular; + + const successCallback = sinon.spy(); + const cancelCallback = sinon.spy(); + const dbRef = ref(getDatabase(), `${TEST_PATH}/childAdded2`); + + const unsubscribe = onChildAdded( + dbRef, + $ => { + successCallback($.val()); + }, + () => { + cancelCallback(); + }, + ); + + await set(child(dbRef, 'child1'), 'foo'); + await set(child(dbRef, 'child2'), 'bar'); + await Utils.spyToBeCalledTimesAsync(successCallback, 2); + unsubscribe(); + + successCallback.getCall(0).args[0].should.equal('foo'); + successCallback.getCall(1).args[0].should.equal('bar'); + cancelCallback.should.be.callCount(0); + } else { + this.skip(); + } + }); +}); diff --git a/packages/database/e2e/query/onChildChanged.e2e.js b/packages/database/e2e/query/onChildChanged.e2e.js new file mode 100644 index 0000000000..db5b71668a --- /dev/null +++ b/packages/database/e2e/query/onChildChanged.e2e.js @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const { PATH, seed, wipe } = require('../helpers'); + +const TEST_PATH = `${PATH}/on`; + +describe('onChildChanged', function () { + before(async function () { + await seed(TEST_PATH); + }); + after(async function () { + await wipe(TEST_PATH); + }); + + // FIXME super flaky on ios simulator + it('should stop listening if ListeningOptions.onlyOnce is true', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { getDatabase, ref, set, child, onChildChanged } = databaseModular; + const dbRef = ref(getDatabase(), `${TEST_PATH}/childAdded`); + await set(child(dbRef, 'changeme'), 'foo'); + + const callback = sinon.spy(); + + onChildChanged( + dbRef, + $ => { + callback($.val()); + }, + { onlyOnce: true }, + ); + + await set(child(dbRef, 'changeme'), 'bar'); + await Utils.spyToBeCalledOnceAsync(callback, 5000); + callback.should.be.calledWith('bar'); + + await set(child(dbRef, 'changeme'), 'baz'); + callback.should.not.be.calledWith('baz'); + }); + + // FIXME super flaky on android emulator + it('subscribe to child changed events', async function () { + const { getDatabase, ref, set, child, onChildChanged } = databaseModular; + + if (device.getPlatform() === 'ios') { + const successCallback = sinon.spy(); + const cancelCallback = sinon.spy(); + + const dbRef = ref(getDatabase(), `${TEST_PATH}/childChanged2`); + const refChild = child(dbRef, 'changeme'); + await set(refChild, 'foo'); + + const unsubscribe = onChildChanged( + dbRef, + $ => { + successCallback($.val()); + }, + () => { + cancelCallback(); + }, + ); + + const value1 = Date.now(); + const value2 = Date.now() + 123; + + await set(refChild, value1); + await set(refChild, value2); + await Utils.spyToBeCalledTimesAsync(successCallback, 2); + unsubscribe(); + + successCallback.getCall(0).args[0].should.equal(value1); + successCallback.getCall(1).args[0].should.equal(value2); + cancelCallback.should.be.callCount(0); + } else { + this.skip(); + } + }); +}); diff --git a/packages/database/e2e/query/onChildMoved.e2e.js b/packages/database/e2e/query/onChildMoved.e2e.js new file mode 100644 index 0000000000..39b9d72878 --- /dev/null +++ b/packages/database/e2e/query/onChildMoved.e2e.js @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const { PATH, seed, wipe } = require('../helpers'); + +const TEST_PATH = `${PATH}/on`; + +describe('onChildMoved', function () { + before(async function () { + await seed(TEST_PATH); + }); + after(async function () { + await wipe(TEST_PATH); + }); + + // FIXME super flaky on ios simulator + it('should stop listening if ListeningOptions.onlyOnce is true', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { getDatabase, ref, query, orderByChild, set, child, onChildMoved } = databaseModular; + const dbRef = ref(getDatabase(), `${TEST_PATH}/childMoved`); + const orderedRef = query(dbRef, orderByChild('nuggets')); + + const callback = sinon.spy(); + + const initial = { + alex: { nuggets: 60 }, + rob: { nuggets: 56 }, + vassili: { nuggets: 55.5 }, + tony: { nuggets: 52 }, + greg: { nuggets: 52 }, + }; + + onChildMoved( + orderedRef, + $ => { + callback($.val()); + }, + { onlyOnce: true }, + ); + + await set(dbRef, initial); + await set(child(dbRef, 'greg/nuggets'), 57); + await set(child(dbRef, 'rob/nuggets'), 61); + await Utils.spyToBeCalledTimesAsync(callback, 1); + callback.should.be.calledWith({ nuggets: 57 }); + }); + + it('subscribe to child moved events', async function () { + const { getDatabase, ref, query, orderByChild, onChildMoved, set, child } = databaseModular; + + const callback = sinon.spy(); + const dbRef = ref(getDatabase(), `${TEST_PATH}/childMoved2`); + const orderedRef = query(dbRef, orderByChild('nuggets')); + + const initial = { + alex: { nuggets: 60 }, + rob: { nuggets: 56 }, + vassili: { nuggets: 55.5 }, + tony: { nuggets: 52 }, + greg: { nuggets: 52 }, + }; + + const unsubscribe = onChildMoved(orderedRef, $ => { + callback($.val()); + }); + + await set(dbRef, initial); + await set(child(dbRef, 'greg/nuggets'), 57); + await set(child(dbRef, 'rob/nuggets'), 61); + await Utils.spyToBeCalledTimesAsync(callback, 2); + unsubscribe(); + + callback.getCall(0).args[0].should.be.eql(jet.contextify({ nuggets: 57 })); + callback.getCall(1).args[0].should.be.eql(jet.contextify({ nuggets: 61 })); + }); +}); diff --git a/packages/database/e2e/query/onChildRemoved.e2e.js b/packages/database/e2e/query/onChildRemoved.e2e.js new file mode 100644 index 0000000000..674994751e --- /dev/null +++ b/packages/database/e2e/query/onChildRemoved.e2e.js @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const { PATH, seed, wipe } = require('../helpers'); + +const TEST_PATH = `${PATH}/on`; + +describe('onChildRemoved', function () { + before(async function () { + await seed(TEST_PATH); + }); + after(async function () { + await wipe(TEST_PATH); + }); + + // FIXME super flaky on ios simulator + it('should stop listening if ListeningOptions.onlyOnce is true', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { getDatabase, ref, child, set, remove, onChildRemoved } = databaseModular; + const dbRef = ref(getDatabase(), `${TEST_PATH}/childRemoved`); + const childRef = child(dbRef, 'removeme'); + await set(childRef, 'foo'); + + const callback = sinon.spy(); + + onChildRemoved( + dbRef, + $ => { + callback($.val()); + }, + { onlyOnce: true }, + ); + + await remove(childRef); + await Utils.spyToBeCalledTimesAsync(callback, 1); + callback.should.be.calledWith('foo'); + }); + + it('subscribe to child removed events', async function () { + const { getDatabase, ref, child, set, remove, onChildRemoved } = databaseModular; + + const successCallback = sinon.spy(); + const cancelCallback = sinon.spy(); + + const dbRef = ref(getDatabase(), `${TEST_PATH}/childRemoved2`); + const childRef = child(dbRef, 'removeme'); + await set(childRef, 'foo'); + + const unsubscribe = onChildRemoved( + dbRef, + $ => { + successCallback($.val()); + }, + () => { + cancelCallback(); + }, + ); + + await remove(childRef); + await Utils.spyToBeCalledOnceAsync(successCallback, 5000); + unsubscribe(); + + successCallback.getCall(0).args[0].should.equal('foo'); + cancelCallback.should.be.callCount(0); + }); +}); diff --git a/packages/database/e2e/query/onValue.e2e.js b/packages/database/e2e/query/onValue.e2e.js new file mode 100644 index 0000000000..20a8f39fbe --- /dev/null +++ b/packages/database/e2e/query/onValue.e2e.js @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const { PATH, seed, wipe } = require('../helpers'); + +const TEST_PATH = `${PATH}/on`; + +describe('onValue()', function () { + before(async function () { + await seed(TEST_PATH); + }); + after(async function () { + await wipe(TEST_PATH); + }); + + it('throws if callback is not a function', async function () { + const { getDatabase, ref, onValue } = databaseModular; + + try { + onValue(ref(getDatabase()), 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'callback' must be a function"); + return Promise.resolve(); + } + }); + + it('throws if cancel callback is not a function', async function () { + const { getDatabase, ref, onValue } = databaseModular; + + try { + onValue(ref(getDatabase()), () => {}, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'cancelCallbackOrContext' must be a function or object"); + return Promise.resolve(); + } + }); + + it('should callback with an initial value', async function () { + const { getDatabase, ref, set, onValue } = databaseModular; + const dbRef = ref(getDatabase(), `${TEST_PATH}/init`); + + const callback = sinon.spy(); + const unsubscribe = onValue(dbRef, $ => { + callback($.val()); + }); + + const value = Date.now(); + await set(dbRef, value); + await Utils.spyToBeCalledOnceAsync(callback, 5000); + callback.should.be.calledWith(value); + + unsubscribe(); + }); + + it('should stop listening if ListeningOptions.onlyOnce is true', async function () { + const { getDatabase, ref, set, onValue } = databaseModular; + const dbRef = ref(getDatabase(), `${TEST_PATH}/init`); + + const callback = sinon.spy(); + + onValue( + dbRef, + $ => { + callback($.val()); + }, + { onlyOnce: true }, + ); + + let value = Date.now(); + set(dbRef, value); + await Utils.spyToBeCalledOnceAsync(callback, 5000); + callback.should.be.calledWith(value); + + value = Date.now(); + set(dbRef, value); + callback.should.not.be.calledWith(value); + }); + + xit('should callback multiple times when the value changes', async function () { + const { getDatabase, ref, set, onValue } = databaseModular; + const dbRef = ref(getDatabase(), `${TEST_PATH}/init`); + + const callback = sinon.spy(); + const unsubscribe = onValue(dbRef, $ => { + callback($.val()); + }); + + await set(dbRef, 'foo'); + await set(dbRef, 'bar'); + await Utils.spyToBeCalledTimesAsync(callback, 2); + unsubscribe(); + + callback.getCall(0).args[0].should.equal('foo'); // FIXME these simply do *not* come back + callback.getCall(1).args[0].should.equal('bar'); // in the right order every time. ?? + }); + + it('should cancel when something goes wrong', async function () { + const { getDatabase, ref, onValue } = databaseModular; + + const successCallback = sinon.spy(); + const cancelCallback = sinon.spy(); + const dbRef = ref(getDatabase(), 'nope'); + + const unsubscribe = onValue( + dbRef, + $ => { + successCallback($.val()); + }, + error => { + error.message.should.containEql( + "Client doesn't have permission to access the desired data", + ); + cancelCallback(); + }, + ); + await Utils.spyToBeCalledOnceAsync(cancelCallback, 5000); + successCallback.should.be.callCount(0); + + unsubscribe(); + }); +}); diff --git a/packages/database/e2e/query/orderByChild.e2e.js b/packages/database/e2e/query/orderByChild.e2e.js index 28deb940fc..f4eefc9baf 100644 --- a/packages/database/e2e/query/orderByChild.e2e.js +++ b/packages/database/e2e/query/orderByChild.e2e.js @@ -27,51 +27,111 @@ describe('database().ref().orderByChild()', function () { await wipe(TEST_PATH); }); - it('throws if path is not a string value', async function () { - try { - await firebase.database().ref().orderByChild({ foo: 'bar' }); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'path' must be a string value"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if path is not a string value', async function () { + try { + await firebase.database().ref().orderByChild({ foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'path' must be a string value"); + return Promise.resolve(); + } + }); - it('throws if path is an empty path', async function () { - try { - await firebase.database().ref().orderByChild('/'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'path' cannot be empty. Use orderByValue instead"); - return Promise.resolve(); - } - }); + it('throws if path is an empty path', async function () { + try { + await firebase.database().ref().orderByChild('/'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'path' cannot be empty. Use orderByValue instead"); + return Promise.resolve(); + } + }); + + it('throws if an orderBy call has already been set', async function () { + try { + await firebase.database().ref().orderByKey().orderByChild('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You can't combine multiple orderBy calls"); + return Promise.resolve(); + } + }); + + it('order by a child value', async function () { + const ref = firebase.database().ref(TEST_PATH); + + try { + const snapshot = await ref.child('query').orderByChild('number').once('value'); + + const expected = ['b', 'c', 'a']; + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); - it('throws if an orderBy call has already been set', async function () { - try { - await firebase.database().ref().orderByKey().orderByChild('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("You can't combine multiple orderBy calls"); - return Promise.resolve(); - } + return Promise.resolve(); + } catch (error) { + throw error; + } + }); }); - it('order by a child value', async function () { - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + it('throws if path is not a string value', async function () { + const { getDatabase, ref, orderByChild, query } = databaseModular; + + try { + query(ref(getDatabase()), orderByChild({ foo: 'bar' })); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'path' must be a string value"); + return Promise.resolve(); + } + }); + + it('throws if path is an empty path', async function () { + const { getDatabase, ref, orderByChild, query } = databaseModular; + + try { + query(ref(getDatabase()), orderByChild('/')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'path' cannot be empty. Use orderByValue instead"); + return Promise.resolve(); + } + }); + + it('throws if an orderBy call has already been set', async function () { + const { getDatabase, ref, orderByKey, orderByChild, query } = databaseModular; + + try { + query(ref(getDatabase()), orderByKey(), orderByChild('foo')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You can't combine multiple orderBy calls"); + return Promise.resolve(); + } + }); + + it('order by a child value', async function () { + const { getDatabase, ref, get, child, orderByChild, query } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); - try { - const snapshot = await ref.child('query').orderByChild('number').once('value'); + try { + const snapshot = await get(query(child(dbRef, 'query'), orderByChild('number'))); - const expected = ['b', 'c', 'a']; + const expected = ['b', 'c', 'a']; - snapshot.forEach((childSnapshot, i) => { - childSnapshot.key.should.eql(expected[i]); - }); + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); - return Promise.resolve(); - } catch (error) { - throw error; - } + return Promise.resolve(); + } catch (error) { + throw error; + } + }); }); }); diff --git a/packages/database/e2e/query/orderByKey.e2e.js b/packages/database/e2e/query/orderByKey.e2e.js index 384e8f88af..940ce13dc1 100644 --- a/packages/database/e2e/query/orderByKey.e2e.js +++ b/packages/database/e2e/query/orderByKey.e2e.js @@ -27,31 +27,67 @@ describe('database().ref().orderByKey()', function () { await wipe(TEST_PATH); }); - it('throws if an orderBy call has already been set', async function () { - try { - await firebase.database().ref().orderByChild('foo').orderByKey(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("You can't combine multiple orderBy calls"); - return Promise.resolve(); - } + describe('v8 compatibility', function () { + it('throws if an orderBy call has already been set', async function () { + try { + await firebase.database().ref().orderByChild('foo').orderByKey(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You can't combine multiple orderBy calls"); + return Promise.resolve(); + } + }); + + it('order by a key', async function () { + const ref = firebase.database().ref(TEST_PATH); + + try { + const snapshot = await ref.child('query').orderByKey().once('value'); + + const expected = ['a', 'b', 'c']; + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); + + return Promise.resolve(); + } catch (error) { + throw error; + } + }); }); - it('order by a key', async function () { - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + it('throws if an orderBy call has already been set', async function () { + const { getDatabase, ref, orderByChild, orderByKey, query } = databaseModular; + + try { + query(ref(getDatabase()), orderByChild('foo'), orderByKey()); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You can't combine multiple orderBy calls"); + return Promise.resolve(); + } + }); + + it('order by a key', async function () { + const { getDatabase, ref, get, child, orderByKey, query } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); - try { - const snapshot = await ref.child('query').orderByKey().once('value'); + try { + const snapshot = await get(query(child(dbRef, 'query'), orderByKey())); - const expected = ['a', 'b', 'c']; + const expected = ['a', 'b', 'c']; - snapshot.forEach((childSnapshot, i) => { - childSnapshot.key.should.eql(expected[i]); - }); + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); - return Promise.resolve(); - } catch (error) { - throw error; - } + return Promise.resolve(); + } catch (error) { + throw error; + } + }); }); }); diff --git a/packages/database/e2e/query/orderByPriority.e2e.js b/packages/database/e2e/query/orderByPriority.e2e.js index 2d03bb866b..a33c83651a 100644 --- a/packages/database/e2e/query/orderByPriority.e2e.js +++ b/packages/database/e2e/query/orderByPriority.e2e.js @@ -27,37 +27,79 @@ describe('database().ref().orderByPriority()', function () { await wipe(TEST_PATH); }); - it('throws if an orderBy call has already been set', async function () { - try { - await firebase.database().ref().orderByChild('foo').orderByPriority(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("You can't combine multiple orderBy calls"); - return Promise.resolve(); - } + describe('v8 compatibility', function () { + it('throws if an orderBy call has already been set', async function () { + try { + await firebase.database().ref().orderByChild('foo').orderByPriority(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You can't combine multiple orderBy calls"); + return Promise.resolve(); + } + }); + + it('order by priority', async function () { + const ref = firebase.database().ref(TEST_PATH).child('query'); + + await Promise.all([ + ref.child('a').setPriority(2), + ref.child('b').setPriority(3), + ref.child('c').setPriority(1), + ]); + + try { + const snapshot = await ref.orderByPriority().once('value'); + + const expected = ['c', 'a', 'b']; + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); + + return Promise.resolve(); + } catch (error) { + throw error; + } + }); }); - it('order by priority', async function () { - const ref = firebase.database().ref(TEST_PATH).child('query'); + describe('modular', function () { + it('throws if an orderBy call has already been set', async function () { + const { getDatabase, ref, orderByChild, orderByPriority, query } = databaseModular; + + try { + query(ref(getDatabase()), orderByChild('foo'), orderByPriority()); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You can't combine multiple orderBy calls"); + return Promise.resolve(); + } + }); + + it('order by priority', async function () { + const { getDatabase, ref, child, setPriority, orderByPriority, get, query } = databaseModular; + + const dbRef = child(ref(getDatabase(), TEST_PATH), 'query'); - await Promise.all([ - ref.child('a').setPriority(2), - ref.child('b').setPriority(3), - ref.child('c').setPriority(1), - ]); + await Promise.all([ + setPriority(child(dbRef, 'a'), 2), + setPriority(child(dbRef, 'b'), 3), + setPriority(child(dbRef, 'c'), 1), + ]); - try { - const snapshot = await ref.orderByPriority().once('value'); + try { + const snapshot = await get(query(dbRef, orderByPriority())); - const expected = ['c', 'a', 'b']; + const expected = ['c', 'a', 'b']; - snapshot.forEach((childSnapshot, i) => { - childSnapshot.key.should.eql(expected[i]); - }); + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); - return Promise.resolve(); - } catch (error) { - throw error; - } + return Promise.resolve(); + } catch (error) { + throw error; + } + }); }); }); diff --git a/packages/database/e2e/query/orderByValue.e2e.js b/packages/database/e2e/query/orderByValue.e2e.js index a925a304fe..b70298040e 100644 --- a/packages/database/e2e/query/orderByValue.e2e.js +++ b/packages/database/e2e/query/orderByValue.e2e.js @@ -27,37 +27,79 @@ describe('database().ref().orderByValue()', function () { await wipe(TEST_PATH); }); - it('throws if an orderBy call has already been set', async function () { - try { - await firebase.database().ref().orderByChild('foo').orderByValue(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("You can't combine multiple orderBy calls"); - return Promise.resolve(); - } + describe('v8 compatibility', function () { + it('throws if an orderBy call has already been set', async function () { + try { + await firebase.database().ref().orderByChild('foo').orderByValue(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You can't combine multiple orderBy calls"); + return Promise.resolve(); + } + }); + + it('order by value', async function () { + const ref = firebase.database().ref(TEST_PATH).child('query'); + + await ref.set({ + a: 2, + b: 3, + c: 1, + }); + + try { + const snapshot = await ref.orderByValue().once('value'); + + const expected = ['c', 'a', 'b']; + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); + + return Promise.resolve(); + } catch (error) { + throw error; + } + }); }); - it('order by value', async function () { - const ref = firebase.database().ref(TEST_PATH).child('query'); + describe('modular', function () { + it('throws if an orderBy call has already been set', async function () { + const { getDatabase, ref, orderByChild, orderByValue, query } = databaseModular; - await ref.set({ - a: 2, - b: 3, - c: 1, + try { + query(ref(getDatabase()), orderByChild('foo'), orderByValue()); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You can't combine multiple orderBy calls"); + return Promise.resolve(); + } }); - try { - const snapshot = await ref.orderByValue().once('value'); + it('order by value', async function () { + const { getDatabase, ref, child, set, orderByValue, get, query } = databaseModular; - const expected = ['c', 'a', 'b']; + const childRef = child(ref(getDatabase(), TEST_PATH), 'query'); - snapshot.forEach((childSnapshot, i) => { - childSnapshot.key.should.eql(expected[i]); + await set(childRef, { + a: 2, + b: 3, + c: 1, }); - return Promise.resolve(); - } catch (error) { - throw error; - } + try { + const snapshot = await get(query(childRef, orderByValue())); + + const expected = ['c', 'a', 'b']; + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); + + return Promise.resolve(); + } catch (error) { + throw error; + } + }); }); }); diff --git a/packages/database/e2e/query/query.e2e.js b/packages/database/e2e/query/query.e2e.js index ccf72f8543..c252a16eda 100644 --- a/packages/database/e2e/query/query.e2e.js +++ b/packages/database/e2e/query/query.e2e.js @@ -15,16 +15,35 @@ */ describe('DatabaseQuery/DatabaseQueryModifiers', function () { - it('should not mutate previous queries (#2691)', async function () { - const queryBefore = firebase.database().ref(); - queryBefore._modifiers._modifiers.length.should.equal(0); + describe('v8 compatibility', function () { + it('should not mutate previous queries (#2691)', async function () { + const queryBefore = firebase.database().ref(); + queryBefore._modifiers._modifiers.length.should.equal(0); - const queryAfter = queryBefore.orderByChild('age'); - queryBefore._modifiers._modifiers.length.should.equal(0); - queryAfter._modifiers._modifiers.length.should.equal(1); + const queryAfter = queryBefore.orderByChild('age'); + queryBefore._modifiers._modifiers.length.should.equal(0); + queryAfter._modifiers._modifiers.length.should.equal(1); - const queryAfterAfter = queryAfter.equalTo(30); - queryAfter._modifiers._modifiers.length.should.equal(1); - queryAfterAfter._modifiers._modifiers.length.should.equal(3); // adds startAt endAt internally + const queryAfterAfter = queryAfter.equalTo(30); + queryAfter._modifiers._modifiers.length.should.equal(1); + queryAfterAfter._modifiers._modifiers.length.should.equal(3); // adds startAt endAt internally + }); + }); + + describe('modular', function () { + it('should not mutate previous queries (#2691)', async function () { + const { getDatabase, ref, query, orderByChild, equalTo } = databaseModular; + + const queryBefore = ref(getDatabase()); + queryBefore._modifiers._modifiers.length.should.equal(0); + + const queryAfter = query(queryBefore, orderByChild('age')); + queryBefore._modifiers._modifiers.length.should.equal(0); + queryAfter._modifiers._modifiers.length.should.equal(1); + + const queryAfterAfter = query(queryAfter, equalTo(30)); + queryAfter._modifiers._modifiers.length.should.equal(1); + queryAfterAfter._modifiers._modifiers.length.should.equal(3); // adds startAt endAt internally + }); }); }); diff --git a/packages/database/e2e/query/startAt.e2e.js b/packages/database/e2e/query/startAt.e2e.js index 7849539213..13d30025da 100644 --- a/packages/database/e2e/query/startAt.e2e.js +++ b/packages/database/e2e/query/startAt.e2e.js @@ -27,104 +27,225 @@ describe('database().ref().startAt()', function () { await wipe(TEST_PATH); }); - it('throws if an value is undefined', async function () { - try { - await firebase.database().ref().startAt(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'value' must be a number, string, boolean or null value"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if an value is undefined', async function () { + try { + await firebase.database().ref().startAt(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be a number, string, boolean or null value"); + return Promise.resolve(); + } + }); - it('throws if an key is not a string', async function () { - try { - await firebase.database().ref().startAt('foo', 1234); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'key' must be a string value if defined"); - return Promise.resolve(); - } - }); + it('throws if an key is not a string', async function () { + try { + await firebase.database().ref().startAt('foo', 1234); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'key' must be a string value if defined"); + return Promise.resolve(); + } + }); - it('throws if a starting point has already been set', async function () { - try { - await firebase.database().ref().equalTo('foo').startAt('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'Starting point was already set (by another call to startAt or equalTo)', - ); - return Promise.resolve(); - } - }); + it('throws if a starting point has already been set', async function () { + try { + await firebase.database().ref().equalTo('foo').startAt('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Starting point was already set (by another call to startAt or equalTo)', + ); + return Promise.resolve(); + } + }); - it('throws if ordering by key and the key param is set', async function () { - try { - await firebase.database().ref().orderByKey('foo').startAt('foo', 'bar'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'When ordering by key, you may only pass a value argument to startAt(), endAt(), or equalTo()', - ); - return Promise.resolve(); - } - }); + it('throws if ordering by key and the key param is set', async function () { + try { + await firebase.database().ref().orderByKey('foo').startAt('foo', 'bar'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by key, you may only pass a value argument to startAt(), endAt(), or equalTo()', + ); + return Promise.resolve(); + } + }); - it('throws if ordering by key and the value param is not a string', async function () { - try { - await firebase.database().ref().orderByKey('foo').startAt(123); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'When ordering by key, the value of startAt(), endAt(), or equalTo() must be a string', - ); - return Promise.resolve(); - } - }); + it('throws if ordering by key and the value param is not a string', async function () { + try { + await firebase.database().ref().orderByKey('foo').startAt(123); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by key, the value of startAt(), endAt(), or equalTo() must be a string', + ); + return Promise.resolve(); + } + }); - it('throws if ordering by priority and the value param is not priority type', async function () { - try { - await firebase.database().ref().orderByPriority().startAt(true); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'When ordering by priority, the first value of startAt(), endAt(), or equalTo() must be a valid priority value (null, a number, or a string)', - ); - return Promise.resolve(); - } - }); + it('throws if ordering by priority and the value param is not priority type', async function () { + try { + await firebase.database().ref().orderByPriority().startAt(true); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by priority, the first value of startAt(), endAt(), or equalTo() must be a valid priority value (null, a number, or a string)', + ); + return Promise.resolve(); + } + }); - it('snapshot value is null when no ordering modifier is applied', async function () { - const ref = firebase.database().ref(TEST_PATH); + it('snapshot value is null when no ordering modifier is applied', async function () { + const ref = firebase.database().ref(TEST_PATH); - await ref.set({ - a: 1, - b: 2, - c: 3, - d: 4, + await ref.set({ + a: 1, + b: 2, + c: 3, + d: 4, + }); + + const snapshot = await ref.startAt(2).once('value'); + should.equal(snapshot.val(), null); }); - const snapshot = await ref.startAt(2).once('value'); - should.equal(snapshot.val(), null); + it('starts at the correct value', async function () { + const ref = firebase.database().ref(TEST_PATH); + + await ref.set({ + a: 1, + b: 2, + c: 3, + d: 4, + }); + + const snapshot = await ref.orderByValue().startAt(2).once('value'); + + const expected = ['b', 'c', 'd']; + + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); + }); }); - it('starts at the correct value', async function () { - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + it('throws if an value is undefined', async function () { + const { getDatabase, ref, startAt, query } = databaseModular; + + try { + query(ref(getDatabase()), startAt()); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be a number, string, boolean or null value"); + return Promise.resolve(); + } + }); + + it('throws if an key is not a string', async function () { + const { getDatabase, ref, startAt, query } = databaseModular; + + try { + query(ref(getDatabase()), startAt('foo', 1234)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'key' must be a string value if defined"); + return Promise.resolve(); + } + }); + + it('throws if a starting point has already been set', async function () { + const { getDatabase, ref, equalTo, startAt, query } = databaseModular; + + try { + query(ref(getDatabase()), equalTo('foo'), startAt('foo')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'Starting point was already set (by another call to startAt or equalTo)', + ); + return Promise.resolve(); + } + }); + + it('throws if ordering by key and the key param is set', async function () { + const { getDatabase, ref, orderByKey, startAt, query } = databaseModular; + + try { + query(ref(getDatabase()), orderByKey('foo'), startAt('foo', 'bar')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by key, you may only pass a value argument to startAt(), endAt(), or equalTo()', + ); + return Promise.resolve(); + } + }); + + it('throws if ordering by key and the value param is not a string', async function () { + const { getDatabase, ref, orderByKey, startAt, query } = databaseModular; + + try { + query(ref(getDatabase()), orderByKey('foo'), startAt(123)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by key, the value of startAt(), endAt(), or equalTo() must be a string', + ); + return Promise.resolve(); + } + }); - await ref.set({ - a: 1, - b: 2, - c: 3, - d: 4, + it('throws if ordering by priority and the value param is not priority type', async function () { + const { getDatabase, ref, orderByPriority, startAt, query } = databaseModular; + + try { + query(ref(getDatabase()), orderByPriority(), startAt(true)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'When ordering by priority, the first value of startAt(), endAt(), or equalTo() must be a valid priority value (null, a number, or a string)', + ); + return Promise.resolve(); + } }); - const snapshot = await ref.orderByValue().startAt(2).once('value'); + it('snapshot value is null when no ordering modifier is applied', async function () { + const { getDatabase, ref, set, startAt, get, query } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + await set(dbRef, { + a: 1, + b: 2, + c: 3, + d: 4, + }); + + const snapshot = await get(query(dbRef, startAt(2))); + should.equal(snapshot.val(), null); + }); + + it('starts at the correct value', async function () { + const { getDatabase, ref, set, startAt, get, query, orderByValue } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + await set(dbRef, { + a: 1, + b: 2, + c: 3, + d: 4, + }); + + const snapshot = await get(query(dbRef, orderByValue(), startAt(2))); - const expected = ['b', 'c', 'd']; + const expected = ['b', 'c', 'd']; - snapshot.forEach((childSnapshot, i) => { - childSnapshot.key.should.eql(expected[i]); + snapshot.forEach((childSnapshot, i) => { + childSnapshot.key.should.eql(expected[i]); + }); }); }); }); diff --git a/packages/database/e2e/query/toJSON.e2e.js b/packages/database/e2e/query/toJSON.e2e.js index 96a5e94f32..3efb80cae8 100644 --- a/packages/database/e2e/query/toJSON.e2e.js +++ b/packages/database/e2e/query/toJSON.e2e.js @@ -16,9 +16,22 @@ */ describe('database().ref().toJSON()', function () { - it('returns a string version of the current query path', async function () { - const res = firebase.database().ref('foo/bar/baz').toJSON(); - const expected = `${firebase.database()._customUrlOrRegion}/foo/bar/baz`; - should.equal(res, expected); + describe('v8 compatibility', function () { + it('returns a string version of the current query path', async function () { + const res = firebase.database().ref('foo/bar/baz').toJSON(); + const expected = `${firebase.database()._customUrlOrRegion}/foo/bar/baz`; + should.equal(res, expected); + }); + }); + + describe('modular', function () { + it('returns a string version of the current query path', async function () { + const { getDatabase, ref } = databaseModular; + + const db = getDatabase(); + const res = ref(db, 'foo/bar/baz').toJSON(); + const expected = `${db._customUrlOrRegion}/foo/bar/baz`; + should.equal(res, expected); + }); }); }); diff --git a/packages/database/e2e/reference/child.e2e.js b/packages/database/e2e/reference/child.e2e.js index 2827b7a8eb..d729dbc862 100644 --- a/packages/database/e2e/reference/child.e2e.js +++ b/packages/database/e2e/reference/child.e2e.js @@ -16,25 +16,59 @@ */ describe('database().ref().child()', function () { - it('throws if path is not a string', async function () { - try { - firebase.database().ref().child({ foo: 'bar' }); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'path' must be a string value"); - return Promise.resolve(); - } + describe('v8 compatibility', function () { + it('throws if path is not a string', async function () { + try { + firebase.database().ref().child({ foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'path' must be a string value"); + return Promise.resolve(); + } + }); + + it('throws if path is not a valid string', async function () { + try { + firebase.database().ref().child('$$$$$'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'firebase.database() Paths must be non-empty strings and can\'t contain ".", "#", "$", "[", or "]"', + ); + return Promise.resolve(); + } + }); }); - it('throws if path is not a valid string', async function () { - try { - firebase.database().ref().child('$$$$$'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql( - 'firebase.database() Paths must be non-empty strings and can\'t contain ".", "#", "$", "[", or "]"', - ); - return Promise.resolve(); - } + describe('modular', function () { + it('throws if path is not a string', async function () { + const { getDatabase, ref, child } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db); + + try { + child(dbRef, { foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'path' must be a string value"); + return Promise.resolve(); + } + }); + + it('throws if path is not a valid string', async function () { + const { getDatabase, ref, child } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db); + + try { + child(dbRef, '$$$$$'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'firebase.database() Paths must be non-empty strings and can\'t contain ".", "#", "$", "[", or "]"', + ); + return Promise.resolve(); + } + }); }); }); diff --git a/packages/database/e2e/reference/key.e2e.js b/packages/database/e2e/reference/key.e2e.js index bcccdd22c1..0ed277b6ee 100644 --- a/packages/database/e2e/reference/key.e2e.js +++ b/packages/database/e2e/reference/key.e2e.js @@ -16,15 +16,36 @@ */ describe('database().ref().key', function () { - it('returns null when no reference path is provides', function () { - const ref = firebase.database().ref(); - should.equal(ref.key, null); + describe('v8 compatibility', function () { + it('returns null when no reference path is provides', function () { + const ref = firebase.database().ref(); + should.equal(ref.key, null); + }); + + it('return last token in reference path', function () { + const ref1 = firebase.database().ref('foo'); + const ref2 = firebase.database().ref('foo/bar/baz'); + ref1.key.should.equal('foo'); + ref2.key.should.equal('baz'); + }); }); - it('return last token in reference path', function () { - const ref1 = firebase.database().ref('foo'); - const ref2 = firebase.database().ref('foo/bar/baz'); - ref1.key.should.equal('foo'); - ref2.key.should.equal('baz'); + describe('modular', function () { + it('returns null when no reference path is provides', function () { + const { getDatabase, ref } = databaseModular; + + const dbRef = ref(getDatabase()); + should.equal(dbRef.key, null); + }); + + it('return last token in reference path', function () { + const { getDatabase, ref } = databaseModular; + + const db = getDatabase(); + const ref1 = ref(db, 'foo'); + const ref2 = ref(db, 'foo/bar/baz'); + ref1.key.should.equal('foo'); + ref2.key.should.equal('baz'); + }); }); }); diff --git a/packages/database/e2e/reference/onDisconnect.e2e.js b/packages/database/e2e/reference/onDisconnect.e2e.js index e308e7f6e2..8540faf7c4 100644 --- a/packages/database/e2e/reference/onDisconnect.e2e.js +++ b/packages/database/e2e/reference/onDisconnect.e2e.js @@ -18,8 +18,19 @@ // See onDisconnect directory for specific tests describe('database().ref().onDisconnect()', function () { - it('returns a new DatabaseOnDisconnect instance', function () { - const instance = firebase.database().ref().onDisconnect(); - instance.constructor.name.should.eql('DatabaseOnDisconnect'); + describe('v8 compatibility', function () { + it('returns a new DatabaseOnDisconnect instance', function () { + const instance = firebase.database().ref().onDisconnect(); + instance.constructor.name.should.eql('DatabaseOnDisconnect'); + }); + }); + + describe('modular', function () { + it('returns a new DatabaseOnDisconnect instance', function () { + const { getDatabase, ref, onDisconnect } = databaseModular; + + const instance = onDisconnect(ref(getDatabase())); + instance.constructor.name.should.eql('DatabaseOnDisconnect'); + }); }); }); diff --git a/packages/database/e2e/reference/parent.e2e.js b/packages/database/e2e/reference/parent.e2e.js index 592b6e2f12..7dec1d8799 100644 --- a/packages/database/e2e/reference/parent.e2e.js +++ b/packages/database/e2e/reference/parent.e2e.js @@ -16,15 +16,36 @@ */ describe('database().ref().parent', function () { - it('returns null when no reference path is provides', function () { - const ref = firebase.database().ref(); - should.equal(ref.parent, null); + describe('v8 compatibility', function () { + it('returns null when no reference path is provides', function () { + const ref = firebase.database().ref(); + should.equal(ref.parent, null); + }); + + it('return last token in reference path', function () { + const ref1 = firebase.database().ref('/foo').parent; + const ref2 = firebase.database().ref('/foo/bar/baz').parent; + should.equal(ref1, null); + ref2.key.should.equal('bar'); + }); }); - it('return last token in reference path', function () { - const ref1 = firebase.database().ref('/foo').parent; - const ref2 = firebase.database().ref('/foo/bar/baz').parent; - should.equal(ref1, null); - ref2.key.should.equal('bar'); + describe('modular', function () { + it('returns null when no reference path is provides', function () { + const { getDatabase, ref } = databaseModular; + + const dbRef = ref(getDatabase()); + should.equal(dbRef.parent, null); + }); + + it('return last token in reference path', function () { + const { getDatabase, ref } = databaseModular; + + const db = getDatabase(); + const ref1 = ref(db, '/foo').parent; + const ref2 = ref(db, '/foo/bar/baz').parent; + should.equal(ref1, null); + ref2.key.should.equal('bar'); + }); }); }); diff --git a/packages/database/e2e/reference/push.e2e.js b/packages/database/e2e/reference/push.e2e.js index 234c55a5ec..f57bff6b5d 100644 --- a/packages/database/e2e/reference/push.e2e.js +++ b/packages/database/e2e/reference/push.e2e.js @@ -20,72 +20,123 @@ const { PATH } = require('../helpers'); const TEST_PATH = `${PATH}/push`; describe('database().ref().push()', function () { - it('throws if on complete callback is not a function', function () { - try { - firebase.database().ref(TEST_PATH).push('foo', 'bar'); - return Promise.reject(new Error('Did not throw Error')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if on complete callback is not a function', function () { + try { + firebase.database().ref(TEST_PATH).push('foo', 'bar'); + return Promise.reject(new Error('Did not throw Error')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); - it('returns a promise when no value is passed', function () { - const ref = firebase.database().ref(`${TEST_PATH}/boop`); - const pushed = ref.push(); - return pushed - .then(childRef => { - pushed.ref.parent.toString().should.eql(ref.toString()); - pushed.toString().should.eql(childRef.toString()); - return pushed.once('value'); - }) - .then(snap => { - should.equal(snap.val(), null); - snap.ref.toString().should.eql(pushed.toString()); + it('returns a promise when no value is passed', function () { + const ref = firebase.database().ref(`${TEST_PATH}/boop`); + const pushed = ref.push(); + return pushed + .then(childRef => { + pushed.ref.parent.toString().should.eql(ref.toString()); + pushed.toString().should.eql(childRef.toString()); + return pushed.once('value'); + }) + .then(snap => { + should.equal(snap.val(), null); + snap.ref.toString().should.eql(pushed.toString()); + }); + }); + + it('returns a promise and sets the provided value', function () { + const ref = firebase.database().ref(`${TEST_PATH}/value`); + const pushed = ref.push(6); + return pushed + .then(childRef => { + pushed.ref.parent.toString().should.eql(ref.toString()); + pushed.toString().should.eql(childRef.toString()); + return pushed.once('value'); + }) + .then(snap => { + snap.val().should.equal(6); + snap.ref.toString().should.eql(pushed.toString()); + }); + }); + + it('returns a to the callback if provided once set', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(`${TEST_PATH}/callback`); + const value = Date.now(); + ref.push(value, () => { + callback(); }); - }); + await Utils.spyToBeCalledOnceAsync(callback); + }); - it('returns a promise and sets the provided value', function () { - const ref = firebase.database().ref(`${TEST_PATH}/value`); - const pushed = ref.push(6); - return pushed - .then(childRef => { - pushed.ref.parent.toString().should.eql(ref.toString()); - pushed.toString().should.eql(childRef.toString()); - return pushed.once('value'); - }) - .then(snap => { - snap.val().should.equal(6); - snap.ref.toString().should.eql(pushed.toString()); + it('throws if push errors', async function () { + const ref = firebase.database().ref('nope'); + return ref.push('foo').catch(error => { + error.message.should.containEql("doesn't have permission to access"); + return Promise.resolve(); }); - }); + }); - it('returns a to the callback if provided once set', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(`${TEST_PATH}/callback`); - const value = Date.now(); - ref.push(value, () => { - callback(); + it('returns an error to the callback', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref('nope'); + ref.push('foo', error => { + error.message.should.containEql("doesn't have permission to access"); + callback(); + }); + await Utils.spyToBeCalledOnceAsync(callback); + callback.should.be.calledOnce(); }); - await Utils.spyToBeCalledOnceAsync(callback); }); - it('throws if push errors', async function () { - const ref = firebase.database().ref('nope'); - return ref.push('foo').catch(error => { - error.message.should.containEql("doesn't have permission to access"); - return Promise.resolve(); + describe('modular', function () { + it('returns a promise when no value is passed', function () { + const { getDatabase, ref, push } = databaseModular; + + const dbRef = ref(getDatabase(), `${TEST_PATH}/boop`); + const pushed = push(dbRef); + return pushed + .then(childRef => { + pushed.ref.parent.toString().should.eql(dbRef.toString()); + pushed.toString().should.eql(childRef.toString()); + return pushed.once('value'); + }) + .then(snap => { + should.equal(snap.val(), null); + snap.ref.toString().should.eql(pushed.toString()); + }); }); - }); - it('returns an error to the callback', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref('nope'); - ref.push('foo', error => { - error.message.should.containEql("doesn't have permission to access"); - callback(); + it('returns a promise and sets the provided value', function () { + const { getDatabase, ref, push } = databaseModular; + + const dbRef = ref(getDatabase(), `${TEST_PATH}/value`); + const pushed = push(dbRef, 6); + return pushed + .then(childRef => { + pushed.ref.parent.toString().should.eql(dbRef.toString()); + pushed.toString().should.eql(childRef.toString()); + return pushed.once('value'); + }) + .then(snap => { + snap.val().should.equal(6); + snap.ref.toString().should.eql(pushed.toString()); + }); + }); + + it('throws if push errors', async function () { + const { getDatabase, ref, push } = databaseModular; + + const dbRef = ref(getDatabase(), 'nope'); + try { + await push(dbRef, 'foo'); + return Promise.reject(new Error('Did not throw Error')); + } catch (error) { + error.message.should.containEql("doesn't have permission to access"); + return Promise.resolve(); + } }); - await Utils.spyToBeCalledOnceAsync(callback); - callback.should.be.calledOnce(); }); }); diff --git a/packages/database/e2e/reference/remove.e2e.js b/packages/database/e2e/reference/remove.e2e.js index 4b62800801..4fa18e76a2 100644 --- a/packages/database/e2e/reference/remove.e2e.js +++ b/packages/database/e2e/reference/remove.e2e.js @@ -20,21 +20,35 @@ const { PATH } = require('../helpers'); const TEST_PATH = `${PATH}/remove`; describe('database().ref().remove()', function () { - it('throws if onComplete is not a function', async function () { - try { - await firebase.database().ref(TEST_PATH).remove('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } + describe('v8 compatibility', function () { + it('throws if onComplete is not a function', async function () { + try { + await firebase.database().ref(TEST_PATH).remove('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + it('removes a value at the path', async function () { + const ref = firebase.database().ref(TEST_PATH); + await ref.set('foo'); + await ref.remove(); + const snapshot = await ref.once('value'); + snapshot.exists().should.equal(false); + }); }); - it('removes a value at the path', async function () { - const ref = firebase.database().ref(TEST_PATH); - await ref.set('foo'); - await ref.remove(); - const snapshot = await ref.once('value'); - snapshot.exists().should.equal(false); + describe('modular', function () { + it('removes a value at the path', async function () { + const { getDatabase, ref, set, remove, get } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + await set(dbRef, 'foo'); + await remove(dbRef); + const snapshot = await get(dbRef); + snapshot.exists().should.equal(false); + }); }); }); diff --git a/packages/database/e2e/reference/root.e2e.js b/packages/database/e2e/reference/root.e2e.js index c7677ce9ee..227076830c 100644 --- a/packages/database/e2e/reference/root.e2e.js +++ b/packages/database/e2e/reference/root.e2e.js @@ -16,8 +16,19 @@ */ describe('database().ref().root', function () { - it('returns a root reference', function () { - const ref = firebase.database().ref('foo/bar/baz'); - should.equal(ref.root.key, null); + describe('v8 compatibility', function () { + it('returns a root reference', function () { + const ref = firebase.database().ref('foo/bar/baz'); + should.equal(ref.root.key, null); + }); + }); + + describe('modular', function () { + it('returns a root reference', function () { + const { getDatabase, ref } = databaseModular; + + const dbRef = ref(getDatabase(), 'foo/bar/baz'); + should.equal(dbRef.root.key, null); + }); }); }); diff --git a/packages/database/e2e/reference/set.e2e.js b/packages/database/e2e/reference/set.e2e.js index 3f49bbb5ed..adaaf39f43 100644 --- a/packages/database/e2e/reference/set.e2e.js +++ b/packages/database/e2e/reference/set.e2e.js @@ -19,7 +19,7 @@ const { PATH, seed, wipe } = require('../helpers'); const TEST_PATH = `${PATH}/set`; -describe('database().ref().set()', function () { +describe('set', function () { before(async function () { await seed(TEST_PATH); }); @@ -27,49 +27,92 @@ describe('database().ref().set()', function () { await wipe(TEST_PATH); }); - it('throws if no value is provided', async function () { - try { - await firebase.database().ref(TEST_PATH).set(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'value' must be defined"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if no value is provided', async function () { + try { + await firebase.database().ref(TEST_PATH).set(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be defined"); + return Promise.resolve(); + } + }); - it('throws if onComplete is not a function', async function () { - try { - await firebase.database().ref(TEST_PATH).set(null, 'foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } - }); + it('throws if onComplete is not a function', async function () { + try { + await firebase.database().ref(TEST_PATH).set(null, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); - it('sets a new value', async function () { - const value = Date.now(); - const ref = firebase.database().ref(TEST_PATH); - await ref.set(value); - const snapshot = await ref.once('value'); - snapshot.val().should.eql(value); - }); + it('sets a new value', async function () { + const value = Date.now(); + const ref = firebase.database().ref(TEST_PATH); + await ref.set(value); + const snapshot = await ref.once('value'); + snapshot.val().should.eql(value); + }); + + it('callback if function is passed', async function () { + const value = Date.now(); + return new Promise(async resolve => { + await firebase.database().ref(TEST_PATH).set(value, resolve); + }); + }); - it('callback if function is passed', async function () { - const value = Date.now(); - return new Promise(async resolve => { - await firebase.database().ref(TEST_PATH).set(value, resolve); + it('throws if permission defined', async function () { + const value = Date.now(); + try { + await firebase.database().ref('nope/foo').set(value); + return Promise.reject(new Error('Did not throw error.')); + } catch (error) { + error.code.includes('database/permission-denied').should.be.true(); + return Promise.resolve(); + } }); }); - it('throws if permission defined', async function () { - const value = Date.now(); - try { - await firebase.database().ref('nope/foo').set(value); - return Promise.reject(new Error('Did not throw error.')); - } catch (error) { - error.code.includes('database/permission-denied').should.be.true(); - return Promise.resolve(); - } + describe('modular', function () { + it('throws if no value is provided', async function () { + const { getDatabase, ref, set } = databaseModular; + const db = getDatabase(); + + try { + await set(ref(db, TEST_PATH)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' must be defined"); + return Promise.resolve(); + } + }); + + it('sets a new value', async function () { + const { getDatabase, ref, set, get } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, TEST_PATH); + + const value = Date.now(); + await set(dbRef, value); + const snapshot = await get(dbRef); + snapshot.val().should.eql(value); + }); + + it('throws if permission defined', async function () { + const { getDatabase, ref, set } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, 'nope/foo'); + + const value = Date.now(); + try { + await set(dbRef, value); + return Promise.reject(new Error('Did not throw error.')); + } catch (error) { + error.code.includes('database/permission-denied').should.be.true(); + return Promise.resolve(); + } + }); }); }); diff --git a/packages/database/e2e/reference/setPriority.e2e.js b/packages/database/e2e/reference/setPriority.e2e.js index f1bba1e334..8db16c14af 100644 --- a/packages/database/e2e/reference/setPriority.e2e.js +++ b/packages/database/e2e/reference/setPriority.e2e.js @@ -27,64 +27,128 @@ describe('database().ref().setPriority()', function () { await wipe(TEST_PATH); }); - it('throws if priority is not a valid type', async function () { - try { - await firebase.database().ref().setPriority({}); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'priority' must be a number, string or null value"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if priority is not a valid type', async function () { + try { + await firebase.database().ref().setPriority({}); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'priority' must be a number, string or null value"); + return Promise.resolve(); + } + }); - it('throws if onComplete is not a function', async function () { - try { - await firebase.database().ref().setPriority(null, 'foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } - }); + it('throws if onComplete is not a function', async function () { + try { + await firebase.database().ref().setPriority(null, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); - it('should correctly set a priority for all non-null values', async function () { - await Promise.all( - Object.keys(CONTENT.TYPES).map(async dataRef => { - const ref = firebase.database().ref(`${TEST_PATH}/types/${dataRef}`); - await ref.setPriority(1); - const snapshot = await ref.once('value'); - if (snapshot.val() !== null) { - snapshot.getPriority().should.eql(1); - } - }), - ); - }); + it('should correctly set a priority for all non-null values', async function () { + await Promise.all( + Object.keys(CONTENT.TYPES).map(async dataRef => { + const ref = firebase.database().ref(`${TEST_PATH}/types/${dataRef}`); + await ref.setPriority(1); + const snapshot = await ref.once('value'); + if (snapshot.val() !== null) { + snapshot.getPriority().should.eql(1); + } + }), + ); + }); - it('callback if function is passed', async function () { - const value = Date.now(); - return new Promise(async resolve => { - await firebase.database().ref(`${TEST_PATH}/types/string`).set(value, resolve); + it('callback if function is passed', async function () { + const value = Date.now(); + return new Promise(async resolve => { + await firebase.database().ref(`${TEST_PATH}/types/string`).set(value, resolve); + }); + }); + + it('throws if setting priority on non-existent node', async function () { + try { + await firebase.database().ref('tests/siudfhsuidfj').setPriority(1); + return Promise.reject(new Error('Did not throw error.')); + } catch (error) { + // WEB SDK: INVALID_PARAMETERS: could not set priority on non-existent node + // TODO Get this error? Native code = -999 Unknown + return Promise.resolve(); + } }); - }); - it('throws if setting priority on non-existent node', async function () { - try { - await firebase.database().ref('tests/siudfhsuidfj').setPriority(1); - return Promise.reject(new Error('Did not throw error.')); - } catch (error) { - // WEB SDK: INVALID_PARAMETERS: could not set priority on non-existent node - // TODO Get this error? Native code = -999 Unknown - return Promise.resolve(); - } + it('throws if permission defined', async function () { + try { + await firebase.database().ref('nope/foo').setPriority(1); + return Promise.reject(new Error('Did not throw error.')); + } catch (error) { + error.code.includes('database/permission-denied').should.be.true(); + return Promise.resolve(); + } + }); }); - it('throws if permission defined', async function () { - try { - await firebase.database().ref('nope/foo').setPriority(1); - return Promise.reject(new Error('Did not throw error.')); - } catch (error) { - error.code.includes('database/permission-denied').should.be.true(); - return Promise.resolve(); - } + describe('modular', function () { + it('throws if priority is not a valid type', async function () { + const { getDatabase, ref, setPriority } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db); + + try { + await setPriority(dbRef, {}); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'priority' must be a number, string or null value"); + return Promise.resolve(); + } + }); + + it('should correctly set a priority for all non-null values', async function () { + const { getDatabase, ref, setPriority, get } = databaseModular; + + await Promise.all( + Object.keys(CONTENT.TYPES).map(async dataRef => { + const db = getDatabase(); + const dbRef = ref(db, `${TEST_PATH}/types/${dataRef}`); + + await setPriority(dbRef, 1); + const snapshot = await get(dbRef); + if (snapshot.val() !== null) { + snapshot.getPriority().should.eql(1); + } + }), + ); + }); + + it('throws if setting priority on non-existent node', async function () { + const { getDatabase, ref, setPriority } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, 'tests/siudfhsuidfj'); + + try { + await setPriority(dbRef, 1); + return Promise.reject(new Error('Did not throw error.')); + } catch (error) { + // WEB SDK: INVALID_PARAMETERS: could not set priority on non-existent node + // TODO Get this error? Native code = -999 Unknown + return Promise.resolve(); + } + }); + + it('throws if permission defined', async function () { + const { getDatabase, ref, setPriority } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, 'nope/foo'); + + try { + await setPriority(dbRef, 1); + return Promise.reject(new Error('Did not throw error.')); + } catch (error) { + error.code.includes('database/permission-denied').should.be.true(); + return Promise.resolve(); + } + }); }); }); diff --git a/packages/database/e2e/reference/setWithPriority.e2e.js b/packages/database/e2e/reference/setWithPriority.e2e.js index 24b3508454..6e4c156535 100644 --- a/packages/database/e2e/reference/setWithPriority.e2e.js +++ b/packages/database/e2e/reference/setWithPriority.e2e.js @@ -27,52 +27,96 @@ describe('database().ref().setWithPriority()', function () { await wipe(TEST_PATH); }); - it('throws if newVal is not defined', async function () { - try { - await firebase.database().ref().setWithPriority(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'newVal' must be defined"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if newVal is not defined', async function () { + try { + await firebase.database().ref().setWithPriority(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'newVal' must be defined"); + return Promise.resolve(); + } + }); - it('throws if newPriority is incorrect type', async function () { - try { - await firebase.database().ref().setWithPriority(null, { foo: 'bar' }); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'newPriority' must be a number, string or null value"); - return Promise.resolve(); - } - }); + it('throws if newPriority is incorrect type', async function () { + try { + await firebase.database().ref().setWithPriority(null, { foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'newPriority' must be a number, string or null value"); + return Promise.resolve(); + } + }); - it('throws if onComplete is not a function', async function () { - try { - await firebase.database().ref().setWithPriority(null, null, 'foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } - }); + it('throws if onComplete is not a function', async function () { + try { + await firebase.database().ref().setWithPriority(null, null, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + it('callback if function is passed', async function () { + const value = Date.now(); + return new Promise(async resolve => { + await firebase + .database() + .ref(`${TEST_PATH}/setValueWithCallback`) + .setWithPriority(value, 2, resolve); + }); + }); - it('callback if function is passed', async function () { - const value = Date.now(); - return new Promise(async resolve => { - await firebase - .database() - .ref(`${TEST_PATH}/setValueWithCallback`) - .setWithPriority(value, 2, resolve); + it('sets with a new value and priority', async function () { + const value = Date.now(); + const ref = firebase.database().ref(`${TEST_PATH}/setValue`); + await ref.setWithPriority(value, 2); + const snapshot = await ref.once('value'); + snapshot.val().should.eql(value); + snapshot.getPriority().should.eql(2); }); }); - it('sets with a new value and priority', async function () { - const value = Date.now(); - const ref = firebase.database().ref(`${TEST_PATH}/setValue`); - await ref.setWithPriority(value, 2); - const snapshot = await ref.once('value'); - snapshot.val().should.eql(value); - snapshot.getPriority().should.eql(2); + describe('modular', function () { + it('throws if newVal is not defined', async function () { + const { getDatabase, ref, setWithPriority } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db); + + try { + await setWithPriority(dbRef); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'newVal' must be defined"); + return Promise.resolve(); + } + }); + + it('throws if newPriority is incorrect type', async function () { + const { getDatabase, ref, setWithPriority } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db); + + try { + await setWithPriority(dbRef, null, { foo: 'bar' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'newPriority' must be a number, string or null value"); + return Promise.resolve(); + } + }); + + it('sets with a new value and priority', async function () { + const { getDatabase, ref, setWithPriority, get } = databaseModular; + const db = getDatabase(); + const dbRef = ref(db, `${TEST_PATH}/setValue`); + + const value = Date.now(); + await setWithPriority(dbRef, value, 2); + const snapshot = await get(dbRef); + snapshot.val().should.eql(value); + snapshot.getPriority().should.eql(2); + }); }); }); diff --git a/packages/database/e2e/reference/transaction.e2e.js b/packages/database/e2e/reference/transaction.e2e.js index c581cefcf3..64f67cb27b 100644 --- a/packages/database/e2e/reference/transaction.e2e.js +++ b/packages/database/e2e/reference/transaction.e2e.js @@ -28,83 +28,172 @@ describe('database().ref().transaction()', function () { await wipe(TEST_PATH); }); - it('throws if no transactionUpdate is provided', async function () { - try { - await firebase.database().ref(TEST_PATH).transaction(); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'transactionUpdate' must be a function"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if no transactionUpdate is provided', async function () { + try { + await firebase.database().ref(TEST_PATH).transaction(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'transactionUpdate' must be a function"); + return Promise.resolve(); + } + }); - it('throws if onComplete is not a function', async function () { - try { - await firebase.database().ref().transaction(NOOP, 'foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } - }); + it('throws if onComplete is not a function', async function () { + try { + await firebase.database().ref().transaction(NOOP, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); - it('throws if applyLocally is not a boolean', async function () { - try { - await firebase.database().ref().transaction(NOOP, NOOP, 'foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'applyLocally' must be a boolean value if provided"); - return Promise.resolve(); - } - }); + it('throws if applyLocally is not a boolean', async function () { + try { + await firebase.database().ref().transaction(NOOP, NOOP, 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'applyLocally' must be a boolean value if provided"); + return Promise.resolve(); + } + }); + + // FIXME this test works in isolation but not running in the suite? + xit('updates the value via a transaction', async function () { + const ref = firebase.database().ref(`${TEST_PATH}/transactionUpdate`); + await ref.set(1); + await Utils.sleep(2000); + const { committed, snapshot } = await ref.transaction(value => { + return value + 1; + }); - // FIXME this test works in isolation but not running in the suite? - xit('updates the value via a transaction', async function () { - const ref = firebase.database().ref(`${TEST_PATH}/transactionUpdate`); - await ref.set(1); - await Utils.sleep(2000); - const { committed, snapshot } = await ref.transaction(value => { - return value + 1; + should.equal(committed, true, 'Transaction did not commit.'); + snapshot.val().should.equal(2); }); - should.equal(committed, true, 'Transaction did not commit.'); - snapshot.val().should.equal(2); - }); + it('aborts transaction if undefined returned', async function () { + const ref = firebase.database().ref(`${TEST_PATH}/transactionAbort`); + await ref.set(1); - it('aborts transaction if undefined returned', async function () { - const ref = firebase.database().ref(`${TEST_PATH}/transactionAbort`); - await ref.set(1); - - return new Promise((resolve, reject) => { - ref.transaction( - () => { - return undefined; - }, - (error, committed) => { - if (error) { - return reject(error); - } - - if (!committed) { - return resolve(); - } + return new Promise((resolve, reject) => { + ref.transaction( + () => { + return undefined; + }, + (error, committed) => { + if (error) { + return reject(error); + } - return reject(new Error('Transaction did not abort commit.')); - }, - ); + if (!committed) { + return resolve(); + } + + return reject(new Error('Transaction did not abort commit.')); + }, + ); + }); }); - }); - // FIXME flaky on android local against emulator? - it('passes valid data through the callback', async function () { - if (device.getPlatform() === 'ios') { - const ref = firebase.database().ref(`${TEST_PATH}/transactionCallback`); - await ref.set(1); + // FIXME flaky on android local against emulator? + it('passes valid data through the callback', async function () { + if (device.getPlatform() === 'ios') { + const ref = firebase.database().ref(`${TEST_PATH}/transactionCallback`); + await ref.set(1); + + return new Promise((resolve, reject) => { + ref.transaction( + $ => { + return $ + 1; + }, + (error, committed, snapshot) => { + if (error) { + return reject(error); + } + + if (!committed) { + return reject(new Error('Transaction aborted when it should not have done')); + } + + should.equal(snapshot.val(), 2); + return resolve(); + }, + ); + }); + } else { + this.skip(); + } + }); + + // FIXME flaky on android local against emulator? + it('throws when an error occurs', async function () { + if (device.getPlatform() === 'ios') { + const ref = firebase.database().ref('nope'); + + try { + await ref.transaction($ => { + return $ + 1; + }); + return Promise.reject(new Error('Did not throw error.')); + } catch (error) { + error.message.should.containEql( + "Client doesn't have permission to access the desired data", + ); + return Promise.resolve(); + } + } else { + this.skip(); + } + }); + + // FIXME flaky on android in CI? works most of the time... + it('passes error back to the callback', async function () { + if (device.getPlatform() === 'ios' || !global.isCI) { + const ref = firebase.database().ref('nope'); + + return new Promise((resolve, reject) => { + ref + .transaction( + $ => { + return $ + 1; + }, + (error, committed, snapshot) => { + if (snapshot !== null) { + return reject(new Error('Snapshot should not be available')); + } + + if (committed === true) { + return reject(new Error('Transaction should not have committed')); + } + + error.message.should.containEql( + "Client doesn't have permission to access the desired data", + ); + return resolve(); + }, + ) + .catch(e => { + return reject(e); + }); + }); + } else { + this.skip(); + } + }); + + it('sets a value if one does not exist', async function () { + const ref = firebase.database().ref(`${TEST_PATH}/transactionCreate`); + const value = Date.now(); return new Promise((resolve, reject) => { ref.transaction( $ => { - return $ + 1; + if ($ === null) { + return { foo: value }; + } + + throw new Error('Value should not exist'); }, (error, committed, snapshot) => { if (error) { @@ -112,104 +201,124 @@ describe('database().ref().transaction()', function () { } if (!committed) { - return reject(new Error('Transaction aborted when it should not have done')); + return reject(new Error('Transaction should have committed')); } - should.equal(snapshot.val(), 2); + snapshot.val().should.eql( + jet.contextify({ + foo: value, + }), + ); return resolve(); }, ); }); - } else { - this.skip(); - } + }); }); - // FIXME flaky on android local against emulator? - it('throws when an error occurs', async function () { - if (device.getPlatform() === 'ios') { - const ref = firebase.database().ref('nope'); + describe('modular', function () { + it('throws if no transactionUpdate is provided', async function () { + const { getDatabase, ref, runTransaction } = databaseModular; try { - await ref.transaction($ => { - return $ + 1; - }); - return Promise.reject(new Error('Did not throw error.')); + await runTransaction(ref(getDatabase(), TEST_PATH)); + return Promise.reject(new Error('Did not throw an Error.')); } catch (error) { - error.message.should.containEql( - "Client doesn't have permission to access the desired data", - ); + error.message.should.containEql("'transactionUpdate' must be a function"); return Promise.resolve(); } - } else { - this.skip(); - } - }); + }); - // FIXME flaky on android in CI? works most of the time... - it('passes error back to the callback', async function () { - if (device.getPlatform() === 'ios' || !global.isCI) { - const ref = firebase.database().ref('nope'); + it('throws if options.applyLocally is not a boolean', async function () { + const { getDatabase, ref, runTransaction } = databaseModular; - return new Promise((resolve, reject) => { - ref - .transaction( - $ => { - return $ + 1; - }, - (error, committed, snapshot) => { - if (snapshot !== null) { - return reject(new Error('Snapshot should not be available')); - } + try { + await runTransaction(ref(getDatabase(), TEST_PATH), NOOP, { applyLocally: 'foo' }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'applyLocally' must be a boolean value if provided"); + return Promise.resolve(); + } + }); - if (committed === true) { - return reject(new Error('Transaction should not have committed')); - } + // FIXME this test works in isolation but not running in the suite? + xit('updates the value via a transaction', async function () { + const { getDatabase, set, ref, runTransaction } = databaseModular; - error.message.should.containEql( - "Client doesn't have permission to access the desired data", - ); - return resolve(); - }, - ) - .catch(e => { - return reject(e); - }); + const dbRef = ref(getDatabase(), `${TEST_PATH}/transactionUpdate`); + await set(dbRef, 1); + await Utils.sleep(2000); + const { committed, snapshot } = await runTransaction(dbRef, value => { + return value + 1; }); - } else { - this.skip(); - } - }); - it('sets a value if one does not exist', async function () { - const ref = firebase.database().ref(`${TEST_PATH}/transactionCreate`); - const value = Date.now(); - - return new Promise((resolve, reject) => { - ref.transaction( - $ => { - if ($ === null) { - return { foo: value }; - } - - throw new Error('Value should not exist'); - }, - (error, committed, snapshot) => { - if (error) { - return reject(error); - } - - if (!committed) { - return reject(new Error('Transaction should have committed')); - } - - snapshot.val().should.eql( - jet.contextify({ - foo: value, - }), + should.equal(committed, true, 'Transaction did not commit.'); + snapshot.val().should.equal(2); + }); + + it('aborts transaction if undefined returned', async function () { + const { getDatabase, set, ref, runTransaction } = databaseModular; + + const dbRef = ref(getDatabase(), `${TEST_PATH}/transactionAbort`); + await set(dbRef, 1); + + const { committed } = await runTransaction(dbRef, () => { + return undefined; + }); + + if (!committed) { + return; + } + + throw new Error('Transaction did not abort commit.'); + }); + + // FIXME flaky on android local against emulator? + it('throws when an error occurs', async function () { + if (device.getPlatform() === 'ios') { + const { getDatabase, ref, runTransaction } = databaseModular; + + const dbRef = ref(getDatabase(), 'nope'); + + try { + await runTransaction(dbRef, $ => { + return $ + 1; + }); + return Promise.reject(new Error('Did not throw error.')); + } catch (error) { + error.message.should.containEql( + "Client doesn't have permission to access the desired data", ); - return resolve(); - }, + return Promise.resolve(); + } + } else { + this.skip(); + } + }); + + // FIXME runs in isolation but not in suite. Crashes on iOS, and gets stuck on Android. + xit('sets a value if one does not exist', async function () { + const { getDatabase, ref, runTransaction } = databaseModular; + + const dbRef = ref(getDatabase(), `${TEST_PATH}/transactionCreate`); + const value = Date.now(); + + const { committed, snapshot } = await runTransaction(dbRef, $ => { + if ($ === null) { + return { foo: value }; + } + + throw new Error('Value should not exist'); + }); + + if (!committed) { + throw new Error('Transaction should have committed'); + } + + snapshot.val().should.eql( + jet.contextify({ + foo: value, + }), ); }); }); diff --git a/packages/database/e2e/reference/update.e2e.js b/packages/database/e2e/reference/update.e2e.js index 3dc34639e7..dd20261136 100644 --- a/packages/database/e2e/reference/update.e2e.js +++ b/packages/database/e2e/reference/update.e2e.js @@ -24,73 +24,127 @@ describe('database().ref().update()', function () { await firebase.database().ref(TEST_PATH).remove(); }); - it('throws if values is not an object', async function () { - try { - await firebase.database().ref(TEST_PATH).update('foo'); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'values' must be an object"); - return Promise.resolve(); - } - }); + describe('v8 compatibility', function () { + it('throws if values is not an object', async function () { + try { + await firebase.database().ref(TEST_PATH).update('foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' must be an object"); + return Promise.resolve(); + } + }); + + it('throws if update paths are not valid', async function () { + try { + await firebase.database().ref(TEST_PATH).update({ + $$$$: 'foo', + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' contains an invalid path."); + return Promise.resolve(); + } + }); - it('throws if update paths are not valid', async function () { - try { - await firebase.database().ref(TEST_PATH).update({ - $$$$: 'foo', + it('throws if onComplete is not a function', async function () { + try { + await firebase.database().ref(TEST_PATH).update( + { + foo: 'bar', + }, + 'foo', + ); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'onComplete' must be a function if provided"); + return Promise.resolve(); + } + }); + + it('updates values', async function () { + const value = Date.now(); + const ref = firebase.database().ref(TEST_PATH); + await ref.update({ + foo: value, }); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'values' contains an invalid path."); - return Promise.resolve(); - } - }); + const snapshot = await ref.once('value'); + snapshot.val().should.eql( + jet.contextify({ + foo: value, + }), + ); - it('throws if onComplete is not a function', async function () { - try { - await firebase.database().ref(TEST_PATH).update( - { - foo: 'bar', - }, - 'foo', + await ref.update({}); // empty update should pass, but no side effects + const snapshot2 = await ref.once('value'); + snapshot2.val().should.eql( + jet.contextify({ + foo: value, + }), ); - return Promise.reject(new Error('Did not throw an Error.')); - } catch (error) { - error.message.should.containEql("'onComplete' must be a function if provided"); - return Promise.resolve(); - } + }); + + it('callback if function is passed', async function () { + const value = Date.now(); + return new Promise(async resolve => { + await firebase.database().ref(TEST_PATH).update( + { + foo: value, + }, + resolve, + ); + }); + }); }); - it('updates values', async function () { - const value = Date.now(); - const ref = firebase.database().ref(TEST_PATH); - await ref.update({ - foo: value, + describe('modular', function () { + it('throws if values is not an object', async function () { + const { getDatabase, ref, update } = databaseModular; + + try { + await update(ref(getDatabase(), TEST_PATH), 'foo'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' must be an object"); + return Promise.resolve(); + } }); - const snapshot = await ref.once('value'); - snapshot.val().should.eql( - jet.contextify({ - foo: value, - }), - ); - await ref.update({}); // empty update should pass, but no side effects - const snapshot2 = await ref.once('value'); - snapshot2.val().should.eql( - jet.contextify({ + it('throws if update paths are not valid', async function () { + const { getDatabase, ref, update } = databaseModular; + + try { + await update(ref(getDatabase(), TEST_PATH), { + $$$$: 'foo', + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'values' contains an invalid path."); + return Promise.resolve(); + } + }); + + it('updates values', async function () { + const { getDatabase, ref, update, get } = databaseModular; + + const value = Date.now(); + const dbRef = ref(getDatabase(), TEST_PATH); + await update(dbRef, { foo: value, - }), - ); - }); + }); + const snapshot = await get(dbRef); + snapshot.val().should.eql( + jet.contextify({ + foo: value, + }), + ); - it('callback if function is passed', async function () { - const value = Date.now(); - return new Promise(async resolve => { - await firebase.database().ref(TEST_PATH).update( - { + await update(dbRef, {}); // empty update should pass, but no side effects + const snapshot2 = await get(dbRef); + snapshot2.val().should.eql( + jet.contextify({ foo: value, - }, - resolve, + }), ); }); }); diff --git a/packages/database/e2e/snapshot/snapshot.e2e.js b/packages/database/e2e/snapshot/snapshot.e2e.js index 853cb2eaa1..65d4bdcf20 100644 --- a/packages/database/e2e/snapshot/snapshot.e2e.js +++ b/packages/database/e2e/snapshot/snapshot.e2e.js @@ -27,228 +27,507 @@ describe('database()...snapshot', function () { await wipe(TEST_PATH); }); - it('returns the snapshot key', async function () { - const snapshot = await firebase.database().ref(TEST_PATH).child('types/boolean').once('value'); + describe('v8 compatibility', function () { + it('returns the snapshot key', async function () { + const snapshot = await firebase + .database() + .ref(TEST_PATH) + .child('types/boolean') + .once('value'); + + snapshot.key.should.equal('boolean'); + }); - snapshot.key.should.equal('boolean'); - }); + it('returns the snapshot reference', async function () { + const snapshot = await firebase + .database() + .ref(TEST_PATH) + .child('types/boolean') + .once('value'); - it('returns the snapshot reference', async function () { - const snapshot = await firebase.database().ref(TEST_PATH).child('types/boolean').once('value'); + snapshot.ref.key.should.equal('boolean'); + }); - snapshot.ref.key.should.equal('boolean'); - }); + it('returns the correct boolean for exists', async function () { + const snapshot1 = await firebase + .database() + .ref(TEST_PATH) + .child('types/boolean') + .once('value'); - it('returns the correct boolean for exists', async function () { - const snapshot1 = await firebase.database().ref(TEST_PATH).child('types/boolean').once('value'); + const snapshot2 = await firebase.database().ref(TEST_PATH).child('types/nope').once('value'); - const snapshot2 = await firebase.database().ref(TEST_PATH).child('types/nope').once('value'); + snapshot1.exists().should.equal(true); + snapshot2.exists().should.equal(false); + }); - snapshot1.exists().should.equal(true); - snapshot2.exists().should.equal(false); - }); + it('exports a valid object', async function () { + const snapshot = await firebase.database().ref(TEST_PATH).child('types/string').once('value'); - it('exports a valid object', async function () { - const snapshot = await firebase.database().ref(TEST_PATH).child('types/string').once('value'); + const exported = snapshot.exportVal(); - const exported = snapshot.exportVal(); + exported.should.have.property('.value'); + exported.should.have.property('.priority'); + exported['.value'].should.equal('foobar'); + should.equal(exported['.priority'], null); + }); - exported.should.have.property('.value'); - exported.should.have.property('.priority'); - exported['.value'].should.equal('foobar'); - should.equal(exported['.priority'], null); - }); + it('exports a valid object with a object value', async function () { + const snapshot = await firebase.database().ref(TEST_PATH).child('types/object').once('value'); - it('exports a valid object with a object value', async function () { - const snapshot = await firebase.database().ref(TEST_PATH).child('types/object').once('value'); + const exported = snapshot.exportVal(); - const exported = snapshot.exportVal(); + exported.should.have.property('.value'); + exported.should.have.property('.priority'); + exported['.value'].should.eql(jet.contextify(CONTENT.TYPES.object)); + should.equal(exported['.priority'], null); + }); - exported.should.have.property('.value'); - exported.should.have.property('.priority'); - exported['.value'].should.eql(jet.contextify(CONTENT.TYPES.object)); - should.equal(exported['.priority'], null); - }); + it('forEach throws if action param is not a function', async function () { + const ref = firebase.database().ref(TEST_PATH).child('unorderedList'); - it('forEach throws if action param is not a function', async function () { - const ref = firebase.database().ref(TEST_PATH).child('unorderedList'); + await ref.set({ + a: 3, + b: 1, + c: 2, + }); - await ref.set({ - a: 3, - b: 1, - c: 2, + const snapshot = await ref.orderByValue().once('value'); + + try { + snapshot.forEach('foo'); + } catch (error) { + error.message.should.containEql("'action' must be a function"); + } }); - const snapshot = await ref.orderByValue().once('value'); + it('forEach returns an ordered list of snapshots', async function () { + const ref = firebase.database().ref(TEST_PATH).child('unorderedList'); - try { - snapshot.forEach('foo'); - } catch (error) { - error.message.should.containEql("'action' must be a function"); - } - }); + await ref.set({ + a: 3, + b: 1, + c: 2, + }); - it('forEach returns an ordered list of snapshots', async function () { - const ref = firebase.database().ref(TEST_PATH).child('unorderedList'); + const snapshot = await ref.orderByValue().once('value'); + const expected = ['b', 'c', 'a']; - await ref.set({ - a: 3, - b: 1, - c: 2, + snapshot.forEach((childSnap, i) => { + childSnap.val().should.equal(i + 1); + childSnap.key.should.equal(expected[i]); + }); }); - const snapshot = await ref.orderByValue().once('value'); - const expected = ['b', 'c', 'a']; + it('forEach works with arrays', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH).child('types'); - snapshot.forEach((childSnap, i) => { - childSnap.val().should.equal(i + 1); - childSnap.key.should.equal(expected[i]); + const snapshot = await ref.once('value'); + + snapshot.child('array').forEach((childSnap, i) => { + callback(); + childSnap.val().should.equal(i); + childSnap.key.should.equal(i.toString()); + }); + + callback.should.be.callCount(snapshot.child('array').numChildren()); }); - }); - it('forEach works with arrays', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH).child('types'); + it('forEach works with objects and cancels when returning true', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH).child('types/object').orderByKey(); + + const snapshot = await ref.once('value'); - const snapshot = await ref.once('value'); + snapshot.forEach(childSnap => { + callback(); + childSnap.key.should.equal('bar'); + childSnap.val().should.equal('baz'); + return true; + }); - snapshot.child('array').forEach((childSnap, i) => { - callback(); - childSnap.val().should.equal(i); - childSnap.key.should.equal(i.toString()); + callback.should.be.calledOnce(); }); - callback.should.be.callCount(snapshot.child('array').numChildren()); - }); + it('forEach works with arrays and cancels when returning true', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH).child('types'); - it('forEach works with objects and cancels when returning true', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH).child('types/object').orderByKey(); + const snapshot = await ref.once('value'); - const snapshot = await ref.once('value'); + snapshot.child('array').forEach(childSnap => { + callback(); + childSnap.val().should.equal(0); + childSnap.key.should.equal('0'); + return true; + }); - snapshot.forEach(childSnap => { - callback(); - childSnap.key.should.equal('bar'); - childSnap.val().should.equal('baz'); - return true; + callback.should.be.calledOnce(); }); - callback.should.be.calledOnce(); - }); + it('forEach returns false when no child keys', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH).child('types/boolean'); - it('forEach works with arrays and cancels when returning true', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH).child('types'); + const snapshot = await ref.once('value'); - const snapshot = await ref.once('value'); + const bool = snapshot.forEach(() => { + callback(); + }); - snapshot.child('array').forEach(childSnap => { - callback(); - childSnap.val().should.equal(0); - childSnap.key.should.equal('0'); - return true; + bool.should.equal(false); + callback.should.be.callCount(0); }); - callback.should.be.calledOnce(); - }); + it('forEach cancels iteration when returning true', async function () { + const callback = sinon.spy(); + const ref = firebase.database().ref(TEST_PATH).child('types/array'); - it('forEach returns false when no child keys', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH).child('types/boolean'); + const snapshot = await ref.orderByValue().once('value'); - const snapshot = await ref.once('value'); + const cancelled = snapshot.forEach(childSnap => { + callback(childSnap.val()); + return true; + }); - const bool = snapshot.forEach(() => { - callback(); + cancelled.should.equal(true); + callback.should.be.callCount(1); + callback.getCall(0).args[0].should.equal(0); }); - bool.should.equal(false); - callback.should.be.callCount(0); - }); + it('getPriority returns the correct value', async function () { + const ref = firebase.database().ref(TEST_PATH).child('getPriority'); - it('forEach cancels iteration when returning true', async function () { - const callback = sinon.spy(); - const ref = firebase.database().ref(TEST_PATH).child('types/array'); + await ref.setWithPriority('foo', 'bar'); + const snapshot = await ref.once('value'); - const snapshot = await ref.orderByValue().once('value'); + snapshot.getPriority().should.equal('bar'); + }); + + it('hasChild throws if path is not a string value', async function () { + const ref = firebase.database().ref(TEST_PATH).child('types/boolean'); - const cancelled = snapshot.forEach(childSnap => { - callback(childSnap.val()); - return true; + const snapshot = await ref.once('value'); + + try { + snapshot.hasChild({ foo: 'bar' }); + } catch (error) { + error.message.should.containEql("'path' must be a string value"); + } }); - cancelled.should.equal(true); - callback.should.be.callCount(1); - callback.getCall(0).args[0].should.equal(0); - }); + it('hasChild returns the correct boolean value', async function () { + const ref = firebase.database().ref(TEST_PATH); - it('getPriority returns the correct value', async function () { - const ref = firebase.database().ref(TEST_PATH).child('getPriority'); + const snapshot1 = await ref.child('types/boolean').once('value'); + const snapshot2 = await ref.child('types').once('value'); - await ref.setWithPriority('foo', 'bar'); - const snapshot = await ref.once('value'); + snapshot1.hasChild('foo').should.equal(false); + snapshot2.hasChild('boolean').should.equal(true); + }); - snapshot.getPriority().should.equal('bar'); - }); + it('hasChildren returns the correct boolean value', async function () { + const ref = firebase.database().ref(TEST_PATH); + const snapshot = await ref.child('types/object').once('value'); + snapshot.hasChildren().should.equal(true); + }); - it('hasChild throws if path is not a string value', async function () { - const ref = firebase.database().ref(TEST_PATH).child('types/boolean'); + it('numChildren returns the correct number value', async function () { + const ref = firebase.database().ref(TEST_PATH); - const snapshot = await ref.once('value'); + const snapshot1 = await ref.child('types/boolean').once('value'); + const snapshot2 = await ref.child('types/array').once('value'); + const snapshot3 = await ref.child('types/object').once('value'); - try { - snapshot.hasChild({ foo: 'bar' }); - } catch (error) { - error.message.should.containEql("'path' must be a string value"); - } - }); + snapshot1.numChildren().should.equal(0); + snapshot2.numChildren().should.equal(CONTENT.TYPES.array.length); + snapshot3.numChildren().should.equal(Object.keys(CONTENT.TYPES.object).length); + }); - it('hasChild returns the correct boolean value', async function () { - const ref = firebase.database().ref(TEST_PATH); + it('toJSON returns the value of the snapshot', async function () { + const ref = firebase.database().ref(TEST_PATH); - const snapshot1 = await ref.child('types/boolean').once('value'); - const snapshot2 = await ref.child('types').once('value'); + const snapshot1 = await ref.child('types/string').once('value'); + const snapshot2 = await ref.child('types/object').once('value'); - snapshot1.hasChild('foo').should.equal(false); - snapshot2.hasChild('boolean').should.equal(true); - }); + snapshot1.toJSON().should.equal('foobar'); + snapshot2.toJSON().should.eql(jet.contextify(CONTENT.TYPES.object)); + }); + + it('val returns the value of the snapshot', async function () { + const ref = firebase.database().ref(TEST_PATH); - it('hasChildren returns the correct boolean value', async function () { - const ref = firebase.database().ref(TEST_PATH); - const snapshot = await ref.child('types/object').once('value'); - snapshot.hasChildren().should.equal(true); + const snapshot1 = await ref.child('types/string').once('value'); + const snapshot2 = await ref.child('types/object').once('value'); + + snapshot1.val().should.equal('foobar'); + snapshot2.val().should.eql(jet.contextify(CONTENT.TYPES.object)); + }); }); - it('numChildren returns the correct number value', async function () { - const ref = firebase.database().ref(TEST_PATH); + describe('modular', function () { + it('returns the snapshot key', async function () { + const { getDatabase, ref, child, get } = databaseModular; - const snapshot1 = await ref.child('types/boolean').once('value'); - const snapshot2 = await ref.child('types/array').once('value'); - const snapshot3 = await ref.child('types/object').once('value'); + const snapshot = await get(child(ref(getDatabase(), TEST_PATH), 'types/boolean')); - snapshot1.numChildren().should.equal(0); - snapshot2.numChildren().should.equal(CONTENT.TYPES.array.length); - snapshot3.numChildren().should.equal(Object.keys(CONTENT.TYPES.object).length); - }); + snapshot.key.should.equal('boolean'); + }); - it('toJSON returns the value of the snapshot', async function () { - const ref = firebase.database().ref(TEST_PATH); + it('returns the snapshot reference', async function () { + const { getDatabase, ref, child, get } = databaseModular; - const snapshot1 = await ref.child('types/string').once('value'); - const snapshot2 = await ref.child('types/object').once('value'); + const snapshot = await get(child(ref(getDatabase(), TEST_PATH), 'types/boolean')); - snapshot1.toJSON().should.equal('foobar'); - snapshot2.toJSON().should.eql(jet.contextify(CONTENT.TYPES.object)); - }); + snapshot.ref.key.should.equal('boolean'); + }); + + it('returns the correct boolean for exists', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const snapshot1 = await get(child(ref(getDatabase(), TEST_PATH), 'types/boolean')); + + const snapshot2 = await get(child(ref(getDatabase(), TEST_PATH), 'types/nope')); + + snapshot1.exists().should.equal(true); + snapshot2.exists().should.equal(false); + }); + + it('exports a valid object', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const snapshot = await get(child(ref(getDatabase(), TEST_PATH), 'types/string')); + + const exported = snapshot.exportVal(); + + exported.should.have.property('.value'); + exported.should.have.property('.priority'); + exported['.value'].should.equal('foobar'); + should.equal(exported['.priority'], null); + }); - it('val returns the value of the snapshot', async function () { - const ref = firebase.database().ref(TEST_PATH); + it('exports a valid object with a object value', async function () { + const { getDatabase, ref, child, get } = databaseModular; - const snapshot1 = await ref.child('types/string').once('value'); - const snapshot2 = await ref.child('types/object').once('value'); + const snapshot = await get(child(ref(getDatabase(), TEST_PATH), 'types/object')); + + const exported = snapshot.exportVal(); + + exported.should.have.property('.value'); + exported.should.have.property('.priority'); + exported['.value'].should.eql(jet.contextify(CONTENT.TYPES.object)); + should.equal(exported['.priority'], null); + }); + + it('forEach throws if action param is not a function', async function () { + const { getDatabase, ref, child, set, get, orderByValue, query } = databaseModular; + + const dbRef = child(ref(getDatabase(), TEST_PATH), 'unorderedList'); + + await set(dbRef, { + a: 3, + b: 1, + c: 2, + }); + + const snapshot = await get(query(dbRef, orderByValue())); + + try { + snapshot.forEach('foo'); + } catch (error) { + error.message.should.containEql("'action' must be a function"); + } + }); + + it('forEach returns an ordered list of snapshots', async function () { + const { getDatabase, ref, child, set, get, orderByValue, query } = databaseModular; + + const dbRef = child(ref(getDatabase(), TEST_PATH), 'unorderedList'); + + await set(dbRef, { + a: 3, + b: 1, + c: 2, + }); + + const snapshot = await get(query(dbRef, orderByValue())); + const expected = ['b', 'c', 'a']; + + snapshot.forEach((childSnap, i) => { + childSnap.val().should.equal(i + 1); + childSnap.key.should.equal(expected[i]); + }); + }); - snapshot1.val().should.equal('foobar'); - snapshot2.val().should.eql(jet.contextify(CONTENT.TYPES.object)); + it('forEach works with arrays', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const callback = sinon.spy(); + const dbRef = child(ref(getDatabase(), TEST_PATH), 'types'); + + const snapshot = await get(dbRef); + + snapshot.child('array').forEach((childSnap, i) => { + callback(); + childSnap.val().should.equal(i); + childSnap.key.should.equal(i.toString()); + }); + + callback.should.be.callCount(snapshot.child('array').numChildren()); + }); + + it('forEach works with objects and cancels when returning true', async function () { + const { getDatabase, ref, child, orderByKey, query, get } = databaseModular; + + const callback = sinon.spy(); + const dbRef = query(child(ref(getDatabase(), TEST_PATH), 'types/object'), orderByKey()); + + const snapshot = await get(dbRef); + + snapshot.forEach(childSnap => { + callback(); + childSnap.key.should.equal('bar'); + childSnap.val().should.equal('baz'); + return true; + }); + + callback.should.be.calledOnce(); + }); + + it('forEach works with arrays and cancels when returning true', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const callback = sinon.spy(); + const dbRef = child(ref(getDatabase(), TEST_PATH), 'types'); + + const snapshot = await get(dbRef); + + snapshot.child('array').forEach(childSnap => { + callback(); + childSnap.val().should.equal(0); + childSnap.key.should.equal('0'); + return true; + }); + + callback.should.be.calledOnce(); + }); + + it('forEach returns false when no child keys', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const callback = sinon.spy(); + const dbRef = child(ref(getDatabase(), TEST_PATH), 'types/boolean'); + + const snapshot = await get(dbRef); + + const bool = snapshot.forEach(() => { + callback(); + }); + + bool.should.equal(false); + callback.should.be.callCount(0); + }); + + it('forEach cancels iteration when returning true', async function () { + const { getDatabase, ref, child, orderByValue, query, get } = databaseModular; + + const callback = sinon.spy(); + const dbRef = child(ref(getDatabase(), TEST_PATH), 'types/array'); + + const snapshot = await get(query(dbRef, orderByValue())); + + const cancelled = snapshot.forEach(childSnap => { + callback(childSnap.val()); + return true; + }); + + cancelled.should.equal(true); + callback.should.be.callCount(1); + callback.getCall(0).args[0].should.equal(0); + }); + + it('getPriority returns the correct value', async function () { + const { getDatabase, ref, child, setWithPriority, get } = databaseModular; + + const dbRef = child(ref(getDatabase(), TEST_PATH), 'getPriority'); + + await setWithPriority(dbRef, 'foo', 'bar'); + const snapshot = await get(dbRef); + + snapshot.getPriority().should.equal('bar'); + }); + + it('hasChild throws if path is not a string value', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const dbRef = child(ref(getDatabase(), TEST_PATH), 'types/boolean'); + + const snapshot = await get(dbRef); + + try { + snapshot.hasChild({ foo: 'bar' }); + } catch (error) { + error.message.should.containEql("'path' must be a string value"); + } + }); + + it('hasChild returns the correct boolean value', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + const snapshot1 = await get(child(dbRef, 'types/boolean')); + const snapshot2 = await get(child(dbRef, 'types')); + + snapshot1.hasChild('foo').should.equal(false); + snapshot2.hasChild('boolean').should.equal(true); + }); + + it('hasChildren returns the correct boolean value', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + const snapshot = await get(child(dbRef, 'types/object')); + snapshot.hasChildren().should.equal(true); + }); + + it('numChildren returns the correct number value', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + const snapshot1 = await get(child(dbRef, 'types/boolean')); + const snapshot2 = await get(child(dbRef, 'types/array')); + const snapshot3 = await get(child(dbRef, 'types/object')); + + snapshot1.numChildren().should.equal(0); + snapshot2.numChildren().should.equal(CONTENT.TYPES.array.length); + snapshot3.numChildren().should.equal(Object.keys(CONTENT.TYPES.object).length); + }); + + it('toJSON returns the value of the snapshot', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + const snapshot1 = await get(child(dbRef, 'types/string')); + const snapshot2 = await get(child(dbRef, 'types/object')); + + snapshot1.toJSON().should.equal('foobar'); + snapshot2.toJSON().should.eql(jet.contextify(CONTENT.TYPES.object)); + }); + + it('val returns the value of the snapshot', async function () { + const { getDatabase, ref, child, get } = databaseModular; + + const dbRef = ref(getDatabase(), TEST_PATH); + + const snapshot1 = await get(child(dbRef, 'types/string')); + const snapshot2 = await get(child(dbRef, 'types/object')); + + snapshot1.val().should.equal('foobar'); + snapshot2.val().should.eql(jet.contextify(CONTENT.TYPES.object)); + }); }); }); diff --git a/packages/database/lib/index.d.ts b/packages/database/lib/index.d.ts index 74fdff8d1e..5b2a421fcc 100644 --- a/packages/database/lib/index.d.ts +++ b/packages/database/lib/index.d.ts @@ -17,6 +17,8 @@ import { ReactNativeFirebase } from '@react-native-firebase/app'; +export type FirebaseApp = ReactNativeFirebase.FirebaseApp; + /** * Firebase Database package for React Native. * diff --git a/packages/database/lib/index.js b/packages/database/lib/index.js index bc5aab86db..fd21e16f55 100644 --- a/packages/database/lib/index.js +++ b/packages/database/lib/index.js @@ -216,6 +216,8 @@ export default createModuleNamespace({ ModuleClass: FirebaseDatabaseModule, }); +export * from './modular'; + // import database, { firebase } from '@react-native-firebase/database'; // database().X(...); // firebase.database().X(...); diff --git a/packages/database/lib/modular/index.d.ts b/packages/database/lib/modular/index.d.ts new file mode 100644 index 0000000000..6ab01e2bfc --- /dev/null +++ b/packages/database/lib/modular/index.d.ts @@ -0,0 +1,217 @@ +import { ReactNativeFirebase } from '@react-native-firebase/app'; +import { FirebaseDatabaseTypes } from '..'; + +import FirebaseApp = ReactNativeFirebase.FirebaseApp; +import Database = FirebaseDatabaseTypes.Module; +import DatabaseReference = FirebaseDatabaseTypes.Reference; + +/** + * Returns the instance of the Realtime Database SDK that is associated with + * the provided FirebaseApp. Initializes a new instance with + * default settings if no instance exists or if the existing + * instance uses a custom database URL. + * + * @param {FirebaseApp?} app - The FirebaseApp instance that the returned Realtime Database instance is associated with. + * @param {string?} url + * @returns {*} + */ +export declare function getDatabase(app?: FirebaseApp, url?: string): Database; + +/** + * Modify this Database instance to communicate with the Firebase Database emulator. + * This must be called synchronously immediately following the first call to firebase.database(). + * Do not use with production credentials as emulator traffic is not encrypted. + * + * Note: on android, hosts 'localhost' and '127.0.0.1' are automatically remapped to '10.0.2.2' (the + * "host" computer IP address for android emulators) to make the standard development experience easy. + * If you want to use the emulator on a real android device, you will need to specify the actual host + * computer IP address. + * + * @param db: The Database instance + * @param host: emulator host (eg, 'localhost') + * @param port: emulator port (eg, 9000) + */ +export declare function connectDatabaseEmulator( + db: Database, + host: string, + port: number, + // TODO: this exists in both the JS namespaced and modular versions of the SDK. + // But the RNFB namespaced version doesn't have it. + // options?: { + // mockUserToken?: EmulatorMockTokenOptions | string; + // }, +): void; + +/** + * Disconnects from the server (all Database operations will be completed offline). + * + * The client automatically maintains a persistent connection to the Database server, which + * will remain active indefinitely and reconnect when disconnected. However, the `goOffline()` and + * `goOnline()` methods may be used to control the client connection in cases where a persistent + * connection is undesirable. + * + * While offline, the client will no longer receive data updates from the Database. However, + * all Database operations performed locally will continue to immediately fire events, allowing + * your application to continue behaving normally. Additionally, each operation performed locally + * will automatically be queued and retried upon reconnection to the Database server. + * + * To reconnect to the Database and begin receiving remote events, see `goOnline()`. + * + * #### Example + * + * ```js + * const db = getDatabase();; + * await goOnline(db); + * ``` + */ +export declare function goOffline(db: Database): Promise; + +/** + * Reconnects to the server and synchronizes the offline Database state with the server state. + * + * This method should be used after disabling the active connection with `goOffline()`. Once + * reconnected, the client will transmit the proper data and fire the appropriate events so that + * your client "catches up" automatically. + * + * #### Example + * + * ```js + * const db = getDatabase(); + * await goOnline(db); + * ``` + */ +export declare function goOnline(db: Database): Promise; + +/** + * Returns a `Reference` representing the location in the Database corresponding to the provided path. + * If no path is provided, the Reference will point to the root of the Database. + * + * #### Example + * + * ```js + * const db = getDatabase(); + * + * // Get a reference to the root of the Database + * const rootRef = ref(db); + * + * // Get a reference to the /users/ada node + * const adaRef = ref(db, "users/ada"); + * ``` + * + * @param db The Database instance. + * @param path Optional path representing the location the returned `Reference` will point. If not provided, the returned `Reference` will point to the root of the Database. + */ +export declare function ref(db: Database, path?: string): DatabaseReference; + +/** + * Generates a Reference from a database URL. + * Note domain must be the same. + * Any query parameters are stripped as per the web SDK. + * + * @param db The Database instance. + * @param url The Firebase URL at which the returned Reference will point. + * @returns {DatabaseReference} + */ +export declare function refFromURL(db: Database, url: string): DatabaseReference; + +/** + * Sets whether persistence is enabled for all database calls for the current app + * instance. + * + * > Ensure this is called before any database calls are performed, otherwise + * persistence will only come into effect when the app is next started. + * + * #### Example + * + * ```js + * const db = getDatabase(); + * setPersistenceEnabled(db, true); + * + * async function bootstrap() { + * // Bootstrapping application + * const snapshot = await ref(db, "settings").once("value"); + * } + * ``` + * + * @param db The Database instance. + * @param enabled Whether persistence is enabled for the Database service. + */ +export declare function setPersistenceEnabled(db: Database, enabled: boolean): void; + +/** + * Sets the native logging level for the database module. By default, + * only warnings and errors are logged natively. Setting this to true will log all + * database events. + * + * > Ensure logging is disabled for production apps, as excessive logging can cause performance issues. + * + * #### Example + * + * ```js + * const db = getDatabase(); + * + * // Set debug logging if developing + * if (__DEV__) { + * setLoggingEnabled(db, true); + * } + * ``` + * + * @param db The Database instance. + * @param enabled Whether debug logging is enabled. + * @returns {void} + */ +export declare function setLoggingEnabled(db: Database, enabled: boolean): void; + +/** + * By default, Firebase Database will use up to 10MB of disk space to cache data. If the cache grows beyond this size, + * Firebase Database will start removing data that hasn't been recently used. If you find that your application + * caches too little or too much data, call this method to change the cache size. This method must be called before + * creating your first Database reference and only needs to be called once per application. + * + * Note that the specified cache size is only an approximation and the size on disk may temporarily exceed it at times. + * Cache sizes smaller than 1 MB or greater than 100 MB are not supported. + * + * #### Example + * + * ```js + * const db = getDatabase(); + * + * setPersistenceEnabled(db, true); + * setPersistenceCacheSizeBytes(db, 2000000); // 2MB + * + * async function bootstrap() { + * // Bootstrapping application + * const snapshot = await ref(db, 'settings').once("value"); + * } + * ``` + * + * @param db The Database instance. + * @param bytes The new size of the cache in bytes. + */ +export declare function setPersistenceCacheSizeBytes(db: Database, bytes: number): void; + +/** + * Force the use of longPolling instead of websockets. This will be ignored if websocket protocol is used in databaseURL. + */ +export declare function forceLongPolling(): void; + +/** + * Force the use of websockets instead of longPolling. + */ +export declare function forceWebSockets(): void; + +/** + * Returns a placeholder value for auto-populating the current timestamp (time + * since the Unix epoch, in milliseconds) as determined by the Firebase + * servers. + */ +export function serverTimestamp(): object; + +/** + * Returns a placeholder value that can be used to atomically increment the + * current database value by the provided delta. + * + * @param delta - the amount to modify the current value atomically. + * @returns A placeholder value for modifying data atomically server-side. + */ +export function increment(delta: number): object; diff --git a/packages/database/lib/modular/index.js b/packages/database/lib/modular/index.js new file mode 100644 index 0000000000..f864fd057a --- /dev/null +++ b/packages/database/lib/modular/index.js @@ -0,0 +1,124 @@ +import { firebase } from '..'; + +/** + * @typedef {import("..").FirebaseApp} FirebaseApp + * @typedef {import("..").FirebaseDatabaseTypes.Module} Database + */ + +/** + * @param {FirebaseApp?} app - The FirebaseApp instance that the returned Realtime Database instance is associated with. + * @param {string?} url + * @returns {Database} + */ +export function getDatabase(app, url) { + if (app) { + return firebase.app(app.name).database(url); + } + + return firebase.app().database(url); +} + +/** + * @param {Database} db + * @param {string} host + * @param {number} port + * @returns {void} + */ +export function connectDatabaseEmulator(db, host, port) { + db.useEmulator(host, port); +} + +/** + * @param {Database} db + * @returns {Promise} + */ +export function goOffline(db) { + return db.goOffline(); +} + +/** + * @param {Database} db + * @returns {Promise} + */ +export function goOnline(db) { + return db.goOnline(); +} + +/** + * @param {Database} db + * @param {string?} path + * @returns {DatabaseReference} + */ +export function ref(db, path) { + return db.ref(path); +} + +/** + * @param {Database} db + * @param {string} url + * @returns {DatabaseReference} + */ +export function refFromURL(db, url) { + return db.refFromURL(url); +} + +/** + * @param {Database} db + * @param {boolean} enabled + * @returns {void} + */ +export function setPersistenceEnabled(db, enabled) { + return db.setPersistenceEnabled(enabled); +} + +/** + * @param {Database} db + * @param {boolean} enabled + * @returns {void} + */ +export function setLoggingEnabled(db, enabled) { + return db.setLoggingEnabled(enabled); +} + +/** + * @param {Database} db + * @param {number} bytes + * @returns {void} + */ +export function setPersistenceCacheSizeBytes(db, bytes) { + return db.setPersistenceCacheSizeBytes(bytes); +} + +export function forceLongPolling() { + throw new Error('forceLongPolling() is not implemented'); +} + +export function forceWebSockets() { + throw new Error('forceWebSockets() is not implemented'); +} + +/** + * @param {Database} db + * @returns {Date} + */ +export function getServerTime(db) { + return db.getServerTime(); +} + +/** + * @returns {object} + */ +export function serverTimestamp() { + return firebase.database.ServerValue.TIMESTAMP; +} + +/** + * @param {number} delta + * @returns {object} + */ +export function increment(delta) { + return firebase.database.ServerValue.increment(delta); +} + +export * from './query'; +export * from './transaction'; diff --git a/packages/database/lib/modular/query.d.ts b/packages/database/lib/modular/query.d.ts new file mode 100644 index 0000000000..05fb472470 --- /dev/null +++ b/packages/database/lib/modular/query.d.ts @@ -0,0 +1,1025 @@ +import { FirebaseDatabaseTypes } from '../..'; + +import Query = FirebaseDatabaseTypes.Query; +import DataSnapshot = FirebaseDatabaseTypes.DataSnapshot; +import DatabaseReference = FirebaseDatabaseTypes.Reference; +import OnDisconnect = FirebaseDatabaseTypes.OnDisconnect; + +export type Query = Query; +export type DataSnapshot = DataSnapshot; +export type DatabaseReference = DatabaseReference; +export type OnDisconnect = OnDisconnect; + +/** + * A `Promise` that can also act as a `DatabaseReference` when returned by + * {@link push}. The reference is available immediately and the `Promise` resolves + * as the write to the backend completes. + */ +export interface ThenableReference + extends DatabaseReference, + Pick, 'then' | 'catch'> {} + +export type Unsubscribe = () => void; + +export interface ListenOptions { + readonly onlyOnce?: boolean; +} + +/** Describes the different query constraints available in this SDK. */ +export type QueryConstraintType = + | 'endAt' + | 'endBefore' + | 'startAt' + | 'startAfter' + | 'limitToFirst' + | 'limitToLast' + | 'orderByChild' + | 'orderByKey' + | 'orderByPriority' + | 'orderByValue' + | 'equalTo'; + +/** + * A `QueryConstraint` is used to narrow the set of documents returned by a + * Database query. `QueryConstraint`s are created by invoking {@link endAt}, + * {@link endBefore}, {@link startAt}, {@link startAfter}, {@link + * limitToFirst}, {@link limitToLast}, {@link orderByChild}, + * {@link orderByChild}, {@link orderByKey} , {@link orderByPriority} , + * {@link orderByValue} or {@link equalTo} and + * can then be passed to {@link query} to create a new query instance that + * also contains this `QueryConstraint`. + */ +export interface QueryConstraint { + /** The type of this query constraints */ + readonly _type: QueryConstraintType; + + _apply(query: Query): Query; +} + +/** + * Creates a `QueryConstraint` with the specified ending point. + * + * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` + * allows you to choose arbitrary starting and ending points for your queries. + * + * The ending point is inclusive, so children with exactly the specified value + * will be included in the query. The optional key argument can be used to + * further limit the range of the query. If it is specified, then children that + * have exactly the specified value must also have a key name less than or equal + * to the specified key. + * + * @param value - The value to end at. The argument type depends on which + * `orderBy*()` function was used in this query. Specify a value that matches + * the `orderBy*()` type. When used in combination with `orderByKey()`, the + * value must be a string. + * @param key - The child key to end at, among the children with the previously + * specified priority. This argument is only allowed if ordering by child, + * value, or priority. + */ +export declare function endAt( + value: number | string | boolean | null, + key?: string, +): QueryConstraint; + +/** + * Creates a QueryConstraint with the specified ending point (exclusive). + * + * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` allows you to + * choose arbitrary starting and ending points for your queries. + * + * The ending point is exclusive. If only a value is provided, children with a + * value less than the specified value will be included in the query. If a key + * is specified, then children must have a value less than or equal to the + * specified value and a key name less than the specified key. + * + * @param value - The value to end before. The argument type depends on which + * `orderBy*()` function was used in this query. Specify a value that matches + * the `orderBy*()` type. When used in combination with `orderByKey()`, the + * value must be a string. + * @param key - The child key to end before, among the children with the + * previously specified priority. This argument is only allowed if ordering by + * child, value, or priority. + */ +export declare function endBefore( + value: number | string | boolean | null, + key?: string, +): QueryConstraint; + +/** + * Creates a QueryConstraint that includes children that match the specified value. + * + * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` allows + * you to choose arbitrary starting and ending points for your queries. + * + * The optional key argument can be used to further limit the range of the + * query. If it is specified, then children that have exactly the specified + * value must also have exactly the specified key as their key name. This + * can be used to filter result sets with many matches for the same value. + * + * @param value - The value to match for. The argument type depends on which + * `orderBy*()` function was used in this query. Specify a value that matches + * the `orderBy*()` type. When used in combination with `orderByKey()`, the + * value must be a string. + * @param key - The child key to start at, among the children with the + * previously specified priority. This argument is only allowed if ordering by + * child, value, or priority. + */ +export declare function equalTo( + value: number | string | boolean | null, + key?: string, +): QueryConstraint; + +/** + * Creates a `QueryConstraint` that includes children that match the specified + * value. + * + * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` + * allows you to choose arbitrary starting and ending points for your queries. + * + * The optional key argument can be used to further limit the range of the + * query. If it is specified, then children that have exactly the specified + * value must also have exactly the specified key as their key name. This can be + * used to filter result sets with many matches for the same value. + * + * @param value - The value to match for. The argument type depends on which + * `orderBy*()` function was used in this query. Specify a value that matches + * the `orderBy*()` type. When used in combination with `orderByKey()`, the + * value must be a string. + * @param key - The child key to start at, among the children with the + * previously specified priority. This argument is only allowed if ordering by + * child, value, or priority. + */ +export function equalTo(value: number | string | boolean | null, key?: string): QueryConstraint; + +/** + * Creates a QueryConstraint with the specified starting point. + * + * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` + * allows you to choose arbitrary starting and ending points for your queries. + * + * The starting point is inclusive, so children with exactly the specified + * value will be included in the query. The optional key argument can be used + * to further limit the range of the query. If it is specified, then children + * that have exactly the specified value must also have a key name greater than + * or equal to the specified key. + * + * @param value - The value to start at. The argument type depends on which + * `orderBy*()` function was used in this query. Specify a value that matches + * the `orderBy*()` type. When used in combination with `orderByKey()`, the + * value must be a string. + * @param key - The child key to start at. This argument is only allowed if + * ordering by child, value, or priority. + */ +export declare function startAt( + value?: number | string | boolean | null, + key?: string, +): QueryConstraint; + +/** + * Creates a `QueryConstraint` with the specified starting point. + * + * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` + * allows you to choose arbitrary starting and ending points for your queries. + * + * The starting point is inclusive, so children with exactly the specified value + * will be included in the query. The optional key argument can be used to + * further limit the range of the query. If it is specified, then children that + * have exactly the specified value must also have a key name greater than or + * equal to the specified key. + * + * @param value - The value to start at. The argument type depends on which + * `orderBy*()` function was used in this query. Specify a value that matches + * the `orderBy*()` type. When used in combination with `orderByKey()`, the + * value must be a string. + * @param key - The child key to start at. This argument is only allowed if + * ordering by child, value, or priority. + */ +export function startAt( + value: number | string | boolean | null = null, + key?: string, +): QueryConstraint; + +/** + * Creates a `QueryConstraint` with the specified starting point (exclusive). + * + * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` + * allows you to choose arbitrary starting and ending points for your queries. + * + * The starting point is exclusive. If only a value is provided, children + * with a value greater than the specified value will be included in the query. + * If a key is specified, then children must have a value greater than or equal + * to the specified value and a a key name greater than the specified key. + * + * @param value - The value to start after. The argument type depends on which + * `orderBy*()` function was used in this query. Specify a value that matches + * the `orderBy*()` type. When used in combination with `orderByKey()`, the + * value must be a string. + * @param key - The child key to start after. This argument is only allowed if + * ordering by child, value, or priority. + */ +export function startAfter(value: number | string | boolean | null, key?: string): QueryConstraint; + +/** + * Creates a `QueryConstraint` with the specified starting point (exclusive). + * + * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` + * allows you to choose arbitrary starting and ending points for your queries. + * + * The starting point is exclusive. If only a value is provided, children + * with a value greater than the specified value will be included in the query. + * If a key is specified, then children must have a value greater than or equal + * to the specified value and a key name greater than the specified key. + * + * @param value - The value to start after. The argument type depends on which + * `orderBy*()` function was used in this query. Specify a value that matches + * the `orderBy*()` type. When used in combination with `orderByKey()`, the + * value must be a string. + * @param key - The child key to start after. This argument is only allowed if + * ordering by child, value, or priority. + */ +export function startAfter(value: number | string | boolean | null, key?: string): QueryConstraint; + +/** + * Creates a new `QueryConstraint` that if limited to the first specific number + * of children. + * + * The `limitToFirst()` method is used to set a maximum number of children to be + * synced for a given callback. If we set a limit of 100, we will initially only + * receive up to 100 `child_added` events. If we have fewer than 100 messages + * stored in our Database, a `child_added` event will fire for each message. + * However, if we have over 100 messages, we will only receive a `child_added` + * event for the first 100 ordered messages. As items change, we will receive + * `child_removed` events for each item that drops out of the active list so + * that the total number stays at 100. + * + * @param limit - The maximum number of nodes to include in this query. + */ +export function limitToFirst(limit: number): QueryConstraint; + +/** + * Creates a new `QueryConstraint` that is limited to return only the last + * specified number of children. + * + * The `limitToLast()` method is used to set a maximum number of children to be + * synced for a given callback. If we set a limit of 100, we will initially only + * receive up to 100 `child_added` events. If we have fewer than 100 messages + * stored in our Database, a `child_added` event will fire for each message. + * However, if we have over 100 messages, we will only receive a `child_added` + * event for the last 100 ordered messages. As items change, we will receive + * `child_removed` events for each item that drops out of the active list so + * that the total number stays at 100. + * + * @param limit - The maximum number of nodes to include in this query. + */ +export function limitToLast(limit: number): QueryConstraint; + +/** + * Creates a new `QueryConstraint` that orders by the specified child key. + * + * Queries can only order by one key at a time. Calling `orderByChild()` + * multiple times on the same query is an error. + * + * Firebase queries allow you to order your data by any child key on the fly. + * However, if you know in advance what your indexes will be, you can define + * them via the .indexOn rule in your Security Rules for better performance. + * + * @param path - The path to order by. + */ +export function orderByChild(path: string): QueryConstraint; + +/** + * Creates a new `QueryConstraint` that orders by the key. + * + * Sorts the results of a query by their (ascending) key values. + */ +export function orderByKey(): QueryConstraint; + +/** + * Creates a new `QueryConstraint` that orders by priority. + * + * Applications need not use priority but can order collections by + * ordinary properties + */ +export function orderByPriority(): QueryConstraint; + +/** + * Creates a new `QueryConstraint` that orders by value. + * + * If the children of a query are all scalar values (string, number, or + * boolean), you can order the results by their (ascending) values. + */ +export function orderByValue(): QueryConstraint; + +/** + * Creates a new immutable instance of `Query` that is extended to also include + * additional query constraints. + * + * @param query - The Query instance to use as a base for the new constraints. + * @param queryConstraints - The list of `QueryConstraint`s to apply. + * @throws if any of the provided query constraints cannot be combined with the + * existing or new constraints. + */ +export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onValue` event will trigger once with the initial data stored at this + * location, and then trigger again each time the data changes. The + * `DataSnapshot` passed to the callback will be for the location at which + * `on()` was called. It won't trigger until the entire contents has been + * synchronized. If the location has no data, it will be triggered with an empty + * `DataSnapshot` (`val()` will return `null`). + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. The + * callback will be passed a DataSnapshot. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @returns A function that can be invoked to remove the listener. + */ +export function onValue( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + cancelCallback?: (error: Error) => unknown, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onValue` event will trigger once with the initial data stored at this + * location, and then trigger again each time the data changes. The + * `DataSnapshot` passed to the callback will be for the location at which + * `on()` was called. It won't trigger until the entire contents has been + * synchronized. If the location has no data, it will be triggered with an empty + * `DataSnapshot` (`val()` will return `null`). + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. The + * callback will be passed a DataSnapshot. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onValue( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + options: ListenOptions, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onValue` event will trigger once with the initial data stored at this + * location, and then trigger again each time the data changes. The + * `DataSnapshot` passed to the callback will be for the location at which + * `on()` was called. It won't trigger until the entire contents has been + * synchronized. If the location has no data, it will be triggered with an empty + * `DataSnapshot` (`val()` will return `null`). + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. The + * callback will be passed a DataSnapshot. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onValue( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + cancelCallback: (error: Error) => unknown, + options: ListenOptions, +): Unsubscribe; + +export function onValue( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onChildAdded` event will be triggered once for each initial child at this + * location, and it will be triggered again every time a new child is added. The + * `DataSnapshot` passed into the callback will reflect the data for the + * relevant child. For ordering purposes, it is passed a second argument which + * is a string containing the key of the previous sibling child by sort order, + * or `null` if it is the first child. + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildAdded( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown, + cancelCallback?: (error: Error) => unknown, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onChildAdded` event will be triggered once for each initial child at this + * location, and it will be triggered again every time a new child is added. The + * `DataSnapshot` passed into the callback will reflect the data for the + * relevant child. For ordering purposes, it is passed a second argument which + * is a string containing the key of the previous sibling child by sort order, + * or `null` if it is the first child. + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildAdded( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + options: ListenOptions, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onChildAdded` event will be triggered once for each initial child at this + * location, and it will be triggered again every time a new child is added. The + * `DataSnapshot` passed into the callback will reflect the data for the + * relevant child. For ordering purposes, it is passed a second argument which + * is a string containing the key of the previous sibling child by sort order, + * or `null` if it is the first child. + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildAdded( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + cancelCallback: (error: Error) => unknown, + options: ListenOptions, +): Unsubscribe; + +export function onChildAdded( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onChildChanged` event will be triggered when the data stored in a child + * (or any of its descendants) changes. Note that a single `child_changed` event + * may represent multiple changes to the child. The `DataSnapshot` passed to the + * callback will contain the new child contents. For ordering purposes, the + * callback is also passed a second argument which is a string containing the + * key of the previous sibling child by sort order, or `null` if it is the first + * child. + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildChanged( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + cancelCallback?: (error: Error) => unknown, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onChildChanged` event will be triggered when the data stored in a child + * (or any of its descendants) changes. Note that a single `child_changed` event + * may represent multiple changes to the child. The `DataSnapshot` passed to the + * callback will contain the new child contents. For ordering purposes, the + * callback is also passed a second argument which is a string containing the + * key of the previous sibling child by sort order, or `null` if it is the first + * child. + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildChanged( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + options: ListenOptions, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onChildChanged` event will be triggered when the data stored in a child + * (or any of its descendants) changes. Note that a single `child_changed` event + * may represent multiple changes to the child. The `DataSnapshot` passed to the + * callback will contain the new child contents. For ordering purposes, the + * callback is also passed a second argument which is a string containing the + * key of the previous sibling child by sort order, or `null` if it is the first + * child. + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildChanged( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + cancelCallback: (error: Error) => unknown, + options: ListenOptions, +): Unsubscribe; + +export function onChildChanged( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onChildMoved` event will be triggered when a child's sort order changes + * such that its position relative to its siblings changes. The `DataSnapshot` + * passed to the callback will be for the data of the child that has moved. It + * is also passed a second argument which is a string containing the key of the + * previous sibling child by sort order, or `null` if it is the first child. + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildMoved( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + cancelCallback?: (error: Error) => unknown, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onChildMoved` event will be triggered when a child's sort order changes + * such that its position relative to its siblings changes. The `DataSnapshot` + * passed to the callback will be for the data of the child that has moved. It + * is also passed a second argument which is a string containing the key of the + * previous sibling child by sort order, or `null` if it is the first child. + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildMoved( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + options: ListenOptions, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. + * + * An `onChildMoved` event will be triggered when a child's sort order changes + * such that its position relative to its siblings changes. The `DataSnapshot` + * passed to the callback will be for the data of the child that has moved. It + * is also passed a second argument which is a string containing the key of the + * previous sibling child by sort order, or `null` if it is the first child. + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildMoved( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + cancelCallback: (error: Error) => unknown, + options: ListenOptions, +): Unsubscribe; + +export function onChildMoved( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. See + * {@link https://firebase.google.com/docs/database/web/retrieve-data | Retrieve Data on the Web} + * for more details. + * + * An `onChildRemoved` event will be triggered once every time a child is + * removed. The `DataSnapshot` passed into the callback will be the old data for + * the child that was removed. A child will get removed when either: + * + * - a client explicitly calls `remove()` on that child or one of its ancestors + * - a client calls `set(null)` on that child or one of its ancestors + * - that child has all of its children removed + * - there is a query in effect which now filters out the child (because it's + * sort order changed or the max limit was hit) + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildRemoved( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + cancelCallback?: (error: Error) => unknown, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. See + * {@link https://firebase.google.com/docs/database/web/retrieve-data | Retrieve Data on the Web} + * for more details. + * + * An `onChildRemoved` event will be triggered once every time a child is + * removed. The `DataSnapshot` passed into the callback will be the old data for + * the child that was removed. A child will get removed when either: + * + * - a client explicitly calls `remove()` on that child or one of its ancestors + * - a client calls `set(null)` on that child or one of its ancestors + * - that child has all of its children removed + * - there is a query in effect which now filters out the child (because it's + * sort order changed or the max limit was hit) + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildRemoved( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + options: ListenOptions, +): Unsubscribe; + +/** + * Listens for data changes at a particular location. + * + * This is the primary way to read data from a Database. Your callback + * will be triggered for the initial data and again whenever the data changes. + * Invoke the returned unsubscribe callback to stop receiving updates. See + * {@link https://firebase.google.com/docs/database/web/retrieve-data | Retrieve Data on the Web} + * for more details. + * + * An `onChildRemoved` event will be triggered once every time a child is + * removed. The `DataSnapshot` passed into the callback will be the old data for + * the child that was removed. A child will get removed when either: + * + * - a client explicitly calls `remove()` on that child or one of its ancestors + * - a client calls `set(null)` on that child or one of its ancestors + * - that child has all of its children removed + * - there is a query in effect which now filters out the child (because it's + * sort order changed or the max limit was hit) + * + * @param query - The query to run. + * @param callback - A callback that fires when the specified event occurs. + * The callback will be passed a DataSnapshot and a string containing the key of + * the previous child, by sort order, or `null` if it is the first child. + * @param cancelCallback - An optional callback that will be notified if your + * event subscription is ever canceled because your client does not have + * permission to read this data (or it had permission but has now lost it). + * This callback will be passed an `Error` object indicating why the failure + * occurred. + * @param options - An object that can be used to configure `onlyOnce`, which + * then removes the listener after its first invocation. + * @returns A function that can be invoked to remove the listener. + */ +export function onChildRemoved( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + cancelCallback: (error: Error) => unknown, + options: ListenOptions, +): Unsubscribe; + +export function onChildRemoved( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe; + +/** + * Writes data to this Database location. + * + * This will overwrite any data at this location and all child locations. + * + * The effect of the write will be visible immediately, and the corresponding + * events ("value", "child_added", etc.) will be triggered. Synchronization of + * the data to the Firebase servers will also be started, and the returned + * Promise will resolve when complete. If provided, the `onComplete` callback + * will be called asynchronously after synchronization has finished. + * + * Passing `null` for the new value is equivalent to calling `remove()`; namely, + * all data at this location and all child locations will be deleted. + * + * `set()` will remove any priority stored at this location, so if priority is + * meant to be preserved, you need to use `setWithPriority()` instead. + * + * Note that modifying data with `set()` will cancel any pending transactions + * at that location, so extreme care should be taken if mixing `set()` and + * `transaction()` to modify the same data. + * + * A single `set()` will generate a single "value" event at the location where + * the `set()` was performed. + * + * @param ref - The location to write to. + * @param value - The value to be written (string, number, boolean, object, + * array, or null). + * @returns Resolves when write to server is complete. + */ +export function set(ref: DatabaseReference, value: unknown): Promise; + +/** + * Sets a priority for the data at this Database location. + * + * Applications need not use priority but can order collections by + * ordinary properties + * + * @param ref - The location to write to. + * @param priority - The priority to be written (string, number, or null). + * @returns Resolves when write to server is complete. + */ +export function setPriority( + ref: DatabaseReference, + priority: string | number | null, +): Promise; + +/** + * Writes data the Database location. Like `set()` but also specifies the + * priority for that data. + * + * Applications need not use priority but can order collections by + * ordinary properties + * + * @param ref - The location to write to. + * @param value - The value to be written (string, number, boolean, object, + * array, or null). + * @param priority - The priority to be written (string, number, or null). + * @returns Resolves when write to server is complete. + */ +export function setWithPriority( + ref: DatabaseReference, + value: unknown, + priority: string | number | null, +): Promise; + +/** + * Gets the most up-to-date result for this query. + * + * @param query - The query to run. + * @returns A `Promise` which resolves to the resulting DataSnapshot if a value is + * available, or rejects if the client is unable to return a value (e.g., if the + * server is unreachable and there is nothing cached). + */ +export function get(query: Query): Promise; + +/** + * Gets a `Reference` for the location at the specified relative path. + * + * The relative path can either be a simple child name (for example, "ada") or + * a deeper slash-separated path (for example, "ada/name/first"). + * + * @param parent - The parent location. + * @param path - A relative path from this location to the desired child + * location. + * @returns The specified child location. + */ +export function child(parent: DatabaseReference, path: string): DatabaseReference; + +/** + * Returns an `OnDisconnect` object - see + * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript} + * for more information on how to use it. + * + * @param ref - The reference to add OnDisconnect triggers for. + */ +export function onDisconnect(ref: DatabaseReference): OnDisconnect; + +/** + * By calling `keepSynced(true)` on a location, the data for that location will automatically + * be downloaded and kept in sync, even when no listeners are attached for that location. + * + * #### Example + * + * ```js + * const dbRef = ref(getDatabase(), 'users'); + * await keepSynced(dbRef, true); + * ``` + * + * @param ref A location to keep synchronized. + * @param bool Pass `true` to keep this location synchronized, pass `false` to stop synchronization. + */ +export function keepSynced(ref: DatabaseReference, bool: boolean): Promise; + +/** + * Generates a new child location using a unique key and returns its + * `Reference`. + * + * This is the most common pattern for adding data to a collection of items. + * + * If you provide a value to `push()`, the value is written to the + * generated location. If you don't pass a value, nothing is written to the + * database and the child remains empty (but you can use the `Reference` + * elsewhere). + * + * The unique keys generated by `push()` are ordered by the current time, so the + * resulting list of items is chronologically sorted. The keys are also + * designed to be unguessable (they contain 72 random bits of entropy). + * + * See {@link https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data | Append to a list of data}. + * See {@link https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html | The 2^120 Ways to Ensure Unique Identifiers}. + * + * @param parent - The parent location. + * @param value - Optional value to be written at the generated location. + * @returns Combined `Promise` and `Reference`; resolves when write is complete, + * but can be used immediately as the `Reference` to the child location. + */ +export function push(parent: DatabaseReference, value?: unknown): ThenableReference; + +/** + * Removes the data at this Database location. + * + * Any data at child locations will also be deleted. + * + * The effect of the remove will be visible immediately and the corresponding + * event 'value' will be triggered. Synchronization of the remove to the + * Firebase servers will also be started, and the returned Promise will resolve + * when complete. If provided, the onComplete callback will be called + * asynchronously after synchronization has finished. + * + * @param ref - The location to remove. + * @returns Resolves when remove on server is complete. + */ +export function remove(ref: DatabaseReference): Promise; + +/** + * Writes multiple values to the Database at once. + * + * The `values` argument contains multiple property-value pairs that will be + * written to the Database together. Each child property can either be a simple + * property (for example, "name") or a relative path (for example, + * "name/first") from the current location to the data to update. + * + * As opposed to the `set()` method, `update()` can be use to selectively update + * only the referenced properties at the current location (instead of replacing + * all the child properties at the current location). + * + * The effect of the write will be visible immediately, and the corresponding + * events ('value', 'child_added', etc.) will be triggered. Synchronization of + * the data to the Firebase servers will also be started, and the returned + * Promise will resolve when complete. If provided, the `onComplete` callback + * will be called asynchronously after synchronization has finished. + * + * A single `update()` will generate a single "value" event at the location + * where the `update()` was performed, regardless of how many children were + * modified. + * + * Note that modifying data with `update()` will cancel any pending + * transactions at that location, so extreme care should be taken if mixing + * `update()` and `transaction()` to modify the same data. + * + * Passing `null` to `update()` will remove the data at this location. + * + * See + * {@link https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html | Introducing multi-location updates and more}. + * + * @param ref - The location to write to. + * @param values - Object containing multiple values. + * @returns Resolves when update on server is complete. + */ +export function update(ref: DatabaseReference, values: object): Promise; diff --git a/packages/database/lib/modular/query.js b/packages/database/lib/modular/query.js new file mode 100644 index 0000000000..3013e22034 --- /dev/null +++ b/packages/database/lib/modular/query.js @@ -0,0 +1,291 @@ +/** + * @typedef {import('../..').DatabaseReference} DatabaseReference + * @typedef {import('../..').DataSnapshot} DataSnapshot + * @typedef {import('./query').QueryConstraint} IQueryConstraint + * @typedef {import('./query').Query} Query + * @typedef {import('./query').OnDisconnect} OnDisconnect + * @typedef {import('./query').ListenOptions} ListenOptions + * @typedef {import('./query').Unsubscribe} Unsubscribe + * @typedef {import('./query').EventType} EventType + * @typedef {import('./query').ThenableReference} ThenableReference + */ + +/** + * @implements {IQueryConstraint} + */ +class QueryConstraint { + constructor(type, ...args) { + this._type = type; + this._args = args; + } + + _apply(query) { + return query[this._type].apply(query, this._args); + } +} + +/** + * @param {number | string | boolean | null} value + * @param {string?} key + * @returns {QueryConstraint} + */ +export function endAt(value, key) { + return new QueryConstraint('endAt', value, key); +} + +/** + * @param {number | string | boolean | null} value + * @param {string?} key + * @returns {QueryConstraint} + */ +export function endBefore(value, key) { + return new QueryConstraint('endBefore', value, key); +} + +/** + * @param {number | string | boolean | null} value, + * @param {string?} key, + * @returns {QueryConstraint} + */ +export function startAt(value, key) { + return new QueryConstraint('startAt', value, key); +} + +/** + * @param {number | string | boolean | null} value, + * @param {string?} key, + * @returns {QueryConstraint} + */ +export function startAfter(value, key) { + return new QueryConstraint('startAfter', value, key); +} + +/** + * @param {number} limit + * @returns {QueryConstraint} + */ +export function limitToFirst(limit) { + return new QueryConstraint('limitToFirst', limit); +} + +/** + * @param {number} limit + * @returns {QueryConstraint} + */ +export function limitToLast(limit) { + return new QueryConstraint('limitToLast', limit); +} + +/** + * @param {string} path + * @returns {QueryConstraint} + */ +export function orderByChild(path) { + return new QueryConstraint('orderByChild', path); +} + +export function orderByKey() { + return new QueryConstraint('orderByKey'); +} + +export function orderByPriority() { + return new QueryConstraint('orderByPriority'); +} + +export function orderByValue() { + return new QueryConstraint('orderByValue'); +} + +/** + * @param {number | string | boolean | null} value + * @param {string?} key + * @returns {QueryConstraint} + */ +export function equalTo(value, key) { + return new QueryConstraint('equalTo', value, key); +} + +/** + * @param {Query} query + * @param {QueryConstraint[]} queryConstraints + * @returns {Query} + */ +export function query(query, ...queryConstraints) { + let q = query; + for (const queryConstraint of queryConstraints) { + q = queryConstraint._apply(q); + } + return q; +} + +/** + * @param {Query} query + * @param {EventType} eventType + * @param {(snapshot: DataSnapshot) => unknown} callback + * @param {((error: Error) => unknown) | ListenOptions} cancelCallbackOrListenOptions + * @param {ListenOptions?} options + * @returns {Unsubscribe} + */ +function addEventListener(query, eventType, callback, cancelCallbackOrListenOptions, options) { + let cancelCallback = cancelCallbackOrListenOptions; + + if (typeof cancelCallbackOrListenOptions === 'object') { + cancelCallback = undefined; + options = cancelCallbackOrListenOptions; + } + + if (options && options.onlyOnce) { + const userCallback = callback; + callback = snapshot => { + query.off(eventType, callback); + return userCallback(snapshot); + }; + } + + query.on(eventType, callback, cancelCallback); + + return () => query.off(eventType, callback); +} + +/** + * @param {Query} query + * @param {(snapshot: DataSnapshot) => unknown} callback + * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions + * @param {ListenOptions?} options + * @returns {Unsubscribe} + */ +export function onValue(query, callback, cancelCallbackOrListenOptions, options) { + return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options); +} + +/** + * @param {Query} query + * @param {(snapshot: DataSnapshot, previousChildName: string | null) => unknown} callback + * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions + * @param {ListenOptions?} options + * @returns {Unsubscribe} + */ +export function onChildAdded(query, callback, cancelCallbackOrListenOptions, options) { + return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options); +} + +/** + * @param {Query} query + * @param {(snapshot: DataSnapshot, previousChildName: string | null) => unknown} callback + * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions + * @param {ListenOptions?} options + * @returns {Unsubscribe} + */ +export function onChildChanged(query, callback, cancelCallbackOrListenOptions, options) { + return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options); +} + +/** + * @param {Query} query + * @param {(snapshot: DataSnapshot, previousChildName: string | null) => unknown} callback + * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions + * @param {ListenOptions?} options + * @returns {Unsubscribe} + */ +export function onChildMoved(query, callback, cancelCallbackOrListenOptions, options) { + return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options); +} + +/** + * @param {Query} query + * @param {(snapshot: DataSnapshot, previousChildName: string | null) => unknown} callback + * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions + * @param {ListenOptions?} options + * @returns {Unsubscribe} + */ +export function onChildRemoved(query, callback, cancelCallbackOrListenOptions, options) { + return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options); +} + +/** + * @param {DatabaseReference} ref + * @param {unknown} value + * @returns {Promise} + */ +export function set(ref, value) { + return ref.set(value); +} + +/** + * @param {DatabaseReference} ref + * @param {string | number | null} priority + * @returns {Promise} + */ +export function setPriority(ref, priority) { + return ref.setPriority(priority); +} + +/** + * @param {DatabaseReference} ref + * @param {unknown} value + * @param {string | number | null} priority + * @returns {Promise} + */ +export function setWithPriority(ref, value, priority) { + return ref.setWithPriority(value, priority); +} + +/** + * @param {Query} query + * @returns {DataSnapshot} + */ +export function get(query) { + return query.once('value'); +} + +/** + * @param {DatabaseReference} parent + * @param {string} path + * @returns {DatabaseReference} + */ +export function child(parent, path) { + return parent.child(path); +} + +/** + * @param {DatabaseReference} ref + * @returns {OnDisconnect} + */ +export function onDisconnect(ref) { + return ref.onDisconnect(); +} + +/** + * @param {DatabaseReference} ref + * @param {boolean} value + * @returns {Promise} + */ +export function keepSynced(ref, value) { + return ref.keepSynced(value); +} + +/** + * @param {DatabaseReference} parent + * @param {unknown} value + * @returns {ThenableReference} + */ +export function push(parent, value) { + return parent.push(value); +} + +/** + * @param {DatabaseReference} ref + * @returns {Promise} + */ +export function remove(ref) { + return ref.remove(); +} + +/** + * @param {DatabaseReference} ref + * @param {object} values + * @returns {Promise} + */ +export function update(ref, values) { + return ref.update(values); +} diff --git a/packages/database/lib/modular/transaction.d.ts b/packages/database/lib/modular/transaction.d.ts new file mode 100644 index 0000000000..c675ac6317 --- /dev/null +++ b/packages/database/lib/modular/transaction.d.ts @@ -0,0 +1,56 @@ +import { FirebaseDatabaseTypes } from '../../index'; + +import TransactionResult = FirebaseDatabaseTypes.TransactionResult; +import DatabaseReference = FirebaseDatabaseTypes.Reference; + +export { TransactionResult }; + +/** + * An options object to configure transactions. + */ +export interface TransactionOptions { + readonly applyLocally?: boolean; +} + +/** + * Atomically modifies the data at this location. + * + * Atomically modify the data at this location. Unlike a normal `set()`, which + * just overwrites the data regardless of its previous value, `runTransaction()` is + * used to modify the existing value to a new value, ensuring there are no + * conflicts with other clients writing to the same location at the same time. + * + * To accomplish this, you pass `runTransaction()` an update function which is + * used to transform the current value into a new value. If another client + * writes to the location before your new value is successfully written, your + * update function will be called again with the new current value, and the + * write will be retried. This will happen repeatedly until your write succeeds + * without conflict or you abort the transaction by not returning a value from + * your update function. + * + * Note: Modifying data with `set()` will cancel any pending transactions at + * that location, so extreme care should be taken if mixing `set()` and + * `runTransaction()` to update the same data. + * + * Note: When using transactions with Security and Firebase Rules in place, be + * aware that a client needs `.read` access in addition to `.write` access in + * order to perform a transaction. This is because the client-side nature of + * transactions requires the client to read the data in order to transactionally + * update it. + * + * @param ref - The location to atomically modify. + * @param transactionUpdate - A developer-supplied function which will be passed + * the current data stored at this location (as a JavaScript object). The + * function should return the new value it would like written (as a JavaScript + * object). If `undefined` is returned (i.e. you return with no arguments) the + * transaction will be aborted and the data at this location will not be + * modified. + * @param options - An options object to configure transactions. + * @returns A `Promise` that can optionally be used instead of the `onComplete` + * callback to handle success and failure. + */ +export function runTransaction( + ref: DatabaseReference, + transactionUpdate: (currentData: any) => unknown, + options?: TransactionOptions, +): Promise; diff --git a/packages/database/lib/modular/transaction.js b/packages/database/lib/modular/transaction.js new file mode 100644 index 0000000000..c5c8b5e05e --- /dev/null +++ b/packages/database/lib/modular/transaction.js @@ -0,0 +1,15 @@ +/** + * @typedef {import('./database').DatabaseReference} DatabaseReference + * @typedef {import('./transaction').TransactionOptions} TransactionOptions + * @typedef {import('./transaction').TransactionResult} TransactionResult + */ + +/** + * @param {DatabaseReference} ref + * @param {(options: any) => unknown} transactionUpdate + * @param {TransactionOptions?} options + * @returns {Promise} + */ +export function runTransaction(ref, transactionUpdate, options) { + return ref.transaction(transactionUpdate, undefined, options && options.applyLocally); +} diff --git a/tests/app.js b/tests/app.js index 517fd9f817..3cc8e04eba 100644 --- a/tests/app.js +++ b/tests/app.js @@ -41,6 +41,7 @@ import '@react-native-firebase/remote-config'; import * as remoteConfigModular from '@react-native-firebase/remote-config'; import '@react-native-firebase/storage'; import * as storageModular from '@react-native-firebase/storage'; +import * as databaseModular from '@react-native-firebase/database'; import jet from 'jet/platform/react-native'; import React from 'react'; import { AppRegistry, Button, NativeModules, Text, View } from 'react-native'; @@ -68,6 +69,7 @@ jet.exposeContextProperty('inAppMessagingModular', inAppMessagingModular); jet.exposeContextProperty('installationsModular', installationsModular); jet.exposeContextProperty('crashlyticsModular', crashlyticsModular); jet.exposeContextProperty('dynamicLinksModular', dynamicLinksModular); +jet.exposeContextProperty('databaseModular', databaseModular); firebase.database().useEmulator('localhost', 9000); firebase.auth().useEmulator('http://localhost:9099'); diff --git a/tests/e2e/globals.js b/tests/e2e/globals.js index 793859f561..2bca260d1e 100644 --- a/tests/e2e/globals.js +++ b/tests/e2e/globals.js @@ -149,4 +149,10 @@ Object.defineProperty(global, 'dynamicLinksModular', { }, }); +Object.defineProperty(global, 'databaseModular', { + get() { + return jet.databaseModular; + }, +}); + global.isCI = !!process.env.CI;