diff --git a/packages/firestore/package.json b/packages/firestore/package.json index a5047f82888..2a3a1391a30 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -25,8 +25,6 @@ "test:lite:prod": "node ./scripts/run-tests.js --platform node_lite --main=lite/index.ts 'test/lite/**/*.test.ts'", "test:lite:browser": "karma start --single-run --lite", "test:lite:browser:debug": "karma start --browsers=Chrome --lite --auto-watch", - "pretest": "yarn test:prepare", - "pretest:ci": "yarn pretest", "test": "run-s lint test:all", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all:ci", "test:all:ci": "run-p test:browser test:lite:browser test:travis", @@ -44,7 +42,6 @@ "api-report:api-json": "rm -rf temp && api-extractor run --local --verbose", "api-report": "run-s api-report:main api-report:lite && yarn api-report:api-json", "doc": "api-documenter markdown --input temp --output docs", - "test:prepare": "node ./scripts/prepare-test.js", "typings:public": "node ../../scripts/build/use_typings.js ./dist/index.d.ts" }, "exports": { diff --git a/packages/firestore/scripts/prepare-test.js b/packages/firestore/scripts/prepare-test.js deleted file mode 100644 index 208647deed1..00000000000 --- a/packages/firestore/scripts/prepare-test.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file 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. - */ - -"use strict";var __spreadArray=this&&this.__spreadArray||function(to,from){for(var i=0,il=from.length,j=to.length;i): firebase.firestore.DocumentData { + * toFirestore(post: WithFieldValue): DocumentData { * return {title: post.title, author: post.author}; * }, * fromFirestore( - * snapshot: firebase.firestore.QueryDocumentSnapshot, - * options: firebase.firestore.SnapshotOptions + * snapshot: QueryDocumentSnapshot, + * options: SnapshotOptions * ): Post { * const data = snapshot.data(options)!; * return new Post(data.title, data.author); @@ -269,7 +269,7 @@ export class DocumentSnapshot< * Retrieves all fields in the document as an `Object`. Returns `undefined` if * the document doesn't exist. * - * By default, `FieldValue.serverTimestamp()` values that have not yet been + * By default, `serverTimestamp()` values that have not yet been * set to their final value will be returned as `null`. You can override * this by passing an options object. * @@ -306,7 +306,7 @@ export class DocumentSnapshot< * Retrieves the field specified by `fieldPath`. Returns `undefined` if the * document or field doesn't exist. * - * By default, a `FieldValue.serverTimestamp()` that has not yet been set to + * By default, a `serverTimestamp()` that has not yet been set to * its final value will be returned as `null`. You can override this by * passing an options object. * @@ -353,7 +353,7 @@ export class QueryDocumentSnapshot< /** * Retrieves all fields in the document as an `Object`. * - * By default, `FieldValue.serverTimestamp()` values that have not yet been + * By default, `serverTimestamp()` values that have not yet been * set to their final value will be returned as `null`. You can override * this by passing an options object. * diff --git a/packages/firestore/src/lite-api/query.ts b/packages/firestore/src/lite-api/query.ts index 59fce5b76ec..b56bbf218c3 100644 --- a/packages/firestore/src/lite-api/query.ts +++ b/packages/firestore/src/lite-api/query.ts @@ -480,8 +480,7 @@ export function newQueryFilter( if (op === Operator.ARRAY_CONTAINS || op === Operator.ARRAY_CONTAINS_ANY) { throw new FirestoreError( Code.INVALID_ARGUMENT, - `Invalid Query. You can't perform '${op}' ` + - 'queries on FieldPath.documentId().' + `Invalid Query. You can't perform '${op}' ` + 'queries on documentId().' ); } else if (op === Operator.IN || op === Operator.NOT_IN) { validateDisjunctiveFilterElements(value, op); @@ -639,7 +638,7 @@ export function newQueryBoundFromFields( if (!isCollectionGroupQuery(query) && rawValue.indexOf('/') !== -1) { throw new FirestoreError( Code.INVALID_ARGUMENT, - `Invalid query. When querying a collection and ordering by FieldPath.documentId(), ` + + `Invalid query. When querying a collection and ordering by documentId(), ` + `the value passed to ${methodName}() must be a plain document ID, but ` + `'${rawValue}' contains a slash.` ); @@ -649,7 +648,7 @@ export function newQueryBoundFromFields( throw new FirestoreError( Code.INVALID_ARGUMENT, `Invalid query. When querying a collection group and ordering by ` + - `FieldPath.documentId(), the value passed to ${methodName}() must result in a ` + + `documentId(), the value passed to ${methodName}() must result in a ` + `valid document path, but '${path}' is not because it contains an odd number ` + `of segments.` ); @@ -681,7 +680,7 @@ function parseDocumentIdValue( if (documentIdValue === '') { throw new FirestoreError( Code.INVALID_ARGUMENT, - 'Invalid query. When querying with FieldPath.documentId(), you ' + + 'Invalid query. When querying with documentId(), you ' + 'must provide a valid document ID, but it was an empty string.' ); } @@ -689,7 +688,7 @@ function parseDocumentIdValue( throw new FirestoreError( Code.INVALID_ARGUMENT, `Invalid query. When querying a collection by ` + - `FieldPath.documentId(), you must provide a plain document ID, but ` + + `documentId(), you must provide a plain document ID, but ` + `'${documentIdValue}' contains a '/' character.` ); } @@ -698,7 +697,7 @@ function parseDocumentIdValue( throw new FirestoreError( Code.INVALID_ARGUMENT, `Invalid query. When querying a collection group by ` + - `FieldPath.documentId(), the value provided must result in a valid document path, ` + + `documentId(), the value provided must result in a valid document path, ` + `but '${path}' is not because it has an odd number of segments (${path.length}).` ); } @@ -708,7 +707,7 @@ function parseDocumentIdValue( } else { throw new FirestoreError( Code.INVALID_ARGUMENT, - `Invalid query. When querying with FieldPath.documentId(), you must provide a valid ` + + `Invalid query. When querying with documentId(), you must provide a valid ` + `string or a DocumentReference, but it was: ` + `${valueDescription(documentIdValue)}.` ); diff --git a/packages/firestore/src/lite-api/snapshot.ts b/packages/firestore/src/lite-api/snapshot.ts index 100a0b2f16d..d726d92a7e4 100644 --- a/packages/firestore/src/lite-api/snapshot.ts +++ b/packages/firestore/src/lite-api/snapshot.ts @@ -57,10 +57,10 @@ import { AbstractUserDataWriter } from './user_data_writer'; * } * * const postConverter = { - * toFirestore(post: WithFieldValue): firebase.firestore.DocumentData { + * toFirestore(post: WithFieldValue): DocumentData { * return {title: post.title, author: post.author}; * }, - * fromFirestore(snapshot: firebase.firestore.QueryDocumentSnapshot): Post { + * fromFirestore(snapshot: QueryDocumentSnapshot): Post { * const data = snapshot.data(options)!; * return new Post(data.title, data.author); * } diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index c5fbb152872..c0700798ef4 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -722,7 +722,7 @@ export function parseData( validatePlainObject('Unsupported field value:', context, input); return parseObject(input, context); } else if (input instanceof FieldValue) { - // FieldValues usually parse into transforms (except FieldValue.delete()) + // FieldValues usually parse into transforms (except deleteField()) // in which case we do not want to include this field in our parsed data // (as doing so will overwrite the field directly prior to the transform // trying to transform it). So we don't add this location to diff --git a/packages/firestore/test/integration/api/array_transforms.test.ts b/packages/firestore/test/integration/api/array_transforms.test.ts index 19996c0ed96..3fb54d1c83c 100644 --- a/packages/firestore/test/integration/api/array_transforms.test.ts +++ b/packages/firestore/test/integration/api/array_transforms.test.ts @@ -15,18 +15,26 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { addEqualityMatcher } from '../../util/equality_matcher'; import { EventsAccumulator } from '../util/events_accumulator'; -import * as firebaseExport from '../util/firebase_export'; +import { + doc, + DocumentData, + DocumentReference, + DocumentSnapshot, + getDocFromCache, + onSnapshot, + setDoc, + updateDoc, + arrayRemove, + arrayUnion +} from '../util/firebase_export'; import { apiDescribe, withTestDb, withTestDoc } from '../util/helpers'; addEqualityMatcher(); -const FieldValue = firebaseExport.FieldValue; - /** * Note: Transforms are tested pretty thoroughly in server_timestamp.test.ts * (via set, update, transactions, nested in documents, multiple transforms @@ -35,27 +43,25 @@ const FieldValue = firebaseExport.FieldValue; */ apiDescribe('Array Transforms:', (persistence: boolean) => { // A document reference to read and write to. - let docRef: firestore.DocumentReference; + let docRef: DocumentReference; // Accumulator used to capture events during the test. - let accumulator: EventsAccumulator; + let accumulator: EventsAccumulator; // Listener registration for a listener maintained during the course of the // test. let unsubscribe: () => void; /** Writes some initialData and consumes the events generated. */ - async function writeInitialData( - initialData: firestore.DocumentData - ): Promise { - await docRef.set(initialData); + async function writeInitialData(initialData: DocumentData): Promise { + await setDoc(docRef, initialData); await accumulator.awaitLocalEvent(); const snapshot = await accumulator.awaitRemoteEvent(); expect(snapshot.data()).to.deep.equal(initialData); } async function expectLocalAndRemoteEvent( - expected: firestore.DocumentData + expected: DocumentData ): Promise { const localSnap = await accumulator.awaitLocalEvent(); expect(localSnap.data()).to.deep.equal(expected); @@ -70,15 +76,16 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { async function withTestSetup(test: () => Promise): Promise { await withTestDoc(persistence, async doc => { docRef = doc; - accumulator = new EventsAccumulator(); - unsubscribe = docRef.onSnapshot( + accumulator = new EventsAccumulator(); + unsubscribe = onSnapshot( + docRef, { includeMetadataChanges: true }, accumulator.storeEvent ); // wait for initial null snapshot to avoid potential races. const snapshot = await accumulator.awaitRemoteEvent(); - expect(snapshot.exists).to.be.false; + expect(snapshot.exists()).to.be.false; await test(); unsubscribe(); }); @@ -86,7 +93,7 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { it('create document with arrayUnion()', async () => { await withTestSetup(async () => { - await docRef.set({ array: FieldValue.arrayUnion(1, 2) }); + await setDoc(docRef, { array: arrayUnion(1, 2) }); await expectLocalAndRemoteEvent({ array: [1, 2] }); }); }); @@ -94,7 +101,7 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { it('append to array via update()', async () => { await withTestSetup(async () => { await writeInitialData({ array: [1, 3] }); - await docRef.update({ array: FieldValue.arrayUnion(2, 1, 4) }); + await updateDoc(docRef, { array: arrayUnion(2, 1, 4) }); await expectLocalAndRemoteEvent({ array: [1, 3, 2, 4] }); }); }); @@ -102,10 +109,7 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { it('append to array via set(..., {merge: true})', async () => { await withTestSetup(async () => { await writeInitialData({ array: [1, 3] }); - await docRef.set( - { array: FieldValue.arrayUnion(2, 1, 4) }, - { merge: true } - ); + await setDoc(docRef, { array: arrayUnion(2, 1, 4) }, { merge: true }); await expectLocalAndRemoteEvent({ array: [1, 3, 2, 4] }); }); }); @@ -113,8 +117,8 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { it('append object to array via update()', async () => { await withTestSetup(async () => { await writeInitialData({ array: [{ a: 'hi' }] }); - await docRef.update({ - array: FieldValue.arrayUnion({ a: 'hi' }, { a: 'bye' }) + await updateDoc(docRef, { + array: arrayUnion({ a: 'hi' }, { a: 'bye' }) }); await expectLocalAndRemoteEvent({ array: [{ a: 'hi' }, { a: 'bye' }] }); }); @@ -123,7 +127,7 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { it('remove from array via update()', async () => { await withTestSetup(async () => { await writeInitialData({ array: [1, 3, 1, 3] }); - await docRef.update({ array: FieldValue.arrayRemove(1, 4) }); + await updateDoc(docRef, { array: arrayRemove(1, 4) }); await expectLocalAndRemoteEvent({ array: [3, 3] }); }); }); @@ -131,10 +135,7 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { it('remove from array via set(..., {merge: true})', async () => { await withTestSetup(async () => { await writeInitialData({ array: [1, 3, 1, 3] }); - await docRef.set( - { array: FieldValue.arrayRemove(1, 4) }, - { merge: true } - ); + await setDoc(docRef, { array: arrayRemove(1, 4) }, { merge: true }); await expectLocalAndRemoteEvent({ array: [3, 3] }); }); }); @@ -142,14 +143,14 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { it('remove object from array via update()', async () => { await withTestSetup(async () => { await writeInitialData({ array: [{ a: 'hi' }, { a: 'bye' }] }); - await docRef.update({ array: FieldValue.arrayRemove({ a: 'hi' }) }); + await updateDoc(docRef, { array: arrayRemove({ a: 'hi' }) }); await expectLocalAndRemoteEvent({ array: [{ a: 'bye' }] }); }); }); it('arrayUnion() supports DocumentReference', async () => { await withTestSetup(async () => { - await docRef.set({ array: FieldValue.arrayUnion(docRef) }); + await setDoc(docRef, { array: arrayUnion(docRef) }); await expectLocalAndRemoteEvent({ array: [docRef] }); }); }); @@ -165,8 +166,8 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { (persistence ? describe : describe.skip)('Server Application: ', () => { it('set() with no cached base doc', async () => { await withTestDoc(persistence, async docRef => { - await docRef.set({ array: FieldValue.arrayUnion(1, 2) }); - const snapshot = await docRef.get({ source: 'cache' }); + await setDoc(docRef, { array: arrayUnion(1, 2) }); + const snapshot = await getDocFromCache(docRef); expect(snapshot.data()).to.deep.equal({ array: [1, 2] }); }); }); @@ -177,18 +178,18 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { // stored in our cache await withTestDoc(persistence, async docRef => { path = docRef.path; - await docRef.set({ array: [42] }); + await setDoc(docRef, { array: [42] }); }); await withTestDb(persistence, async db => { - const docRef = db.doc(path!); - await docRef.update({ array: FieldValue.arrayUnion(1, 2) }); + const docRef = doc(db, path!); + await updateDoc(docRef, { array: arrayUnion(1, 2) }); // Nothing should be cached since it was an update and we had no base // doc. let errCaught = false; try { - await docRef.get({ source: 'cache' }); + await getDocFromCache(docRef); } catch (err) { expect(err.code).to.equal('unavailable'); errCaught = true; @@ -203,36 +204,33 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { // stored in our cache await withTestDoc(persistence, async docRef => { path = docRef.path; - await docRef.set({ array: [42] }); + await setDoc(docRef, { array: [42] }); }); await withTestDb(persistence, async db => { - const docRef = db.doc(path!); - await docRef.set( - { array: FieldValue.arrayUnion(1, 2) }, - { merge: true } - ); + const docRef = doc(db, path!); + await setDoc(docRef, { array: arrayUnion(1, 2) }, { merge: true }); // Document will be cached but we'll be missing 42. - const snapshot = await docRef.get({ source: 'cache' }); + const snapshot = await getDocFromCache(docRef); expect(snapshot.data()).to.deep.equal({ array: [1, 2] }); }); }); it('update() with cached base doc using arrayUnion()', async () => { await withTestDoc(persistence, async docRef => { - await docRef.set({ array: [42] }); - await docRef.update({ array: FieldValue.arrayUnion(1, 2) }); - const snapshot = await docRef.get({ source: 'cache' }); + await setDoc(docRef, { array: [42] }); + await updateDoc(docRef, { array: arrayUnion(1, 2) }); + const snapshot = await getDocFromCache(docRef); expect(snapshot.data()).to.deep.equal({ array: [42, 1, 2] }); }); }); it('update() with cached base doc using arrayRemove()', async () => { await withTestDoc(persistence, async docRef => { - await docRef.set({ array: [42, 1, 2] }); - await docRef.update({ array: FieldValue.arrayRemove(1, 2) }); - const snapshot = await docRef.get({ source: 'cache' }); + await setDoc(docRef, { array: [42, 1, 2] }); + await updateDoc(docRef, { array: arrayRemove(1, 2) }); + const snapshot = await getDocFromCache(docRef); expect(snapshot.data()).to.deep.equal({ array: [42] }); }); }); diff --git a/packages/firestore/test/integration/api/batch_writes.test.ts b/packages/firestore/test/integration/api/batch_writes.test.ts index b1b241bf442..6844e57969d 100644 --- a/packages/firestore/test/integration/api/batch_writes.test.ts +++ b/packages/firestore/test/integration/api/batch_writes.test.ts @@ -15,54 +15,65 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { EventsAccumulator } from '../util/events_accumulator'; -import * as firebaseExport from '../util/firebase_export'; -import * as integrationHelpers from '../util/helpers'; - -const apiDescribe = integrationHelpers.apiDescribe; -const FieldPath = firebaseExport.FieldPath; -const FieldValue = firebaseExport.FieldValue; -const Timestamp = firebaseExport.Timestamp; +import { + collection, + getDoc, + setDoc, + writeBatch, + doc, + DocumentData, + DocumentSnapshot, + onSnapshot, + QuerySnapshot, + serverTimestamp, + FieldPath, + Timestamp, + QueryDocumentSnapshot +} from '../util/firebase_export'; +import { + apiDescribe, + toDataArray, + withTestCollection, + withTestDb, + withTestDoc +} from '../util/helpers'; apiDescribe('Database batch writes', (persistence: boolean) => { it('supports empty batches', () => { - return integrationHelpers.withTestDb(persistence, db => { - return db.batch().commit(); + return withTestDb(persistence, db => { + return writeBatch(db).commit(); }); }); it('can set documents', () => { - return integrationHelpers.withTestDoc(persistence, doc => { - return doc.firestore - .batch() + return withTestDoc(persistence, (doc, db) => { + return writeBatch(db) .set(doc, { foo: 'bar' }) .commit() - .then(() => doc.get()) + .then(() => getDoc(doc)) .then(snapshot => { - expect(snapshot.exists).to.equal(true); + expect(snapshot.exists()).to.equal(true); expect(snapshot.data()).to.deep.equal({ foo: 'bar' }); }); }); }); it('can set documents with merge', () => { - return integrationHelpers.withTestDoc(persistence, doc => { - return doc.firestore - .batch() + return withTestDoc(persistence, (doc, db) => { + return writeBatch(db) .set(doc, { a: 'b', nested: { a: 'b' } }, { merge: true }) .commit() .then(() => { - return doc.firestore - .batch() + return writeBatch(db) .set(doc, { c: 'd', nested: { c: 'd' } }, { merge: true }) .commit(); }) - .then(() => doc.get()) + .then(() => getDoc(doc)) .then(snapshot => { - expect(snapshot.exists).to.equal(true); + expect(snapshot.exists()).to.equal(true); expect(snapshot.data()).to.deep.equal({ a: 'b', c: 'd', @@ -73,13 +84,12 @@ apiDescribe('Database batch writes', (persistence: boolean) => { }); it('can update documents', () => { - return integrationHelpers.withTestDoc(persistence, doc => { - return doc - .set({ foo: 'bar' }) - .then(() => doc.firestore.batch().update(doc, { baz: 42 }).commit()) - .then(() => doc.get()) + return withTestDoc(persistence, (doc, db) => { + return setDoc(doc, { foo: 'bar' }) + .then(() => writeBatch(db).update(doc, { baz: 42 }).commit()) + .then(() => getDoc(doc)) .then(snapshot => { - expect(snapshot.exists).to.equal(true); + expect(snapshot.exists()).to.equal(true); expect(snapshot.data()).to.deep.equal({ foo: 'bar', baz: 42 }); }); }); @@ -97,14 +107,19 @@ apiDescribe('Database batch writes', (persistence: boolean) => { 'is.admin': true }; - return integrationHelpers.withTestDb(persistence, db => { - const doc = db.collection('counters').doc(); - return doc.firestore - .batch() - .set(doc, initialData) - .update(doc, 'owner.name', 'Sebastian', new FieldPath('is.admin'), true) + return withTestDb(persistence, db => { + const docRef = doc(collection(db, 'counters')); + return writeBatch(db) + .set(docRef, initialData) + .update( + docRef, + 'owner.name', + 'Sebastian', + new FieldPath('is.admin'), + true + ) .commit() - .then(() => doc.get()) + .then(() => getDoc(docRef)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.data()).to.deep.equal(finalData); @@ -114,207 +129,183 @@ apiDescribe('Database batch writes', (persistence: boolean) => { it('can delete documents', () => { // TODO(#1865): This test fails with node:persistence against Prod - return integrationHelpers.withTestDoc(persistence, doc => { - return doc - .set({ foo: 'bar' }) - .then(() => doc.get()) + return withTestDoc(persistence, (doc, db) => { + return setDoc(doc, { foo: 'bar' }) + .then(() => getDoc(doc)) .then(snapshot => { - expect(snapshot.exists).to.equal(true); + expect(snapshot.exists()).to.equal(true); }) - .then(() => doc.firestore.batch().delete(doc).commit()) - .then(() => doc.get()) + .then(() => writeBatch(db).delete(doc).commit()) + .then(() => getDoc(doc)) .then(snapshot => { - expect(snapshot.exists).to.equal(false); + expect(snapshot.exists()).to.equal(false); }); }); }); it('commit atomically, raising correct events', () => { - return integrationHelpers.withTestCollection( - persistence, - {}, - collection => { - const docA = collection.doc('a'); - const docB = collection.doc('b'); - const accumulator = new EventsAccumulator(); - const unsubscribe = collection.onSnapshot( - { includeMetadataChanges: true }, - accumulator.storeEvent - ); - return accumulator - .awaitEvent() - .then(initialSnap => { - expect(initialSnap.docs.length).to.equal(0); + return withTestCollection(persistence, {}, (collection, db) => { + const docA = doc(collection, 'a'); + const docB = doc(collection, 'b'); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot( + collection, + { includeMetadataChanges: true }, + accumulator.storeEvent + ); + return accumulator + .awaitEvent() + .then(initialSnap => { + expect(initialSnap.docs.length).to.equal(0); - // Atomically write two documents. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - collection.firestore - .batch() - .set(docA, { a: 1 }) - .set(docB, { b: 2 }) - .commit(); + // Atomically write two documents. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + writeBatch(db).set(docA, { a: 1 }).set(docB, { b: 2 }).commit(); - return accumulator.awaitEvent(); - }) - .then(localSnap => { - expect(localSnap.metadata.hasPendingWrites).to.equal(true); - expect(integrationHelpers.toDataArray(localSnap)).to.deep.equal([ - { a: 1 }, - { b: 2 } - ]); - return accumulator.awaitEvent(); - }) - .then(serverSnap => { - expect(serverSnap.metadata.hasPendingWrites).to.equal(false); - expect(integrationHelpers.toDataArray(serverSnap)).to.deep.equal([ - { a: 1 }, - { b: 2 } - ]); - unsubscribe(); - }); - } - ); + return accumulator.awaitEvent(); + }) + .then(localSnap => { + expect(localSnap.metadata.hasPendingWrites).to.equal(true); + expect(toDataArray(localSnap)).to.deep.equal([{ a: 1 }, { b: 2 }]); + return accumulator.awaitEvent(); + }) + .then(serverSnap => { + expect(serverSnap.metadata.hasPendingWrites).to.equal(false); + expect(toDataArray(serverSnap)).to.deep.equal([{ a: 1 }, { b: 2 }]); + unsubscribe(); + }); + }); }); it('fail atomically, raising correct events', () => { - return integrationHelpers.withTestCollection( - persistence, - {}, - collection => { - const docA = collection.doc('a'); - const docB = collection.doc('b'); - const accumulator = new EventsAccumulator(); - const unsubscribe = collection.onSnapshot( - { includeMetadataChanges: true }, - accumulator.storeEvent - ); - let batchCommitPromise: Promise; - return accumulator - .awaitEvent() - .then(initialSnap => { - expect(initialSnap.docs.length).to.equal(0); + return withTestCollection(persistence, {}, (collection, db) => { + const docA = doc(collection, 'a'); + const docB = doc(collection, 'b'); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot( + collection, + { includeMetadataChanges: true }, + accumulator.storeEvent + ); + let batchCommitPromise: Promise; + return accumulator + .awaitEvent() + .then(initialSnap => { + expect(initialSnap.docs.length).to.equal(0); - // Atomically write 1 document and update a nonexistent - // document. - batchCommitPromise = collection.firestore - .batch() - .set(docA, { a: 1 }) - .update(docB, { b: 2 }) - .commit(); + // Atomically write 1 document and update a nonexistent + // document. + batchCommitPromise = writeBatch(db) + .set(docA, { a: 1 }) + .update(docB, { b: 2 }) + .commit(); - // Node logs warnings if you don't attach an error handler to a - // Promise before it fails, so attach a dummy one here (we handle - // the rejection for real below). - batchCommitPromise.catch(err => {}); + // Node logs warnings if you don't attach an error handler to a + // Promise before it fails, so attach a dummy one here (we handle + // the rejection for real below). + batchCommitPromise.catch(err => {}); - return accumulator.awaitEvent(); - }) - .then(localSnap => { - // Local event with the set document. - expect(localSnap.metadata.hasPendingWrites).to.equal(true); - expect(integrationHelpers.toDataArray(localSnap)).to.deep.equal([ - { a: 1 } - ]); + return accumulator.awaitEvent(); + }) + .then(localSnap => { + // Local event with the set document. + expect(localSnap.metadata.hasPendingWrites).to.equal(true); + expect(toDataArray(localSnap)).to.deep.equal([{ a: 1 }]); - return accumulator.awaitEvent(); - }) - .then(serverSnap => { - // Server event with the set reverted. - expect(serverSnap.metadata.hasPendingWrites).to.equal(false); - expect(serverSnap.docs.length).to.equal(0); + return accumulator.awaitEvent(); + }) + .then(serverSnap => { + // Server event with the set reverted. + expect(serverSnap.metadata.hasPendingWrites).to.equal(false); + expect(serverSnap.docs.length).to.equal(0); - return batchCommitPromise; - }) - .then( - () => { - expect.fail('Batch commit should have failed.'); - }, - err => { - expect(err.message).to.exist; - expect(err.code).to.equal('not-found'); - unsubscribe(); - } - ); - } - ); + return batchCommitPromise; + }) + .then( + () => { + expect.fail('Batch commit should have failed.'); + }, + err => { + expect(err.message).to.exist; + expect(err.code).to.equal('not-found'); + unsubscribe(); + } + ); + }); }); it('write the same server timestamp across writes', () => { - return integrationHelpers.withTestCollection( - persistence, - {}, - collection => { - const docA = collection.doc('a'); - const docB = collection.doc('b'); - const accumulator = new EventsAccumulator(); - const unsubscribe = collection.onSnapshot( - { includeMetadataChanges: true }, - accumulator.storeEvent - ); - return accumulator - .awaitEvent() - .then(initialSnap => { - expect(initialSnap.docs.length).to.equal(0); + return withTestCollection(persistence, {}, (collection, db) => { + const docA = doc(collection, 'a'); + const docB = doc(collection, 'b'); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot( + collection, + { includeMetadataChanges: true }, + accumulator.storeEvent + ); + return accumulator + .awaitEvent() + .then(initialSnap => { + expect(initialSnap.docs.length).to.equal(0); - // Atomically write 2 documents with server timestamps. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - collection.firestore - .batch() - .set(docA, { - when: FieldValue.serverTimestamp() - }) - .set(docB, { - when: FieldValue.serverTimestamp() - }) - .commit(); + // Atomically write 2 documents with server timestamps. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + writeBatch(db) + .set(docA, { + when: serverTimestamp() + }) + .set(docB, { + when: serverTimestamp() + }) + .commit(); - return accumulator.awaitEvent(); - }) - .then(localSnap => { - expect(localSnap.metadata.hasPendingWrites).to.equal(true); - expect(localSnap.docs.length).to.equal(2); - expect(integrationHelpers.toDataArray(localSnap)).to.deep.equal([ - { when: null }, - { when: null } - ]); + return accumulator.awaitEvent(); + }) + .then(localSnap => { + expect(localSnap.metadata.hasPendingWrites).to.equal(true); + expect(localSnap.docs.length).to.equal(2); + expect(toDataArray(localSnap)).to.deep.equal([ + { when: null }, + { when: null } + ]); - return accumulator.awaitEvent(); - }) - .then(serverSnap => { - expect(serverSnap.metadata.hasPendingWrites).to.equal(false); - expect(serverSnap.docs.length).to.equal(2); - const when = serverSnap.docs[0].data()['when']; - expect(when).to.be.an.instanceof(Timestamp); - expect(serverSnap.docs[1].data()['when']).to.deep.equal(when); - const docChanges = serverSnap.docChanges({ - includeMetadataChanges: true - }); - expect(docChanges[0].type).to.equal('modified'); - unsubscribe(); + return accumulator.awaitEvent(); + }) + .then(serverSnap => { + expect(serverSnap.metadata.hasPendingWrites).to.equal(false); + expect(serverSnap.docs.length).to.equal(2); + const when = serverSnap.docs[0].data()['when']; + expect(when).to.be.an.instanceof(Timestamp); + expect(serverSnap.docs[1].data()['when']).to.deep.equal(when); + const docChanges = serverSnap.docChanges({ + includeMetadataChanges: true }); - } - ); + expect(docChanges[0].type).to.equal('modified'); + unsubscribe(); + }); + }); }); it('can write the same document multiple times', () => { - return integrationHelpers.withTestDoc(persistence, doc => { - const accumulator = new EventsAccumulator(); - const unsubscribe = doc.onSnapshot( + return withTestDoc(persistence, (doc, db) => { + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot( + doc, { includeMetadataChanges: true }, accumulator.storeEvent ); return accumulator .awaitEvent() .then(initialSnap => { - expect(initialSnap.exists).to.equal(false); + expect(initialSnap.exists()).to.equal(false); // eslint-disable-next-line @typescript-eslint/no-floating-promises - doc.firestore - .batch() + writeBatch(db) .delete(doc) .set(doc, { a: 1, b: 1, when: 'when' }) .update(doc, { b: 2, - when: FieldValue.serverTimestamp() + when: serverTimestamp() }) .commit(); @@ -346,30 +337,23 @@ apiDescribe('Database batch writes', (persistence: boolean) => { } } - it('for Writebatch.set()', () => { - return integrationHelpers.withTestDb(persistence, db => { - const docRef = db - .collection('posts') - .doc() - .withConverter({ - toFirestore(post: Post): firestore.DocumentData { - return { title: post.title, author: post.author }; - }, - fromFirestore( - snapshot: firestore.QueryDocumentSnapshot, - options: firestore.SnapshotOptions - ): Post { - const data = snapshot.data(options); - return new Post(data.title, data.author); - } - }); - return docRef.firestore - .batch() + it('for WriteBatch.set()', () => { + return withTestDb(persistence, db => { + const docRef = doc(collection(db, 'posts')).withConverter({ + toFirestore(post: Post): DocumentData { + return { title: post.title, author: post.author }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): Post { + const data = snapshot.data(); + return new Post(data.title, data.author); + } + }); + return writeBatch(db) .set(docRef, new Post('post', 'author')) .commit() - .then(() => docRef.get()) + .then(() => getDoc(docRef)) .then(snapshot => { - expect(snapshot.exists).to.equal(true); + expect(snapshot.exists()).to.equal(true); expect(snapshot.data()!.byline()).to.deep.equal('post, by author'); }); }); diff --git a/packages/firestore/test/integration/api/bundle.test.ts b/packages/firestore/test/integration/api/bundle.test.ts index 92d9a24c99f..3dcc0186e18 100644 --- a/packages/firestore/test/integration/api/bundle.test.ts +++ b/packages/firestore/test/integration/api/bundle.test.ts @@ -15,10 +15,23 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { EventsAccumulator } from '../util/events_accumulator'; +import { + collection, + doc, + Firestore, + getDocs, + getDocsFromCache, + loadBundle, + LoadBundleTask, + LoadBundleTaskProgress, + namedQuery, + onSnapshot, + QuerySnapshot, + setDoc +} from '../util/firebase_export'; import { apiDescribe, toDataArray, @@ -28,14 +41,14 @@ import { export const encoder = new TextEncoder(); -function verifySuccessProgress(p: firestore.LoadBundleTaskProgress): void { +function verifySuccessProgress(p: LoadBundleTaskProgress): void { expect(p.taskState).to.equal('Success'); expect(p.bytesLoaded).to.be.equal(p.totalBytes); expect(p.documentsLoaded).to.equal(p.totalDocuments); } function verifyInProgress( - p: firestore.LoadBundleTaskProgress, + p: LoadBundleTaskProgress, expectedDocuments: number ): void { expect(p.taskState).to.equal('Running'); @@ -57,7 +70,7 @@ const BUNDLE_TEMPLATE = [ ]; apiDescribe('Bundles', (persistence: boolean) => { - function verifySnapEqualsTestDocs(snap: firestore.QuerySnapshot): void { + function verifySnapEqualsTestDocs(snap: QuerySnapshot): void { expect(toDataArray(snap)).to.deep.equal([ { k: 'a', bar: 1 }, { k: 'b', bar: 2 } @@ -68,8 +81,8 @@ apiDescribe('Bundles', (persistence: boolean) => { * Returns a valid bundle string from replacing project id in `BUNDLE_TEMPLATE` with the given * db project id (also recalculate length prefixes). */ - function bundleString(db: firestore.FirebaseFirestore): string { - const projectId: string = db.app.options.projectId; + function bundleString(db: Firestore): string { + const projectId: string = db.app.options.projectId!; // Extract elements from BUNDLE_TEMPLATE and replace the project ID. const elements = BUNDLE_TEMPLATE.map(e => e.replace('{0}', projectId)); @@ -92,9 +105,9 @@ apiDescribe('Bundles', (persistence: boolean) => { it('load with documents only with on progress and promise interface', () => { return withTestDb(persistence, async db => { - const progressEvents: firestore.LoadBundleTaskProgress[] = []; + const progressEvents: LoadBundleTaskProgress[] = []; let completeCalled = false; - const task: firestore.LoadBundleTask = db.loadBundle(bundleString(db)); + const task: LoadBundleTask = loadBundle(db, bundleString(db)); task.onProgress( progress => { progressEvents.push(progress); @@ -105,7 +118,7 @@ apiDescribe('Bundles', (persistence: boolean) => { } ); await task; - let fulfillProgress: firestore.LoadBundleTaskProgress; + let fulfillProgress: LoadBundleTaskProgress; await task.then(progress => { fulfillProgress = progress; }); @@ -120,42 +133,41 @@ apiDescribe('Bundles', (persistence: boolean) => { // Read from cache. These documents do not exist in backend, so they can // only be read from cache. - let snap = await db.collection('coll-1').get({ source: 'cache' }); + let snap = await getDocsFromCache(collection(db, 'coll-1')); verifySnapEqualsTestDocs(snap); - snap = await (await db.namedQuery('limit'))!.get({ - source: 'cache' - }); + snap = await getDocsFromCache((await namedQuery(db, 'limit'))!); expect(toDataArray(snap)).to.deep.equal([{ k: 'b', bar: 2 }]); - snap = await (await db.namedQuery('limit-to-last'))!.get({ - source: 'cache' - }); + snap = await getDocsFromCache((await namedQuery(db, 'limit-to-last'))!); expect(toDataArray(snap)).to.deep.equal([{ k: 'a', bar: 1 }]); }); }); it('load with documents and queries with promise interface', () => { return withTestDb(persistence, async db => { - const fulfillProgress: firestore.LoadBundleTaskProgress = - await db.loadBundle(bundleString(db)); + const fulfillProgress: LoadBundleTaskProgress = await loadBundle( + db, + bundleString(db) + ); verifySuccessProgress(fulfillProgress!); // Read from cache. These documents do not exist in backend, so they can // only be read from cache. - const snap = await db.collection('coll-1').get({ source: 'cache' }); + const snap = await getDocsFromCache(collection(db, 'coll-1')); verifySnapEqualsTestDocs(snap); }); }); it('load for a second time skips', () => { return withTestDb(persistence, async db => { - await db.loadBundle(bundleString(db)); + await loadBundle(db, bundleString(db)); let completeCalled = false; - const progressEvents: firestore.LoadBundleTaskProgress[] = []; - const task: firestore.LoadBundleTask = db.loadBundle( + const progressEvents: LoadBundleTaskProgress[] = []; + const task: LoadBundleTask = loadBundle( + db, encoder.encode(bundleString(db)) ); task.onProgress( @@ -177,21 +189,22 @@ apiDescribe('Bundles', (persistence: boolean) => { // Read from cache. These documents do not exist in backend, so they can // only be read from cache. - const snap = await db.collection('coll-1').get({ source: 'cache' }); + const snap = await getDocsFromCache(collection(db, 'coll-1')); verifySnapEqualsTestDocs(snap); }); }); it('load with documents already pulled from backend', () => { return withTestDb(persistence, async db => { - await db.doc('coll-1/a').set({ k: 'a', bar: 0 }); - await db.doc('coll-1/b').set({ k: 'b', bar: 0 }); + await setDoc(doc(db, 'coll-1/a'), { k: 'a', bar: 0 }); + await setDoc(doc(db, 'coll-1/b'), { k: 'b', bar: 0 }); - const accumulator = new EventsAccumulator(); - db.collection('coll-1').onSnapshot(accumulator.storeEvent); + const accumulator = new EventsAccumulator(); + onSnapshot(collection(db, 'coll-1'), accumulator.storeEvent); await accumulator.awaitEvent(); - const progress = await db.loadBundle( + const progress = await loadBundle( + db, // Testing passing in non-string bundles. encoder.encode(bundleString(db)) ); @@ -202,29 +215,31 @@ apiDescribe('Bundles', (persistence: boolean) => { // cache can only be tested in spec tests. await accumulator.assertNoAdditionalEvents(); - let snap = await (await db.namedQuery('limit'))!.get(); + let snap = await getDocs((await namedQuery(db, 'limit'))!); expect(toDataArray(snap)).to.deep.equal([{ k: 'b', bar: 0 }]); - snap = await (await db.namedQuery('limit-to-last'))!.get(); + snap = await getDocs((await namedQuery(db, 'limit-to-last'))!); expect(toDataArray(snap)).to.deep.equal([{ k: 'a', bar: 0 }]); }); }); it('loaded documents should not be GC-ed right away', () => { return withTestDb(persistence, async db => { - const fulfillProgress: firestore.LoadBundleTaskProgress = - await db.loadBundle(bundleString(db)); + const fulfillProgress: LoadBundleTaskProgress = await loadBundle( + db, + bundleString(db) + ); verifySuccessProgress(fulfillProgress!); // Read a different collection, this will trigger GC. - let snap = await db.collection('coll-other').get(); + let snap = await getDocsFromCache(collection(db, 'coll-other')); expect(snap.empty).to.be.true; // Read the loaded documents, expecting document in cache. With memory // GC, the documents would get GC-ed if we did not hold the document keys // in a "umbrella" target. See local_store.ts for details. - snap = await db.collection('coll-1').get({ source: 'cache' }); + snap = await getDocsFromCache(collection(db, 'coll-1')); verifySnapEqualsTestDocs(snap); }); }); @@ -232,19 +247,17 @@ apiDescribe('Bundles', (persistence: boolean) => { it('load with documents from other projects fails', () => { return withTestDb(persistence, async db => { return withAlternateTestDb(persistence, async otherDb => { - await expect(otherDb.loadBundle(bundleString(db))).to.be.rejectedWith( + await expect(loadBundle(otherDb, bundleString(db))).to.be.rejectedWith( 'Tried to deserialize key from different project' ); // Verify otherDb still functions, despite loaded a problematic bundle. - const finalProgress = await otherDb.loadBundle(bundleString(otherDb)); + const finalProgress = await loadBundle(otherDb, bundleString(otherDb)); verifySuccessProgress(finalProgress); // Read from cache. These documents do not exist in backend, so they can // only be read from cache. - const snap = await otherDb - .collection('coll-1') - .get({ source: 'cache' }); + const snap = await getDocsFromCache(collection(otherDb, 'coll-1')); verifySnapEqualsTestDocs(snap); }); }); diff --git a/packages/firestore/test/integration/api/cursor.test.ts b/packages/firestore/test/integration/api/cursor.test.ts index 43257d6fd5c..4fe5228c437 100644 --- a/packages/firestore/test/integration/api/cursor.test.ts +++ b/packages/firestore/test/integration/api/cursor.test.ts @@ -18,7 +18,23 @@ import { Timestamp as TimestampInstance } from '@firebase/firestore-types'; import { expect } from 'chai'; -import * as firebaseExport from '../util/firebase_export'; +import { + collection, + doc, + documentId, + endAt, + endBefore, + getDoc, + getDocs, + limit, + orderBy, + query, + setDoc, + startAfter, + startAt, + Timestamp, + where +} from '../util/firebase_export'; import { apiDescribe, toDataArray, @@ -28,9 +44,6 @@ import { withTestDbs } from '../util/helpers'; -const Timestamp = firebaseExport.Timestamp; -const FieldPath = firebaseExport.FieldPath; - apiDescribe('Cursors', (persistence: boolean) => { it('can page through items', () => { const testDocs = { @@ -42,13 +55,11 @@ apiDescribe('Cursors', (persistence: boolean) => { f: { v: 'f' } }; return withTestCollection(persistence, testDocs, coll => { - return coll - .limit(2) - .get() + return getDocs(query(coll, limit(2))) .then(docs => { expect(toDataArray(docs)).to.deep.equal([{ v: 'a' }, { v: 'b' }]); const lastDoc = docs.docs[docs.docs.length - 1]; - return coll.limit(3).startAfter(lastDoc).get(); + return getDocs(query(coll, limit(3), startAfter(lastDoc))); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([ @@ -57,12 +68,12 @@ apiDescribe('Cursors', (persistence: boolean) => { { v: 'e' } ]); const lastDoc = docs.docs[docs.docs.length - 1]; - return coll.limit(1).startAfter(lastDoc).get(); + return getDocs(query(coll, limit(1), startAfter(lastDoc))); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([{ v: 'f' }]); const lastDoc = docs.docs[docs.docs.length - 1]; - return coll.limit(3).startAfter(lastDoc).get(); + return getDocs(query(coll, limit(3), startAfter(lastDoc))); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([]); @@ -80,22 +91,17 @@ apiDescribe('Cursors', (persistence: boolean) => { f: { k: 'f', nosort: 1 } // should not show up }; return withTestCollection(persistence, testDocs, coll => { - const query = coll.orderBy('sort'); - return coll - .doc('c') - .get() + const sortedQuery = query(coll, orderBy('sort')); + return getDoc(doc(coll, 'c')) .then(doc => { expect(doc.data()).to.deep.equal({ k: 'c', sort: 2 }); - return query - .startAt(doc) - .get() - .then(docs => { - expect(toDataArray(docs)).to.deep.equal([ - { k: 'c', sort: 2 }, - { k: 'd', sort: 2 } - ]); - return query.endBefore(doc).get(); - }); + return getDocs(query(sortedQuery, startAt(doc))).then(docs => { + expect(toDataArray(docs)).to.deep.equal([ + { k: 'c', sort: 2 }, + { k: 'd', sort: 2 } + ]); + return getDocs(query(sortedQuery, endBefore(doc))); + }); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([ @@ -117,17 +123,15 @@ apiDescribe('Cursors', (persistence: boolean) => { f: { k: 'f', nosort: 1 } // should not show up }; return withTestCollection(persistence, testDocs, coll => { - const query = coll.orderBy('sort'); - return query - .startAt(2) - .get() + const sortedQuery = query(coll, orderBy('sort')); + return getDocs(query(sortedQuery, startAt(2))) .then(docs => { expect(toDataArray(docs)).to.deep.equal([ { k: 'b', sort: 2 }, { k: 'c', sort: 2 }, { k: 'd', sort: 2 } ]); - return query.endBefore(2).get(); + return getDocs(query(sortedQuery, endBefore(2))); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([ @@ -149,23 +153,26 @@ apiDescribe('Cursors', (persistence: boolean) => { return withTestDbs(persistence, 2, ([reader, writer]) => { // Create random subcollection with documents pre-filled. Note that // we use subcollections to test the relative nature of __id__. - const writerCollection = writer - .collection('parent-collection') - .doc() - .collection('sub-collection'); - const readerCollection = reader.collection(writerCollection.path); + const writerCollection = collection( + doc(collection(writer, 'parent-collection')), + 'sub-collection' + ); + const readerCollection = collection(reader, writerCollection.path); const sets: Array> = []; Object.keys(testDocs).forEach((key: string) => { - sets.push(writerCollection.doc(key).set(testDocs[key])); + sets.push(setDoc(doc(writerCollection, key), testDocs[key])); }); return Promise.all(sets) .then(() => { - return readerCollection - .orderBy(FieldPath.documentId()) - .startAt('b') - .endBefore('d') - .get(); + return getDocs( + query( + readerCollection, + orderBy(documentId()), + startAt('b'), + endBefore('d') + ) + ); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([{ k: 'b' }, { k: 'c' }]); @@ -177,25 +184,27 @@ apiDescribe('Cursors', (persistence: boolean) => { // We require a db to create reference values return withTestDb(persistence, db => { const testDocs = { - a: { k: '1a', ref: db.collection('1').doc('a') }, - b: { k: '1b', ref: db.collection('1').doc('b') }, - c: { k: '2a', ref: db.collection('2').doc('a') }, - d: { k: '2b', ref: db.collection('2').doc('b') }, - e: { k: '3a', ref: db.collection('3').doc('a') } + a: { k: '1a', ref: doc(db, '1', 'a') }, + b: { k: '1b', ref: doc(db, '1', 'b') }, + c: { k: '2a', ref: doc(db, '2', 'a') }, + d: { k: '2b', ref: doc(db, '2', 'b') }, + e: { k: '3a', ref: doc(db, '3', 'a') } }; return withTestCollection(persistence, testDocs, coll => { - const query = coll.orderBy('ref'); - return query - .startAfter(db.collection('1').doc('a')) - .endAt(db.collection('2').doc('b')) - .get() - .then(docs => { - expect(toDataArray(docs).map(v => v['k'])).to.deep.equal([ - '1b', - '2a', - '2b' - ]); - }); + const sortedQuery = query(coll, orderBy('ref')); + return getDocs( + query( + sortedQuery, + startAfter(doc(db, '1', 'a')), + endAt(doc(db, '2', 'b')) + ) + ).then(docs => { + expect(toDataArray(docs).map(v => v['k'])).to.deep.equal([ + '1b', + '2a', + '2b' + ]); + }); }); }); }); @@ -210,13 +219,13 @@ apiDescribe('Cursors', (persistence: boolean) => { f: { k: 'f', nosort: 1 } // should not show up }; return withTestCollection(persistence, testDocs, coll => { - const query = coll - .orderBy('sort', 'desc') + const sortedQuery = query( + coll, + orderBy('sort', 'desc'), // default indexes reverse the key ordering for descending sorts - .orderBy(FieldPath.documentId(), 'desc'); - return query - .startAt(2) - .get() + orderBy(documentId(), 'desc') + ); + return getDocs(query(sortedQuery, startAt(2))) .then(docs => { expect(toDataArray(docs)).to.deep.equal([ { k: 'c', sort: 2 }, @@ -224,7 +233,7 @@ apiDescribe('Cursors', (persistence: boolean) => { { k: 'a', sort: 1 }, { k: 'e', sort: 0 } ]); - return query.endBefore(2).get(); + return getDocs(query(sortedQuery, endBefore(2))); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([{ k: 'd', sort: 3 }]); @@ -248,14 +257,16 @@ apiDescribe('Cursors', (persistence: boolean) => { f: { timestamp: makeTimestamp(100, 4) } }; return withTestCollection(persistence, testDocs, coll => { - return coll - .orderBy('timestamp') - .startAfter(makeTimestamp(100, 2)) - .endAt(makeTimestamp(100, 5)) - .get() - .then(docs => { - expect(toIds(docs)).to.deep.equal(['c', 'f', 'b', 'e']); - }); + return getDocs( + query( + coll, + orderBy('timestamp'), + startAfter(makeTimestamp(100, 2)), + endAt(makeTimestamp(100, 5)) + ) + ).then(docs => { + expect(toIds(docs)).to.deep.equal(['c', 'f', 'b', 'e']); + }); }); }); @@ -268,13 +279,15 @@ apiDescribe('Cursors', (persistence: boolean) => { e: { timestamp: makeTimestamp(100, 6) } }; return withTestCollection(persistence, testDocs, coll => { - return coll - .where('timestamp', '>=', makeTimestamp(100, 5)) - .where('timestamp', '<', makeTimestamp(100, 8)) - .get() - .then(docs => { - expect(toIds(docs)).to.deep.equal(['d', 'e', 'a']); - }); + return getDocs( + query( + coll, + where('timestamp', '>=', makeTimestamp(100, 5)), + where('timestamp', '<', makeTimestamp(100, 8)) + ) + ).then(docs => { + expect(toIds(docs)).to.deep.equal(['d', 'e', 'a']); + }); }); }); @@ -287,19 +300,17 @@ apiDescribe('Cursors', (persistence: boolean) => { a: { timestamp: nanos } }; return withTestCollection(persistence, testDocs, coll => { - return coll - .where('timestamp', '==', nanos) - .get() + return getDocs(query(coll, where('timestamp', '==', nanos))) .then(docs => { expect(toIds(docs)).to.deep.equal(['a']); - return coll.where('timestamp', '==', micros).get(); + return getDocs(query(coll, where('timestamp', '==', micros))); }) .then(docs => { // Because Timestamp should have been truncated to microseconds, the // microsecond timestamp should be considered equal to the // nanosecond one. expect(toIds(docs)).to.deep.equal(['a']); - return coll.where('timestamp', '==', millis).get(); + return getDocs(query(coll, where('timestamp', '==', millis))); }) .then(docs => { // The truncation is just to the microseconds, however, so the diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 89a6a0883e7..b4ecb21d07b 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -15,13 +15,54 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; +import { deleteApp } from '@firebase/app'; import { Deferred } from '@firebase/util'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { EventsAccumulator } from '../util/events_accumulator'; -import * as firebaseExport from '../util/firebase_export'; +import { + addDoc, + clearIndexedDbPersistence, + collection, + collectionGroup, + deleteDoc, + deleteField, + disableNetwork, + doc, + DocumentData, + documentId, + DocumentSnapshot, + enableIndexedDbPersistence, + enableNetwork, + getDoc, + getDocFromCache, + getDocFromServer, + getDocs, + initializeFirestore, + limit, + onSnapshot, + onSnapshotsInSync, + orderBy, + query, + queryEqual, + refEqual, + serverTimestamp, + setDoc, + SetOptions, + terminate, + updateDoc, + waitForPendingWrites, + where, + writeBatch, + QueryDocumentSnapshot, + DocumentReference, + runTransaction, + WithFieldValue, + Timestamp, + FieldPath, + newTestFirestore +} from '../util/firebase_export'; import { apiDescribe, withTestCollection, @@ -35,17 +76,10 @@ import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; use(chaiAsPromised); -const newTestFirestore = firebaseExport.newTestFirestore; -const Timestamp = firebaseExport.Timestamp; -const FieldPath = firebaseExport.FieldPath; -const FieldValue = firebaseExport.FieldValue; -const DocumentReference = firebaseExport.DocumentReference; -const QueryDocumentSnapshot = firebaseExport.QueryDocumentSnapshot; - apiDescribe('Database', (persistence: boolean) => { it('can set a document', () => { return withTestDoc(persistence, docRef => { - return docRef.set({ + return setDoc(docRef, { desc: 'Stuff related to Firestore project...', owner: { name: 'Jonny', @@ -57,7 +91,7 @@ apiDescribe('Database', (persistence: boolean) => { it('doc() will auto generate an ID', () => { return withTestDb(persistence, async db => { - const ref = db.collection('foo').doc(); + const ref = doc(collection(db, 'foo')); // Auto IDs are 20 characters long expect(ref.id.length).to.equal(20); }); @@ -66,20 +100,19 @@ apiDescribe('Database', (persistence: boolean) => { it('can delete a document', () => { // TODO(#1865): This test fails with node:persistence against Prod return withTestDoc(persistence, docRef => { - return docRef - .set({ foo: 'bar' }) + return setDoc(docRef, { foo: 'bar' }) .then(() => { - return docRef.get(); + return getDoc(docRef); }) .then(doc => { expect(doc.data()).to.deep.equal({ foo: 'bar' }); - return docRef.delete(); + return deleteDoc(docRef); }) .then(() => { - return docRef.get(); + return getDoc(docRef); }) .then(doc => { - expect(doc.exists).to.equal(false); + expect(doc.exists()).to.equal(false); }); }); }); @@ -98,10 +131,9 @@ apiDescribe('Database', (persistence: boolean) => { desc: 'NewDescription', owner: { name: 'Jonny', email: 'new@xyz.com' } }; - return doc - .set(initialData) - .then(() => doc.update(updateData)) - .then(() => doc.get()) + return setDoc(doc, initialData) + .then(() => updateDoc(doc, updateData)) + .then(() => getDoc(doc)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.data()).to.deep.equal(finalData); @@ -111,8 +143,8 @@ apiDescribe('Database', (persistence: boolean) => { it('can retrieve document that does not exist', () => { return withTestDoc(persistence, doc => { - return doc.get().then(snapshot => { - expect(snapshot.exists).to.equal(false); + return getDoc(doc).then(snapshot => { + expect(snapshot.exists()).to.equal(false); expect(snapshot.data()).to.equal(undefined); expect(snapshot.get('foo')).to.equal(undefined); }); @@ -122,25 +154,25 @@ apiDescribe('Database', (persistence: boolean) => { // eslint-disable-next-line no-restricted-properties (persistence ? it : it.skip)('can update an unknown document', () => { return withTestDbs(persistence, 2, async ([reader, writer]) => { - const writerRef = writer.collection('collection').doc(); - const readerRef = reader.collection('collection').doc(writerRef.id); - await writerRef.set({ a: 'a' }); - await readerRef.update({ b: 'b' }); - await writerRef - .get({ source: 'cache' }) - .then(doc => expect(doc.exists).to.be.true); - await readerRef.get({ source: 'cache' }).then( + const writerRef = doc(collection(writer, 'collection')); + const readerRef = doc(collection(reader, 'collection'), writerRef.id); + await setDoc(writerRef, { a: 'a' }); + await updateDoc(readerRef, { b: 'b' }); + await getDocFromCache(writerRef).then( + doc => expect(doc.exists).to.be.true + ); + await getDocFromCache(readerRef).then( () => { expect.fail('Expected cache miss'); }, err => expect(err.code).to.be.equal('unavailable') ); - await writerRef - .get() - .then(doc => expect(doc.data()).to.deep.equal({ a: 'a', b: 'b' })); - await readerRef - .get() - .then(doc => expect(doc.data()).to.deep.equal({ a: 'a', b: 'b' })); + await getDoc(writerRef).then(doc => + expect(doc.data()).to.deep.equal({ a: 'a', b: 'b' }) + ); + await getDoc(readerRef).then(doc => + expect(doc.data()).to.deep.equal({ a: 'a', b: 'b' }) + ); }); }); @@ -159,10 +191,9 @@ apiDescribe('Database', (persistence: boolean) => { desc: 'description', 'owner.data': { name: 'Sebastian', email: 'abc@xyz.com' } }; - return doc - .set(initialData) - .then(() => doc.set(mergeData, { merge: true })) - .then(() => doc.get()) + return setDoc(doc, initialData) + .then(() => setDoc(doc, mergeData, { merge: true })) + .then(() => getDoc(doc)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.data()).to.deep.equal(finalData); @@ -176,13 +207,12 @@ apiDescribe('Database', (persistence: boolean) => { updated: false }; const mergeData = { - time: FieldValue.serverTimestamp(), - nested: { time: FieldValue.serverTimestamp() } + time: serverTimestamp(), + nested: { time: serverTimestamp() } }; - return doc - .set(initialData) - .then(() => doc.set(mergeData, { merge: true })) - .then(() => doc.get()) + return setDoc(doc, initialData) + .then(() => setDoc(doc, mergeData, { merge: true })) + .then(() => getDoc(doc)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.get('updated')).to.be.false; @@ -194,24 +224,24 @@ apiDescribe('Database', (persistence: boolean) => { it('can merge empty object', async () => { await withTestDoc(persistence, async doc => { - const accumulator = new EventsAccumulator(); - const unsubscribe = doc.onSnapshot(accumulator.storeEvent); + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot(doc, accumulator.storeEvent); await accumulator .awaitEvent() - .then(() => doc.set({})) + .then(() => setDoc(doc, {})) .then(() => accumulator.awaitEvent()) .then(docSnapshot => expect(docSnapshot.data()).to.be.deep.equal({})) - .then(() => doc.set({ a: {} }, { mergeFields: ['a'] })) + .then(() => setDoc(doc, { a: {} }, { mergeFields: ['a'] })) .then(() => accumulator.awaitEvent()) .then(docSnapshot => expect(docSnapshot.data()).to.be.deep.equal({ a: {} }) ) - .then(() => doc.set({ b: {} }, { merge: true })) + .then(() => setDoc(doc, { b: {} }, { merge: true })) .then(() => accumulator.awaitEvent()) .then(docSnapshot => expect(docSnapshot.data()).to.be.deep.equal({ a: {}, b: {} }) ) - .then(() => doc.get({ source: 'server' })) + .then(() => getDocFromServer(doc)) .then(docSnapshot => { expect(docSnapshot.data()).to.be.deep.equal({ a: {}, b: {} }); }); @@ -222,18 +252,18 @@ apiDescribe('Database', (persistence: boolean) => { it('update with empty object replaces all fields', () => { return withTestDoc(persistence, async doc => { - await doc.set({ a: 'a' }); - await doc.update('a', {}); - const docSnapshot = await doc.get(); + await setDoc(doc, { a: 'a' }); + await updateDoc(doc, 'a', {}); + const docSnapshot = await getDoc(doc); expect(docSnapshot.data()).to.be.deep.equal({ a: {} }); }); }); it('merge with empty object replaces all fields', () => { return withTestDoc(persistence, async doc => { - await doc.set({ a: 'a' }); - await doc.set({ 'a': {} }, { merge: true }); - const docSnapshot = await doc.get(); + await setDoc(doc, { a: 'a' }); + await setDoc(doc, { 'a': {} }, { merge: true }); + const docSnapshot = await getDoc(doc); expect(docSnapshot.data()).to.be.deep.equal({ a: {} }); }); }); @@ -246,17 +276,16 @@ apiDescribe('Database', (persistence: boolean) => { nested: { untouched: true, foo: 'bar' } }; const mergeData = { - foo: FieldValue.delete(), - nested: { foo: FieldValue.delete() } + foo: deleteField(), + nested: { foo: deleteField() } }; const finalData = { untouched: true, nested: { untouched: true } }; - return doc - .set(initialData) - .then(() => doc.set(mergeData, { merge: true })) - .then(() => doc.get()) + return setDoc(doc, initialData) + .then(() => setDoc(doc, mergeData, { merge: true })) + .then(() => getDoc(doc)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.data()).to.deep.equal(finalData); @@ -273,11 +302,11 @@ apiDescribe('Database', (persistence: boolean) => { nested: { untouched: true, foo: 'bar' } }; const mergeData = { - foo: FieldValue.delete(), - inner: { foo: FieldValue.delete() }, + foo: deleteField(), + inner: { foo: deleteField() }, nested: { - untouched: FieldValue.delete(), - foo: FieldValue.delete() + untouched: deleteField(), + foo: deleteField() } }; const finalData = { @@ -285,12 +314,13 @@ apiDescribe('Database', (persistence: boolean) => { inner: {}, nested: { untouched: true } }; - return doc - .set(initialData) + return setDoc(doc, initialData) .then(() => - doc.set(mergeData, { mergeFields: ['foo', 'inner', 'nested.foo'] }) + setDoc(doc, mergeData, { + mergeFields: ['foo', 'inner', 'nested.foo'] + }) ) - .then(() => doc.get()) + .then(() => getDoc(doc)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.data()).to.deep.equal(finalData); @@ -306,16 +336,17 @@ apiDescribe('Database', (persistence: boolean) => { nested: { untouched: true, foo: 'bar' } }; const mergeData = { - foo: FieldValue.serverTimestamp(), - inner: { foo: FieldValue.serverTimestamp() }, - nested: { foo: FieldValue.serverTimestamp() } + foo: serverTimestamp(), + inner: { foo: serverTimestamp() }, + nested: { foo: serverTimestamp() } }; - return doc - .set(initialData) + return setDoc(doc, initialData) .then(() => - doc.set(mergeData, { mergeFields: ['foo', 'inner', 'nested.foo'] }) + setDoc(doc, mergeData, { + mergeFields: ['foo', 'inner', 'nested.foo'] + }) ) - .then(() => doc.get()) + .then(() => getDoc(doc)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.get('foo')).to.be.instanceof(Timestamp); @@ -344,10 +375,9 @@ apiDescribe('Database', (persistence: boolean) => { topLevel: ['new'], mapInArray: [{ data: 'new' }] }; - return doc - .set(initialData) - .then(() => doc.set(mergeData, { merge: true })) - .then(() => doc.get()) + return setDoc(doc, initialData) + .then(() => setDoc(doc, mergeData, { merge: true })) + .then(() => getDoc(doc)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.data()).to.deep.equal(finalData); @@ -359,7 +389,8 @@ apiDescribe('Database', (persistence: boolean) => { return withTestDoc(persistence, async docRef => { expect(() => { // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.set( + setDoc( + docRef, { desc: 'NewDescription' }, { mergeFields: ['desc', 'owner'] } ); @@ -376,11 +407,12 @@ apiDescribe('Database', (persistence: boolean) => { }; const finalData = { desc: 'Description', owner: 'Sebastian' }; return withTestDocAndInitialData(persistence, initialData, async docRef => { - await docRef.set( + await setDoc( + docRef, { desc: 'NewDescription', owner: 'Sebastian' }, { mergeFields: ['owner'] } ); - const result = await docRef.get(); + const result = await getDoc(docRef); expect(result.data()).to.deep.equal(finalData); }); }); @@ -392,11 +424,12 @@ apiDescribe('Database', (persistence: boolean) => { }; const finalData = { desc: 'Description', owner: 'Sebastian' }; return withTestDocAndInitialData(persistence, initialData, async docRef => { - await docRef.set( - { desc: FieldValue.delete(), owner: 'Sebastian' }, + await setDoc( + docRef, + { desc: deleteField(), owner: 'Sebastian' }, { mergeFields: ['owner'] } ); - const result = await docRef.get(); + const result = await getDoc(docRef); expect(result.data()).to.deep.equal(finalData); }); }); @@ -408,14 +441,15 @@ apiDescribe('Database', (persistence: boolean) => { }; const finalData = { desc: 'Description', owner: 'Sebastian' }; return withTestDocAndInitialData(persistence, initialData, async docRef => { - await docRef.set( + await setDoc( + docRef, { - desc: FieldValue.serverTimestamp(), + desc: serverTimestamp(), owner: 'Sebastian' }, { mergeFields: ['owner'] } ); - const result = await docRef.get(); + const result = await getDoc(docRef); expect(result.data()).to.deep.equal(finalData); }); }); @@ -427,11 +461,12 @@ apiDescribe('Database', (persistence: boolean) => { }; const finalData = initialData; return withTestDocAndInitialData(persistence, initialData, async docRef => { - await docRef.set( + await setDoc( + docRef, { desc: 'NewDescription', owner: 'Sebastian' }, { mergeFields: [] } ); - const result = await docRef.get(); + const result = await getDoc(docRef); expect(result.data()).to.deep.equal(finalData); }); }); @@ -446,22 +481,22 @@ apiDescribe('Database', (persistence: boolean) => { owner: { name: 'Sebastian', email: 'new@xyz.com' } }; return withTestDocAndInitialData(persistence, initialData, async docRef => { - await docRef.set( + await setDoc( + docRef, { desc: 'NewDescription', owner: { name: 'Sebastian', email: 'new@xyz.com' } }, { mergeFields: ['owner.name', 'owner', 'owner'] } ); - const result = await docRef.get(); + const result = await getDoc(docRef); expect(result.data()).to.deep.equal(finalData); }); }); it('cannot update nonexistent document', () => { return withTestDoc(persistence, doc => { - return doc - .update({ owner: 'abc' }) + return updateDoc(doc, { owner: 'abc' }) .then( () => Promise.reject('update should have failed.'), err => { @@ -469,9 +504,9 @@ apiDescribe('Database', (persistence: boolean) => { expect(err.code).to.equal('not-found'); } ) - .then(() => doc.get()) + .then(() => getDoc(doc)) .then(docSnapshot => { - expect(docSnapshot.exists).to.equal(false); + expect(docSnapshot.exists()).to.equal(false); }); }); }); @@ -483,16 +518,15 @@ apiDescribe('Database', (persistence: boolean) => { owner: { name: 'Jonny', email: 'abc@xyz.com' } }; const updateData = { - 'owner.email': FieldValue.delete() + 'owner.email': deleteField() }; const finalData = { desc: 'Description', owner: { name: 'Jonny' } }; - return doc - .set(initialData) - .then(() => doc.update(updateData)) - .then(() => doc.get()) + return setDoc(doc, initialData) + .then(() => updateDoc(doc, updateData)) + .then(() => getDoc(doc)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.data()).to.deep.equal(finalData); @@ -512,12 +546,17 @@ apiDescribe('Database', (persistence: boolean) => { owner: { name: 'Sebastian' }, 'is.admin': true }; - return doc - .set(initialData) + return setDoc(doc, initialData) .then(() => - doc.update('owner.name', 'Sebastian', new FieldPath('is.admin'), true) + updateDoc( + doc, + 'owner.name', + 'Sebastian', + new FieldPath('is.admin'), + true + ) ) - .then(() => doc.get()) + .then(() => getDoc(doc)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.data()).to.deep.equal(finalData); @@ -527,10 +566,9 @@ apiDescribe('Database', (persistence: boolean) => { it('can specify updated field multiple times', () => { return withTestDoc(persistence, doc => { - return doc - .set({}) - .then(() => doc.update('field', 100, new FieldPath('field'), 200)) - .then(() => doc.get()) + return setDoc(doc, {}) + .then(() => updateDoc(doc, 'field', 100, new FieldPath('field'), 200)) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.data()).to.deep.equal({ field: 200 }); }); @@ -544,10 +582,10 @@ apiDescribe('Database', (persistence: boolean) => { return withTestDoc(persistence, async doc => { // Intentionally passing bad types. // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(() => doc.set(val as any)).to.throw(); + expect(() => setDoc(doc, val as any)).to.throw(); // Intentionally passing bad types. // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(() => doc.update(val as any)).to.throw(); + expect(() => updateDoc(doc, val as any)).to.throw(); }); }); } @@ -555,9 +593,8 @@ apiDescribe('Database', (persistence: boolean) => { it('CollectionRef.add() resolves with resulting DocumentRef.', () => { return withTestCollection(persistence, {}, coll => { - return coll - .add({ foo: 1 }) - .then(docRef => docRef.get()) + return addDoc(coll, { foo: 1 }) + .then(docRef => getDoc(docRef)) .then(docSnap => { expect(docSnap.data()).to.deep.equal({ foo: 1 }); }); @@ -568,12 +605,12 @@ apiDescribe('Database', (persistence: boolean) => { const testDocs = { a: { foo: 1 } }; - return withTestCollection(persistence, testDocs, async coll => { + return withTestCollection(persistence, testDocs, async (coll, db) => { let events: string[] = []; const gotInitialSnapshot = new Deferred(); - const doc = coll.doc('a'); + const docA = doc(coll, 'a'); - doc.onSnapshot(snap => { + onSnapshot(docA, snap => { events.push('doc'); gotInitialSnapshot.resolve(); }); @@ -581,7 +618,7 @@ apiDescribe('Database', (persistence: boolean) => { events = []; const done = new Deferred(); - doc.firestore.onSnapshotsInSync(() => { + onSnapshotsInSync(db, () => { events.push('snapshots-in-sync'); if (events.length === 3) { // We should have an initial snapshots-in-sync event, then a snapshot @@ -596,7 +633,7 @@ apiDescribe('Database', (persistence: boolean) => { } }); - await doc.set({ foo: 3 }); + await setDoc(docA, { foo: 3 }); await done.promise; }); }); @@ -607,7 +644,7 @@ apiDescribe('Database', (persistence: boolean) => { it('same inequality fields works', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.where('x', '>=', 32).where('x', '<=', 'cat') + query(coll, where('x', '>=', 32), where('x', '<=', 'cat')) ).not.to.throw(); }); }); @@ -615,7 +652,7 @@ apiDescribe('Database', (persistence: boolean) => { it('inequality and equality on different fields works', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.where('x', '>=', 32).where('y', '==', 'cat') + query(coll, where('x', '>=', 32), where('y', '==', 'cat')) ).not.to.throw(); }); }); @@ -623,7 +660,7 @@ apiDescribe('Database', (persistence: boolean) => { it('inequality and array-contains on different fields works', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.where('x', '>=', 32).where('y', 'array-contains', 'cat') + query(coll, where('x', '>=', 32), where('y', 'array-contains', 'cat')) ).not.to.throw(); }); }); @@ -631,7 +668,7 @@ apiDescribe('Database', (persistence: boolean) => { it('inequality and IN on different fields works', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.where('x', '>=', 32).where('y', 'in', [1, 2]) + query(coll, where('x', '>=', 32), where('y', 'in', [1, 2])) ).not.to.throw(); }); }); @@ -639,32 +676,44 @@ apiDescribe('Database', (persistence: boolean) => { it('inequality and array-contains-any on different fields works', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.where('x', '>=', 32).where('y', 'array-contains-any', [1, 2]) + query( + coll, + where('x', '>=', 32), + where('y', 'array-contains-any', [1, 2]) + ) ).not.to.throw(); }); }); it('inequality same as orderBy works.', () => { return withTestCollection(persistence, {}, async coll => { - expect(() => coll.where('x', '>', 32).orderBy('x')).not.to.throw(); - expect(() => coll.orderBy('x').where('x', '>', 32)).not.to.throw(); + expect(() => + query(coll, where('x', '>', 32), orderBy('x')) + ).not.to.throw(); + expect(() => + query(coll, orderBy('x'), where('x', '>', 32)) + ).not.to.throw(); }); }); it('!= same as orderBy works.', () => { return withTestCollection(persistence, {}, async coll => { - expect(() => coll.where('x', '!=', 32).orderBy('x')).not.to.throw(); - expect(() => coll.orderBy('x').where('x', '!=', 32)).not.to.throw(); + expect(() => + query(coll, where('x', '!=', 32), orderBy('x')) + ).not.to.throw(); + expect(() => + query(coll, orderBy('x'), where('x', '!=', 32)) + ).not.to.throw(); }); }); it('inequality same as first orderBy works.', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.where('x', '>', 32).orderBy('x').orderBy('y') + query(coll, where('x', '>', 32), orderBy('x'), orderBy('y')) ).not.to.throw(); expect(() => - coll.orderBy('x').where('x', '>', 32).orderBy('y') + query(coll, orderBy('x'), where('x', '>', 32), orderBy('y')) ).not.to.throw(); }); }); @@ -672,38 +721,42 @@ apiDescribe('Database', (persistence: boolean) => { it('!= same as first orderBy works.', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.where('x', '!=', 32).orderBy('x').orderBy('y') + query(coll, where('x', '!=', 32), orderBy('x'), orderBy('y')) ).not.to.throw(); expect(() => - coll.orderBy('x').where('x', '!=', 32).orderBy('y') + query(coll, orderBy('x'), where('x', '!=', 32), orderBy('y')) ).not.to.throw(); }); }); it('equality different than orderBy works', () => { return withTestCollection(persistence, {}, async coll => { - expect(() => coll.orderBy('x').where('y', '==', 'cat')).not.to.throw(); + expect(() => + query(coll, orderBy('x'), where('y', '==', 'cat')) + ).not.to.throw(); }); }); it('array-contains different than orderBy works', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.orderBy('x').where('y', 'array-contains', 'cat') + query(coll, orderBy('x'), where('y', 'array-contains', 'cat')) ).not.to.throw(); }); }); it('IN different than orderBy works', () => { return withTestCollection(persistence, {}, async coll => { - expect(() => coll.orderBy('x').where('y', 'in', [1, 2])).not.to.throw(); + expect(() => + query(coll, orderBy('x'), where('y', 'in', [1, 2])) + ).not.to.throw(); }); }); it('array-contains-any different than orderBy works', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.orderBy('x').where('y', 'array-contains-any', [1, 2]) + query(coll, orderBy('x'), where('y', 'array-contains-any', [1, 2])) ).not.to.throw(); }); }); @@ -711,11 +764,11 @@ apiDescribe('Database', (persistence: boolean) => { it('DocumentSnapshot events for non existent document', () => { return withTestCollection(persistence, {}, col => { - const doc = col.doc(); - const storeEvent = new EventsAccumulator(); - doc.onSnapshot(storeEvent.storeEvent); + const docA = doc(col); + const storeEvent = new EventsAccumulator(); + onSnapshot(docA, storeEvent.storeEvent); return storeEvent.awaitEvent().then(snap => { - expect(snap.exists).to.be.false; + expect(snap.exists()).to.be.false; expect(snap.data()).to.equal(undefined); return storeEvent.assertNoAdditionalEvents(); }); @@ -724,25 +777,25 @@ apiDescribe('Database', (persistence: boolean) => { it('DocumentSnapshot events for add data to document', () => { return withTestCollection(persistence, {}, col => { - const doc = col.doc(); - const storeEvent = new EventsAccumulator(); - doc.onSnapshot({ includeMetadataChanges: true }, storeEvent.storeEvent); + const docA = doc(col); + const storeEvent = new EventsAccumulator(); + onSnapshot(docA, { includeMetadataChanges: true }, storeEvent.storeEvent); return storeEvent .awaitEvent() .then(snap => { - expect(snap.exists).to.be.false; + expect(snap.exists()).to.be.false; expect(snap.data()).to.equal(undefined); }) - .then(() => doc.set({ a: 1 })) + .then(() => setDoc(docA, { a: 1 })) .then(() => storeEvent.awaitEvent()) .then(snap => { - expect(snap.exists).to.be.true; + expect(snap.exists()).to.be.true; expect(snap.data()).to.deep.equal({ a: 1 }); expect(snap.metadata.hasPendingWrites).to.be.true; }) .then(() => storeEvent.awaitEvent()) .then(snap => { - expect(snap.exists).to.be.true; + expect(snap.exists()).to.be.true; expect(snap.data()).to.deep.equal({ a: 1 }); expect(snap.metadata.hasPendingWrites).to.be.false; }) @@ -755,16 +808,16 @@ apiDescribe('Database', (persistence: boolean) => { const changedData = { b: 2 }; return withTestCollection(persistence, { key1: initialData }, col => { - const doc = col.doc('key1'); - const storeEvent = new EventsAccumulator(); - doc.onSnapshot({ includeMetadataChanges: true }, storeEvent.storeEvent); + const doc1 = doc(col, 'key1'); + const storeEvent = new EventsAccumulator(); + onSnapshot(doc1, { includeMetadataChanges: true }, storeEvent.storeEvent); return storeEvent .awaitEvent() .then(snap => { expect(snap.data()).to.deep.equal(initialData); expect(snap.metadata.hasPendingWrites).to.be.false; }) - .then(() => doc.set(changedData)) + .then(() => setDoc(doc1, changedData)) .then(() => storeEvent.awaitEvent()) .then(snap => { expect(snap.data()).to.deep.equal(changedData); @@ -783,20 +836,20 @@ apiDescribe('Database', (persistence: boolean) => { const initialData = { a: 1 }; return withTestCollection(persistence, { key1: initialData }, col => { - const doc = col.doc('key1'); - const storeEvent = new EventsAccumulator(); - doc.onSnapshot({ includeMetadataChanges: true }, storeEvent.storeEvent); + const doc1 = doc(col, 'key1'); + const storeEvent = new EventsAccumulator(); + onSnapshot(doc1, { includeMetadataChanges: true }, storeEvent.storeEvent); return storeEvent .awaitEvent() .then(snap => { - expect(snap.exists).to.be.true; + expect(snap.exists()).to.be.true; expect(snap.data()).to.deep.equal(initialData); expect(snap.metadata.hasPendingWrites).to.be.false; }) - .then(() => doc.delete()) + .then(() => deleteDoc(doc1)) .then(() => storeEvent.awaitEvent()) .then(snap => { - expect(snap.exists).to.be.false; + expect(snap.exists()).to.be.false; expect(snap.data()).to.equal(undefined); expect(snap.metadata.hasPendingWrites).to.be.false; }) @@ -806,14 +859,14 @@ apiDescribe('Database', (persistence: boolean) => { it('Listen can be called multiple times', () => { return withTestCollection(persistence, {}, coll => { - const doc = coll.doc(); + const docA = doc(coll); const deferred1 = new Deferred(); const deferred2 = new Deferred(); // eslint-disable-next-line @typescript-eslint/no-floating-promises - doc.set({ foo: 'bar' }).then(() => { - doc.onSnapshot(snap => { + setDoc(docA, { foo: 'bar' }).then(() => { + onSnapshot(docA, snap => { deferred1.resolve(); - doc.onSnapshot(snap => { + onSnapshot(docA, snap => { deferred2.resolve(); }); }); @@ -826,7 +879,7 @@ apiDescribe('Database', (persistence: boolean) => { return withTestDoc(persistence, docRef => { const secondUpdateFound = new Deferred(); let count = 0; - const unlisten = docRef.onSnapshot(doc => { + const unlisten = onSnapshot(docRef, doc => { if (doc) { count++; if (count === 1) { @@ -838,9 +891,9 @@ apiDescribe('Database', (persistence: boolean) => { } }); // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.set({ a: 1 }).then(() => { + setDoc(docRef, { a: 1 }).then(() => { // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.set({ b: 1 }); + setDoc(docRef, { b: 1 }); }); return secondUpdateFound.promise.then(() => { unlisten(); @@ -856,8 +909,9 @@ apiDescribe('Database', (persistence: boolean) => { it('will reject listens', () => { return withTestDb(persistence, async db => { const deferred = new Deferred(); - const queryForRejection = db.collection('a/__badpath__/b'); - queryForRejection.onSnapshot( + const queryForRejection = collection(db, 'a/__badpath__/b'); + onSnapshot( + queryForRejection, () => {}, (err: Error) => { expect(err.name).to.exist; @@ -872,13 +926,15 @@ apiDescribe('Database', (persistence: boolean) => { it('will reject same listens twice in a row', () => { return withTestDb(persistence, async db => { const deferred = new Deferred(); - const queryForRejection = db.collection('a/__badpath__/b'); - queryForRejection.onSnapshot( + const queryForRejection = collection(db, 'a/__badpath__/b'); + onSnapshot( + queryForRejection, () => {}, (err: Error) => { expect(err.name).to.exist; expect(err.message).to.exist; - queryForRejection.onSnapshot( + onSnapshot( + queryForRejection, () => {}, (err2: Error) => { expect(err2.name).to.exist; @@ -894,8 +950,8 @@ apiDescribe('Database', (persistence: boolean) => { it('will reject gets', () => { return withTestDb(persistence, async db => { - const queryForRejection = db.collection('a/__badpath__/b'); - await queryForRejection.get().then( + const queryForRejection = collection(db, 'a/__badpath__/b'); + await getDocs(queryForRejection).then( () => { expect.fail('Promise resolved even though error was expected.'); }, @@ -909,9 +965,8 @@ apiDescribe('Database', (persistence: boolean) => { it('will reject gets twice in a row', () => { return withTestDb(persistence, async db => { - const queryForRejection = db.collection('a/__badpath__/b'); - return queryForRejection - .get() + const queryForRejection = collection(db, 'a/__badpath__/b'); + return getDocs(queryForRejection) .then( () => { expect.fail('Promise resolved even though error was expected.'); @@ -921,7 +976,7 @@ apiDescribe('Database', (persistence: boolean) => { expect(err.message).to.exist; } ) - .then(() => queryForRejection.get()) + .then(() => getDocs(queryForRejection)) .then( () => { expect.fail('Promise resolved even though error was expected.'); @@ -937,26 +992,26 @@ apiDescribe('Database', (persistence: boolean) => { it('exposes "firestore" on document references.', () => { return withTestDb(persistence, async db => { - expect(db.doc('foo/bar').firestore).to.equal(db); + expect(doc(db, 'foo/bar').firestore).to.equal(db); }); }); it('exposes "firestore" on query references.', () => { return withTestDb(persistence, async db => { - expect(db.collection('foo').limit(5).firestore).to.equal(db); + expect(query(collection(db, 'foo'), limit(5)).firestore).to.equal(db); }); }); it('can compare DocumentReference instances with isEqual().', () => { return withTestDb(persistence, firestore => { return withTestDb(persistence, async otherFirestore => { - const docRef = firestore.doc('foo/bar'); - expect(docRef.isEqual(firestore.doc('foo/bar'))).to.be.true; - expect(docRef.collection('baz').parent!.isEqual(docRef)).to.be.true; + const docRef = doc(firestore, 'foo/bar'); + expect(refEqual(docRef, doc(firestore, 'foo/bar'))).to.be.true; + expect(refEqual(collection(docRef, 'baz').parent!, docRef)).to.be.true; - expect(firestore.doc('foo/BAR').isEqual(docRef)).to.be.false; + expect(refEqual(doc(firestore, 'foo/BAR'), docRef)).to.be.false; - expect(otherFirestore.doc('foo/bar').isEqual(docRef)).to.be.false; + expect(refEqual(doc(otherFirestore, 'foo/bar'), docRef)).to.be.false; }); }); }); @@ -964,27 +1019,31 @@ apiDescribe('Database', (persistence: boolean) => { it('can compare Query instances with isEqual().', () => { return withTestDb(persistence, firestore => { return withTestDb(persistence, async otherFirestore => { - const query = firestore - .collection('foo') - .orderBy('bar') - .where('baz', '==', 42); - const query2 = firestore - .collection('foo') - .orderBy('bar') - .where('baz', '==', 42); - expect(query.isEqual(query2)).to.be.true; - - const query3 = firestore - .collection('foo') - .orderBy('BAR') - .where('baz', '==', 42); - expect(query.isEqual(query3)).to.be.false; - - const query4 = otherFirestore - .collection('foo') - .orderBy('bar') - .where('baz', '==', 42); - expect(query4.isEqual(query)).to.be.false; + const query1 = query( + collection(firestore, 'foo'), + orderBy('bar'), + where('baz', '==', 42) + ); + const query2 = query( + collection(firestore, 'foo'), + orderBy('bar'), + where('baz', '==', 42) + ); + expect(queryEqual(query1, query2)).to.be.true; + + const query3 = query( + collection(firestore, 'foo'), + orderBy('BAR'), + where('baz', '==', 42) + ); + expect(queryEqual(query1, query3)).to.be.false; + + const query4 = query( + collection(otherFirestore, 'foo'), + orderBy('bar'), + where('baz', '==', 42) + ); + expect(queryEqual(query4, query1)).to.be.false; }); }); }); @@ -993,13 +1052,13 @@ apiDescribe('Database', (persistence: boolean) => { return withTestDb(persistence, async db => { const expected = 'a/b/c/d'; // doc path from root Firestore. - expect(db.doc('a/b/c/d').path).to.deep.equal(expected); + expect(doc(db, 'a/b/c/d').path).to.deep.equal(expected); // collection path from root Firestore. - expect(db.collection('a/b/c').doc('d').path).to.deep.equal(expected); + expect(doc(collection(db, 'a/b/c'), 'd').path).to.deep.equal(expected); // doc path from CollectionReference. - expect(db.collection('a').doc('b/c/d').path).to.deep.equal(expected); + expect(doc(collection(db, 'a'), 'b/c/d').path).to.deep.equal(expected); // collection path from DocumentReference. - expect(db.doc('a/b').collection('c/d/e').path).to.deep.equal( + expect(collection(doc(db, 'a/b'), 'c/d/e').path).to.deep.equal( expected + '/e' ); }); @@ -1007,33 +1066,30 @@ apiDescribe('Database', (persistence: boolean) => { it('can traverse collection and document parents.', () => { return withTestDb(persistence, async db => { - let collection = db.collection('a/b/c'); - expect(collection.path).to.deep.equal('a/b/c'); + let coll = collection(db, 'a/b/c'); + expect(coll.path).to.deep.equal('a/b/c'); - const doc = collection.parent!; + const doc = coll.parent!; expect(doc.path).to.deep.equal('a/b'); - collection = doc.parent; - expect(collection.path).to.equal('a'); + coll = doc.parent; + expect(coll.path).to.equal('a'); - const nullDoc = collection.parent; + const nullDoc = coll.parent; expect(nullDoc).to.equal(null); }); }); it('can queue writes while offline', () => { - return withTestDoc(persistence, docRef => { - const firestore = docRef.firestore; - - return firestore - .disableNetwork() + return withTestDoc(persistence, (docRef, firestore) => { + return disableNetwork(firestore) .then(() => { return Promise.all([ - docRef.set({ foo: 'bar' }), - firestore.enableNetwork() + setDoc(docRef, { foo: 'bar' }), + enableNetwork(firestore) ]); }) - .then(() => docRef.get()) + .then(() => getDoc(docRef)) .then(doc => { expect(doc.data()).to.deep.equal({ foo: 'bar' }); }); @@ -1042,41 +1098,39 @@ apiDescribe('Database', (persistence: boolean) => { // eslint-disable-next-line no-restricted-properties (persistence ? it : it.skip)('offline writes are sent after restart', () => { - return withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; - + return withTestDoc(persistence, async (docRef, firestore) => { const app = firestore.app; const name = app.name; const options = app.options; - await firestore.disableNetwork(); + await disableNetwork(firestore); // We are merely adding to the cache. // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.set({ foo: 'bar' }); + setDoc(docRef, { foo: 'bar' }); - await app.delete(); + await deleteApp(app); const firestore2 = newTestFirestore( - options.projectId, + options.projectId!, name, DEFAULT_SETTINGS ); - await firestore2.enablePersistence(); - await firestore2.waitForPendingWrites(); - const doc = await firestore2.doc(docRef.path).get(); + await enableIndexedDbPersistence(firestore2); + await waitForPendingWrites(firestore2); + const doc2 = await getDoc(doc(firestore2, docRef.path)); - expect(doc.exists).to.be.true; - expect(doc.metadata.hasPendingWrites).to.be.false; + expect(doc2.exists).to.be.true; + expect(doc2.metadata.hasPendingWrites).to.be.false; }); }); it('rejects subsequent method calls after terminate() is called', async () => { return withTestDb(persistence, db => { - return db.INTERNAL.delete().then(() => { + return terminate(db).then(() => { expect(() => { // eslint-disable-next-line @typescript-eslint/no-floating-promises - db.disableNetwork(); + disableNetwork(db); }).to.throw('The client has already been terminated.'); }); }); @@ -1084,8 +1138,8 @@ apiDescribe('Database', (persistence: boolean) => { it('can call terminate() multiple times', async () => { return withTestDb(persistence, async db => { - await db.terminate(); - await db.terminate(); + await terminate(db); + await terminate(db); }); }); @@ -1094,17 +1148,17 @@ apiDescribe('Database', (persistence: boolean) => { 'maintains persistence after restarting app', async () => { await withTestDoc(persistence, async docRef => { - await docRef.set({ foo: 'bar' }); + await setDoc(docRef, { foo: 'bar' }); const app = docRef.firestore.app; const name = app.name; const options = app.options; - await app.delete(); + await deleteApp(app); - const firestore2 = newTestFirestore(options.projectId, name); - await firestore2.enablePersistence(); - const docRef2 = firestore2.doc(docRef.path); - const docSnap2 = await docRef2.get({ source: 'cache' }); + const firestore2 = newTestFirestore(options.projectId!, name); + await enableIndexedDbPersistence(firestore2); + const docRef2 = doc(firestore2, docRef.path); + const docSnap2 = await getDocFromCache(docRef2); expect(docSnap2.exists).to.be.true; }); } @@ -1114,21 +1168,20 @@ apiDescribe('Database', (persistence: boolean) => { (persistence ? it : it.skip)( 'can clear persistence if the client has been terminated', async () => { - await withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; - await docRef.set({ foo: 'bar' }); + await withTestDoc(persistence, async (docRef, firestore) => { + await setDoc(docRef, { foo: 'bar' }); const app = docRef.firestore.app; const name = app.name; const options = app.options; - await app.delete(); - await firestore.clearPersistence(); - const firestore2 = newTestFirestore(options.projectId, name); - await firestore2.enablePersistence(); - const docRef2 = firestore2.doc(docRef.path); - await expect( - docRef2.get({ source: 'cache' }) - ).to.eventually.be.rejectedWith('Failed to get document from cache.'); + await deleteApp(app); + await clearIndexedDbPersistence(firestore); + const firestore2 = newTestFirestore(options.projectId!, name); + await enableIndexedDbPersistence(firestore2); + const docRef2 = doc(firestore2, docRef.path); + await expect(getDocFromCache(docRef2)).to.eventually.be.rejectedWith( + 'Failed to get document from cache.' + ); }); } ); @@ -1138,19 +1191,19 @@ apiDescribe('Database', (persistence: boolean) => { 'can clear persistence if the client has not been initialized', async () => { await withTestDoc(persistence, async docRef => { - await docRef.set({ foo: 'bar' }); + await setDoc(docRef, { foo: 'bar' }); const app = docRef.firestore.app; const name = app.name; const options = app.options; - await app.delete(); - const firestore2 = newTestFirestore(options.projectId, name); - await firestore2.clearPersistence(); - await firestore2.enablePersistence(); - const docRef2 = firestore2.doc(docRef.path); - await expect( - docRef2.get({ source: 'cache' }) - ).to.eventually.be.rejectedWith('Failed to get document from cache.'); + await deleteApp(app); + const firestore2 = newTestFirestore(options.projectId!, name); + await clearIndexedDbPersistence(firestore2); + await enableIndexedDbPersistence(firestore2); + const docRef2 = doc(firestore2, docRef.path); + await expect(getDocFromCache(docRef2)).to.eventually.be.rejectedWith( + 'Failed to get document from cache.' + ); }); } ); @@ -1159,33 +1212,32 @@ apiDescribe('Database', (persistence: boolean) => { (persistence ? it : it.skip)( 'cannot clear persistence if the client has been initialized', async () => { - await withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; + await withTestDoc(persistence, async (docRef, firestore) => { const expectedError = 'Persistence can only be cleared before a Firestore instance is ' + 'initialized or after it is terminated.'; - expect(() => firestore.clearPersistence()).to.throw(expectedError); + expect(() => clearIndexedDbPersistence(firestore)).to.throw( + expectedError + ); }); } ); it('can get documents while offline', async () => { - await withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; - - await firestore.disableNetwork(); - await expect(docRef.get()).to.eventually.be.rejectedWith( + await withTestDoc(persistence, async (docRef, firestore) => { + await disableNetwork(firestore); + await expect(getDoc(docRef)).to.eventually.be.rejectedWith( 'Failed to get document because the client is offline.' ); - const writePromise = docRef.set({ foo: 'bar' }); - const doc = await docRef.get(); + const writePromise = setDoc(docRef, { foo: 'bar' }); + const doc = await getDoc(docRef); expect(doc.metadata.fromCache).to.be.true; - await firestore.enableNetwork(); + await enableNetwork(firestore); await writePromise; - const doc2 = await docRef.get(); + const doc2 = await getDoc(docRef); expect(doc2.metadata.fromCache).to.be.false; expect(doc2.data()).to.deep.equal({ foo: 'bar' }); }); @@ -1195,65 +1247,59 @@ apiDescribe('Database', (persistence: boolean) => { return withTestDb(persistence, async db => { // There's not currently a way to check if networking is in fact disabled, // so for now just test that the method is well-behaved and doesn't throw. - await db.enableNetwork(); - await db.enableNetwork(); - await db.disableNetwork(); - await db.disableNetwork(); - await db.enableNetwork(); + await enableNetwork(db); + await enableNetwork(db); + await disableNetwork(db); + await disableNetwork(db); + await enableNetwork(db); }); }); it('can start a new instance after shut down', async () => { - return withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; - await firestore.terminate(); + return withTestDoc(persistence, async (docRef, firestore) => { + const app = firestore.app; + await terminate(firestore); - const newFirestore = newTestFirestore( - firestore.app.options.projectId, - firestore.app - ); + const newFirestore = initializeFirestore(app, DEFAULT_SETTINGS); expect(newFirestore).to.not.equal(firestore); // New instance functions. - newFirestore.settings(DEFAULT_SETTINGS); - await newFirestore.doc(docRef.path).set({ foo: 'bar' }); - const doc = await newFirestore.doc(docRef.path).get(); - expect(doc.data()).to.deep.equal({ foo: 'bar' }); + const docRef2 = doc(newFirestore, docRef.path); + await setDoc(docRef2, { foo: 'bar' }); + const docSnap = await getDoc(docRef2); + expect(docSnap.data()).to.deep.equal({ foo: 'bar' }); }); }); it('new operation after termination should throw', async () => { - await withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; - await firestore.terminate(); + await withTestDoc(persistence, async (docRef, firestore) => { + await terminate(firestore); expect(() => { // eslint-disable-next-line @typescript-eslint/no-floating-promises - firestore.doc(docRef.path).set({ foo: 'bar' }); + setDoc(doc(firestore, docRef.path), { foo: 'bar' }); }).to.throw('The client has already been terminated.'); }); }); it('calling terminate multiple times should proceed', async () => { - await withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; - await firestore.terminate(); - await firestore.terminate(); + await withTestDoc(persistence, async (docRef, firestore) => { + await terminate(firestore); + await terminate(firestore); expect(() => { // eslint-disable-next-line @typescript-eslint/no-floating-promises - firestore.doc(docRef.path).set({ foo: 'bar' }); + setDoc(doc(firestore, docRef.path), { foo: 'bar' }); }).to.throw(); }); }); it('can unlisten queries after termination', async () => { - return withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; - const accumulator = new EventsAccumulator(); - const unsubscribe = docRef.onSnapshot(accumulator.storeEvent); + return withTestDoc(persistence, async (docRef, firestore) => { + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot(docRef, accumulator.storeEvent); await accumulator.awaitEvent(); - await firestore.terminate(); + await terminate(firestore); // This should proceed without error. unsubscribe(); @@ -1263,30 +1309,28 @@ apiDescribe('Database', (persistence: boolean) => { }); it('can wait for pending writes', async () => { - await withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; + await withTestDoc(persistence, async (docRef, firestore) => { // Prevent pending writes receiving acknowledgement. - await firestore.disableNetwork(); + await disableNetwork(firestore); - const pendingWrites = docRef.set({ foo: 'bar' }); - const awaitPendingWrites = firestore.waitForPendingWrites(); + const pendingWrites = setDoc(docRef, { foo: 'bar' }); + const awaitPendingWrites = waitForPendingWrites(firestore); // pending writes can receive acknowledgements now. - await firestore.enableNetwork(); + await enableNetwork(firestore); await pendingWrites; await awaitPendingWrites; }); }); it('waiting for pending writes resolves immediately when offline and no pending writes', async () => { - await withTestDoc(persistence, async docRef => { - const firestore = docRef.firestore; + await withTestDoc(persistence, async (docRef, firestore) => { // Prevent pending writes receiving acknowledgement. - await firestore.disableNetwork(); + await disableNetwork(firestore); // `awaitsPendingWrites` is created when there is no pending writes, it will resolve // immediately even if we are offline. - await firestore.waitForPendingWrites(); + await waitForPendingWrites(firestore); }); }); @@ -1297,7 +1341,7 @@ apiDescribe('Database', (persistence: boolean) => { constructor( readonly title: string, readonly author: string, - readonly ref: firestore.DocumentReference | null = null + readonly ref: DocumentReference | null = null ) {} byline(): string { return this.title + ', by ' + this.author; @@ -1305,30 +1349,27 @@ apiDescribe('Database', (persistence: boolean) => { } const postConverter = { - toFirestore(post: Post): firestore.DocumentData { + toFirestore(post: Post): DocumentData { return { title: post.title, author: post.author }; }, - fromFirestore( - snapshot: firestore.QueryDocumentSnapshot, - options: firestore.SnapshotOptions - ): Post { + fromFirestore(snapshot: QueryDocumentSnapshot): Post { expect(snapshot).to.be.an.instanceof(QueryDocumentSnapshot); - const data = snapshot.data(options); + const data = snapshot.data(); return new Post(data.title, data.author, snapshot.ref); } }; const postConverterMerge = { toFirestore( - post: Partial, - options?: firestore.SetOptions - ): firestore.DocumentData { - if (options && (options.merge || options.mergeFields)) { + post: WithFieldValue, + options?: SetOptions + ): DocumentData { + if (options && ('merge' in options || 'mergeFields' in options)) { expect(post).to.not.be.an.instanceof(Post); } else { expect(post).to.be.an.instanceof(Post); } - const result: firestore.DocumentData = {}; + const result: DocumentData = {}; if (post.title) { result.title = post.title; } @@ -1337,10 +1378,7 @@ apiDescribe('Database', (persistence: boolean) => { } return result; }, - fromFirestore( - snapshot: firestore.QueryDocumentSnapshot, - options: firestore.SnapshotOptions - ): Post { + fromFirestore(snapshot: QueryDocumentSnapshot): Post { const data = snapshot.data(); return new Post(data.title, data.author, snapshot.ref); } @@ -1348,13 +1386,12 @@ apiDescribe('Database', (persistence: boolean) => { it('for DocumentReference.withConverter()', () => { return withTestDb(persistence, async db => { - const docRef = db - .collection('posts') - .doc() - .withConverter(postConverter); + const docRef = doc(collection(db, 'posts')).withConverter( + postConverter + ); - await docRef.set(new Post('post', 'author')); - const postData = await docRef.get(); + await setDoc(docRef, new Post('post', 'author')); + const postData = await getDoc(docRef); const post = postData.data(); expect(post).to.not.equal(undefined); expect(post!.byline()).to.equal('post, by author'); @@ -1363,22 +1400,20 @@ apiDescribe('Database', (persistence: boolean) => { it('for DocumentReference.withConverter(null) ', () => { return withTestDb(persistence, async db => { - const docRef = db - .collection('posts') - .doc() + const docRef = doc(collection(db, 'posts')) .withConverter(postConverter) .withConverter(null); - expect(() => docRef.set(new Post('post', 'author'))).to.throw(); + expect(() => setDoc(docRef, new Post('post', 'author'))).to.throw(); }); }); it('for CollectionReference.withConverter()', () => { return withTestDb(persistence, async db => { - const coll = db.collection('posts').withConverter(postConverter); + const coll = collection(db, 'posts').withConverter(postConverter); - const docRef = await coll.add(new Post('post', 'author')); - const postData = await docRef.get(); + const docRef = await addDoc(coll, new Post('post', 'author')); + const postData = await getDoc(docRef); const post = postData.data(); expect(post).to.not.equal(undefined); expect(post!.byline()).to.equal('post, by author'); @@ -1387,27 +1422,27 @@ apiDescribe('Database', (persistence: boolean) => { it('for CollectionReference.withConverter(null)', () => { return withTestDb(persistence, async db => { - const coll = db - .collection('posts') + const coll = collection(db, 'posts') .withConverter(postConverter) .withConverter(null); - expect(() => coll.add(new Post('post', 'author'))).to.throw(); + expect(() => addDoc(coll, new Post('post', 'author'))).to.throw(); }); }); it('for Query.withConverter()', () => { return withTestDb(persistence, async db => { - await db - .doc('postings/post1') - .set({ title: 'post1', author: 'author1' }); - await db - .doc('postings/post2') - .set({ title: 'post2', author: 'author2' }); - const posts = await db - .collectionGroup('postings') - .withConverter(postConverter) - .get(); + await setDoc(doc(db, 'postings/post1'), { + title: 'post1', + author: 'author1' + }); + await setDoc(doc(db, 'postings/post2'), { + title: 'post2', + author: 'author2' + }); + const posts = await getDocs( + collectionGroup(db, 'postings').withConverter(postConverter) + ); expect(posts.size).to.equal(2); expect(posts.docs[0].data()!.byline()).to.equal('post1, by author1'); }); @@ -1415,26 +1450,24 @@ apiDescribe('Database', (persistence: boolean) => { it('for Query.withConverter(null)', () => { return withTestDb(persistence, async db => { - await db - .doc('postings/post1') - .set({ title: 'post1', author: 'author1' }); - const posts = await db - .collectionGroup('postings') - .withConverter(postConverter) - .withConverter(null) - .get(); + await setDoc(doc(db, 'postings/post1'), { + title: 'post1', + author: 'author1' + }); + const posts = await getDocs( + collectionGroup(db, 'postings') + .withConverter(postConverter) + .withConverter(null) + ); expect(posts.docs[0].data()).to.not.be.an.instanceof(Post); }); }); it('requires the correct converter for Partial usage', async () => { return withTestDb(persistence, async db => { - const ref = db - .collection('posts') - .doc('some-post') - .withConverter(postConverter); - await ref.set(new Post('walnut', 'author')); - const batch = db.batch(); + const ref = doc(db, 'posts', 'some-post').withConverter(postConverter); + await setDoc(ref, new Post('walnut', 'author')); + const batch = writeBatch(db); expect(() => batch.set(ref, { title: 'olive' }, { merge: true }) ).to.throw( @@ -1447,117 +1480,112 @@ apiDescribe('Database', (persistence: boolean) => { it('WriteBatch.set() supports partials with merge', async () => { return withTestDb(persistence, async db => { - const ref = db - .collection('posts') - .doc() - .withConverter(postConverterMerge); - await ref.set(new Post('walnut', 'author')); - const batch = db.batch(); + const ref = doc(collection(db, 'posts')).withConverter( + postConverterMerge + ); + await setDoc(ref, new Post('walnut', 'author')); + const batch = writeBatch(db); batch.set(ref, { title: 'olive' }, { merge: true }); await batch.commit(); - const doc = await ref.get(); - expect(doc.get('title')).to.equal('olive'); - expect(doc.get('author')).to.equal('author'); + const docSnap = await getDoc(ref); + expect(docSnap.get('title')).to.equal('olive'); + expect(docSnap.get('author')).to.equal('author'); }); }); it('WriteBatch.set() supports partials with mergeFields', async () => { return withTestDb(persistence, async db => { - const ref = db - .collection('posts') - .doc() - .withConverter(postConverterMerge); - await ref.set(new Post('walnut', 'author')); - const batch = db.batch(); + const ref = doc( + collection(db, 'posts').withConverter(postConverterMerge) + ); + await setDoc(ref, new Post('walnut', 'author')); + const batch = writeBatch(db); batch.set( ref, { title: 'olive', author: 'writer' }, { mergeFields: ['title'] } ); await batch.commit(); - const doc = await ref.get(); - expect(doc.get('title')).to.equal('olive'); - expect(doc.get('author')).to.equal('author'); + const docSnap = await getDoc(ref); + expect(docSnap.get('title')).to.equal('olive'); + expect(docSnap.get('author')).to.equal('author'); }); }); it('Transaction.set() supports partials with merge', async () => { return withTestDb(persistence, async db => { - const ref = db - .collection('posts') - .doc() - .withConverter(postConverterMerge); - await ref.set(new Post('walnut', 'author')); - await db.runTransaction(async tx => { + const ref = doc(collection(db, 'posts')).withConverter( + postConverterMerge + ); + await setDoc(ref, new Post('walnut', 'author')); + await runTransaction(db, async tx => { tx.set(ref, { title: 'olive' }, { merge: true }); }); - const doc = await ref.get(); - expect(doc.get('title')).to.equal('olive'); - expect(doc.get('author')).to.equal('author'); + const docSnap = await getDoc(ref); + expect(docSnap.get('title')).to.equal('olive'); + expect(docSnap.get('author')).to.equal('author'); }); }); it('Transaction.set() supports partials with mergeFields', async () => { return withTestDb(persistence, async db => { - const ref = db - .collection('posts') - .doc() - .withConverter(postConverterMerge); - await ref.set(new Post('walnut', 'author')); - await db.runTransaction(async tx => { + const ref = doc(collection(db, 'posts')).withConverter( + postConverterMerge + ); + await setDoc(ref, new Post('walnut', 'author')); + await runTransaction(db, async tx => { tx.set( ref, { title: 'olive', author: 'person' }, { mergeFields: ['title'] } ); }); - const doc = await ref.get(); - expect(doc.get('title')).to.equal('olive'); - expect(doc.get('author')).to.equal('author'); + const docSnap = await getDoc(ref); + expect(docSnap.get('title')).to.equal('olive'); + expect(docSnap.get('author')).to.equal('author'); }); }); it('DocumentReference.set() supports partials with merge', async () => { return withTestDb(persistence, async db => { - const ref = db - .collection('posts') - .doc() - .withConverter(postConverterMerge); - await ref.set(new Post('walnut', 'author')); - await ref.set({ title: 'olive' }, { merge: true }); - const doc = await ref.get(); - expect(doc.get('title')).to.equal('olive'); - expect(doc.get('author')).to.equal('author'); + const ref = doc(collection(db, 'posts')).withConverter( + postConverterMerge + ); + await setDoc(ref, new Post('walnut', 'author')); + await setDoc(ref, { title: 'olive' }, { merge: true }); + const docSnap = await getDoc(ref); + expect(docSnap.get('title')).to.equal('olive'); + expect(docSnap.get('author')).to.equal('author'); }); }); it('DocumentReference.set() supports partials with mergeFields', async () => { return withTestDb(persistence, async db => { - const ref = db - .collection('posts') - .doc() - .withConverter(postConverterMerge); - await ref.set(new Post('walnut', 'author')); - await ref.set( + const ref = doc(collection(db, 'posts')).withConverter( + postConverterMerge + ); + await setDoc(ref, new Post('walnut', 'author')); + await setDoc( + ref, { title: 'olive', author: 'writer' }, { mergeFields: ['title'] } ); - const doc = await ref.get(); - expect(doc.get('title')).to.equal('olive'); - expect(doc.get('author')).to.equal('author'); + const docSnap = await getDoc(ref); + expect(docSnap.get('title')).to.equal('olive'); + expect(docSnap.get('author')).to.equal('author'); }); }); it('calls DocumentSnapshot.data() with specified SnapshotOptions', () => { return withTestDb(persistence, async db => { - const docRef = db.doc('some/doc').withConverter({ - toFirestore(post: Post): firestore.DocumentData { + const docRef = doc(db, 'some/doc').withConverter({ + toFirestore(post: Post): DocumentData { return { title: post.title, author: post.author }; }, - fromFirestore( - snapshot: firestore.QueryDocumentSnapshot, - options: firestore.SnapshotOptions - ): Post { + fromFirestore(snapshot: QueryDocumentSnapshot): Post { + // Hack: Due to our build setup, TypeScript thinks this is a + // firestore-lite converter which does not have options + const options = arguments[1]; // Check that options were passed in properly. expect(options).to.deep.equal({ serverTimestamps: 'estimate' }); @@ -1566,20 +1594,21 @@ apiDescribe('Database', (persistence: boolean) => { } }); - await docRef.set(new Post('post', 'author')); - const postData = await docRef.get(); + await setDoc(docRef, new Post('post', 'author')); + const postData = await getDoc(docRef); postData.data({ serverTimestamps: 'estimate' }); }); }); it('drops the converter when calling CollectionReference.parent()', () => { return withTestDb(persistence, async db => { - const postsCollection = db - .collection('users/user1/posts') - .withConverter(postConverter); + const postsCollection = collection( + db, + 'users/user1/posts' + ).withConverter(postConverter); const usersCollection = postsCollection.parent; - expect(usersCollection!.isEqual(db.doc('users/user1'))).to.be.true; + expect(refEqual(usersCollection!, doc(db, 'users/user1'))).to.be.true; }); }); @@ -1587,64 +1616,67 @@ apiDescribe('Database', (persistence: boolean) => { return withTestDb(persistence, async db => { const postConverter2 = { ...postConverter }; - const postsCollection = db - .collection('users/user1/posts') - .withConverter(postConverter); - const postsCollection2 = db - .collection('users/user1/posts') - .withConverter(postConverter2); - expect(postsCollection.isEqual(postsCollection2)).to.be.false; - - const docRef = db.doc('some/doc').withConverter(postConverter); - const docRef2 = db.doc('some/doc').withConverter(postConverter2); - expect(docRef.isEqual(docRef2)).to.be.false; + const postsCollection = collection( + db, + 'users/user1/posts' + ).withConverter(postConverter); + const postsCollection2 = collection( + db, + 'users/user1/posts' + ).withConverter(postConverter2); + expect(refEqual(postsCollection, postsCollection2)).to.be.false; + + const docRef = doc(db, 'some/doc').withConverter(postConverter); + const docRef2 = doc(db, 'some/doc').withConverter(postConverter2); + expect(refEqual(docRef, docRef2)).to.be.false; }); }); it('Correct snapshot specified to fromFirestore() when registered with DocumentReference', () => { return withTestDb(persistence, async db => { - const untypedDocRef = db.collection('/models').doc(); + const untypedDocRef = doc(collection(db, '/models')); const docRef = untypedDocRef.withConverter(postConverter); - await docRef.set(new Post('post', 'author')); - const docSnapshot = await docRef.get(); + await setDoc(docRef, new Post('post', 'author')); + const docSnapshot = await getDoc(docRef); const ref = docSnapshot.data()!.ref!; expect(ref).to.be.an.instanceof(DocumentReference); - expect(untypedDocRef.isEqual(ref)).to.be.true; + expect(refEqual(untypedDocRef, ref)).to.be.true; }); }); it('Correct snapshot specified to fromFirestore() when registered with CollectionReference', () => { return withTestDb(persistence, async db => { - const untypedCollection = db - .collection('/models') - .doc() - .collection('sub'); - const collection = untypedCollection.withConverter(postConverter); - const docRef = collection.doc(); - await docRef.set(new Post('post', 'author', docRef)); - const querySnapshot = await collection.get(); + const untypedCollection = collection( + doc(collection(db, '/models')), + 'sub' + ); + const typeCollection = untypedCollection.withConverter(postConverter); + const docRef = doc(typeCollection); + await setDoc(docRef, new Post('post', 'author', docRef)); + const querySnapshot = await getDocs(typeCollection); expect(querySnapshot.size).to.equal(1); const ref = querySnapshot.docs[0].data().ref!; expect(ref).to.be.an.instanceof(DocumentReference); - const untypedDocRef = untypedCollection.doc(docRef.id); - expect(untypedDocRef.isEqual(ref)).to.be.true; + const untypedDocRef = doc(untypedCollection, docRef.id); + expect(refEqual(untypedDocRef, ref)).to.be.true; }); }); it('Correct snapshot specified to fromFirestore() when registered with Query', () => { return withTestDb(persistence, async db => { - const untypedCollection = db.collection('/models'); - const untypedDocRef = untypedCollection.doc(); + const untypedCollection = collection(db, '/models'); + const untypedDocRef = doc(untypedCollection); const docRef = untypedDocRef.withConverter(postConverter); - await docRef.set(new Post('post', 'author', docRef)); - const query = untypedCollection - .where(FieldPath.documentId(), '==', docRef.id) - .withConverter(postConverter); - const querySnapshot = await query.get(); + await setDoc(docRef, new Post('post', 'author', docRef)); + const filteredQuery = query( + untypedCollection, + where(documentId(), '==', docRef.id) + ).withConverter(postConverter); + const querySnapshot = await getDocs(filteredQuery); expect(querySnapshot.size).to.equal(1); const ref = querySnapshot.docs[0].data().ref!; expect(ref).to.be.an.instanceof(DocumentReference); - expect(untypedDocRef.isEqual(ref)).to.be.true; + expect(refEqual(untypedDocRef, ref)).to.be.true; }); }); }); @@ -1664,13 +1696,12 @@ apiDescribe('Database', (persistence: boolean) => { 1, async ([db]) => { const data = { name: 'Rafi', email: 'abc@xyz.com' }; - const doc = await db.collection('users').doc(); + const ref = await doc(collection(db, 'users')); - return doc - .set(data) - .then(() => doc.get()) + return setDoc(ref, data) + .then(() => getDoc(ref)) .then(snapshot => { - expect(snapshot.exists).to.be.ok; + expect(snapshot.exists()).to.be.ok; expect(snapshot.data()).to.deep.equal(data); }); } diff --git a/packages/firestore/test/integration/api/fields.test.ts b/packages/firestore/test/integration/api/fields.test.ts index 6d92def2493..0456ad5e48d 100644 --- a/packages/firestore/test/integration/api/fields.test.ts +++ b/packages/firestore/test/integration/api/fields.test.ts @@ -17,7 +17,18 @@ import { expect } from 'chai'; -import * as firebaseExport from '../util/firebase_export'; +import { + FieldPath, + getDoc, + getDocs, + orderBy, + query, + setDoc, + startAt, + Timestamp, + updateDoc, + where +} from '../util/firebase_export'; import { apiDescribe, toDataArray, @@ -28,9 +39,6 @@ import { } from '../util/helpers'; import { DEFAULT_SETTINGS } from '../util/settings'; -const FieldPath = firebaseExport.FieldPath; -const Timestamp = firebaseExport.Timestamp; - // Allow custom types for testing. // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyTestData = any; @@ -51,9 +59,8 @@ apiDescribe('Nested Fields', (persistence: boolean) => { it('can be written with set()', () => { return withTestDoc(persistence, doc => { - return doc - .set(testData()) - .then(() => doc.get()) + return setDoc(doc, testData()) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.data()).to.deep.equal(testData()); }); @@ -63,9 +70,8 @@ apiDescribe('Nested Fields', (persistence: boolean) => { it('can be read directly with .get()', () => { return withTestDoc(persistence, doc => { const obj = testData(); - return doc - .set(obj) - .then(() => doc.get()) + return setDoc(doc, obj) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.data()).to.deep.equal(obj); expect(docSnap.get('name')).to.deep.equal(obj.name); @@ -82,9 +88,8 @@ apiDescribe('Nested Fields', (persistence: boolean) => { it('can be read directly with .get()', () => { return withTestDoc(persistence, doc => { const obj = testData(); - return doc - .set(obj) - .then(() => doc.get()) + return setDoc(doc, obj) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.data()).to.deep.equal(obj); expect(docSnap.get(new FieldPath('name'))).to.deep.equal(obj.name); @@ -104,15 +109,14 @@ apiDescribe('Nested Fields', (persistence: boolean) => { it('can be updated with update()', () => { return withTestDoc(persistence, doc => { - return doc - .set(testData()) - .then(() => { - return doc.update({ + return setDoc(doc, testData()) + .then(() => + updateDoc(doc, { 'metadata.deep.field': 100, 'metadata.added': 200 - }); - }) - .then(() => doc.get()) + }) + ) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.data()).to.deep.equal({ name: 'room 1', @@ -130,17 +134,17 @@ apiDescribe('Nested Fields', (persistence: boolean) => { it('can be updated with update()', () => { return withTestDoc(persistence, doc => { - return doc - .set(testData()) - .then(() => { - return doc.update( + return setDoc(doc, testData()) + .then(() => + updateDoc( + doc, new FieldPath('metadata', 'deep', 'field'), 100, new FieldPath('metadata', 'added'), 200 - ); - }) - .then(() => doc.get()) + ) + ) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.data()).to.deep.equal({ name: 'room 1', @@ -163,16 +167,15 @@ apiDescribe('Nested Fields', (persistence: boolean) => { '3': testData(200) }; return withTestCollection(persistence, testDocs, coll => { - return coll - .where('metadata.createdAt', '>=', 200) - .get() - .then(results => { + return getDocs(query(coll, where('metadata.createdAt', '>=', 200))).then( + results => { // inequality adds implicit sort on field expect(toDataArray(results)).to.deep.equal([ testData(200), testData(300) ]); - }); + } + ); }); }); @@ -183,16 +186,15 @@ apiDescribe('Nested Fields', (persistence: boolean) => { '3': testData(200) }; return withTestCollection(persistence, testDocs, coll => { - return coll - .where(new FieldPath('metadata', 'createdAt'), '>=', 200) - .get() - .then(results => { - // inequality adds implicit sort on field - expect(toDataArray(results)).to.deep.equal([ - testData(200), - testData(300) - ]); - }); + return getDocs( + query(coll, where(new FieldPath('metadata', 'createdAt'), '>=', 200)) + ).then(results => { + // inequality adds implicit sort on field + expect(toDataArray(results)).to.deep.equal([ + testData(200), + testData(300) + ]); + }); }); }); @@ -203,16 +205,15 @@ apiDescribe('Nested Fields', (persistence: boolean) => { '3': testData(200) }; return withTestCollection(persistence, testDocs, coll => { - return coll - .orderBy('metadata.createdAt') - .get() - .then(results => { + return getDocs(query(coll, orderBy('metadata.createdAt'))).then( + results => { expect(toDataArray(results)).to.deep.equal([ testData(100), testData(200), testData(300) ]); - }); + } + ); }); }); @@ -223,16 +224,15 @@ apiDescribe('Nested Fields', (persistence: boolean) => { '3': testData(200) }; return withTestCollection(persistence, testDocs, coll => { - return coll - .orderBy(new FieldPath('metadata', 'createdAt')) - .get() - .then(results => { - expect(toDataArray(results)).to.deep.equal([ - testData(100), - testData(200), - testData(300) - ]); - }); + return getDocs( + query(coll, orderBy(new FieldPath('metadata', 'createdAt'))) + ).then(results => { + expect(toDataArray(results)).to.deep.equal([ + testData(100), + testData(200), + testData(300) + ]); + }); }); }); }); @@ -252,9 +252,8 @@ apiDescribe('Fields with special characters', (persistence: boolean) => { it('can be written with set()', () => { return withTestDoc(persistence, doc => { - return doc - .set(testData()) - .then(() => doc.get()) + return setDoc(doc, testData()) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.data()).to.deep.equal(testData()); }); @@ -264,9 +263,8 @@ apiDescribe('Fields with special characters', (persistence: boolean) => { it('can be read directly with .data()', () => { return withTestDoc(persistence, doc => { const obj = testData(); - return doc - .set(obj) - .then(() => doc.get()) + return setDoc(doc, obj) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.data()).to.deep.equal(obj); expect(docSnap.get(new FieldPath('field.dot'))).to.deep.equal( @@ -281,17 +279,17 @@ apiDescribe('Fields with special characters', (persistence: boolean) => { it('can be updated with update()', () => { return withTestDoc(persistence, doc => { - return doc - .set(testData()) + return setDoc(doc, testData()) .then(() => { - return doc.update( + return updateDoc( + doc, new FieldPath('field.dot'), 100, 'field\\slash', 200 ); }) - .then(() => doc.get()) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.data()).to.deep.equal({ field: 'field 1', @@ -311,13 +309,11 @@ apiDescribe('Fields with special characters', (persistence: boolean) => { return withTestCollection(persistence, testDocs, coll => { // inequality adds implicit sort on field const expected = [testData(200), testData(300)]; - return coll - .where(new FieldPath('field.dot'), '>=', 200) - .get() + return getDocs(query(coll, where(new FieldPath('field.dot'), '>=', 200))) .then(results => { expect(toDataArray(results)).to.deep.equal(expected); }) - .then(() => coll.where('field\\slash', '>=', 200).get()) + .then(() => getDocs(query(coll, where('field\\slash', '>=', 200)))) .then(results => { expect(toDataArray(results)).to.deep.equal(expected); }); @@ -332,13 +328,11 @@ apiDescribe('Fields with special characters', (persistence: boolean) => { }; return withTestCollection(persistence, testDocs, coll => { const expected = [testData(100), testData(200), testData(300)]; - return coll - .orderBy(new FieldPath('field.dot')) - .get() + return getDocs(query(coll, orderBy(new FieldPath('field.dot')))) .then(results => { expect(toDataArray(results)).to.deep.equal(expected); }) - .then(() => coll.orderBy('field\\slash').get()) + .then(() => getDocs(query(coll, orderBy('field\\slash')))) .then(results => { expect(toDataArray(results)).to.deep.equal(expected); }); @@ -364,9 +358,8 @@ apiDescribe('Timestamp Fields in snapshots', (persistence: boolean) => { ); return withTestDoc(persistence, doc => { - return doc - .set(testDataWithTimestamps(timestamp)) - .then(() => doc.get()) + return setDoc(doc, testDataWithTimestamps(timestamp)) + .then(() => getDoc(doc)) .then(docSnap => { expect(docSnap.get('timestamp')) .to.be.an.instanceof(Timestamp) @@ -392,27 +385,27 @@ apiDescribe('`undefined` properties', (persistence: boolean) => { it('are ignored in set()', () => { return withTestDocAndSettings(persistence, settings, async doc => { - await doc.set({ foo: 'foo', 'bar': undefined }); - const docSnap = await doc.get(); + await setDoc(doc, { foo: 'foo', 'bar': undefined }); + const docSnap = await getDoc(doc); expect(docSnap.data()).to.deep.equal({ foo: 'foo' }); }); }); it('are ignored in set({ merge: true })', () => { return withTestDocAndSettings(persistence, settings, async doc => { - await doc.set({ foo: 'foo', bar: 'unchanged' }); - await doc.set({ foo: 'foo', bar: undefined }, { merge: true }); - const docSnap = await doc.get(); + await setDoc(doc, { foo: 'foo', bar: 'unchanged' }); + await setDoc(doc, { foo: 'foo', bar: undefined }, { merge: true }); + const docSnap = await getDoc(doc); expect(docSnap.data()).to.deep.equal({ foo: 'foo', bar: 'unchanged' }); }); }); it('are ignored in update()', () => { return withTestDocAndSettings(persistence, settings, async doc => { - await doc.set({}); - await doc.update({ a: { foo: 'foo', 'bar': undefined } }); - await doc.update('b', { foo: 'foo', 'bar': undefined }); - const docSnap = await doc.get(); + await setDoc(doc, {}); + await updateDoc(doc, { a: { foo: 'foo', 'bar': undefined } }); + await updateDoc(doc, 'b', { foo: 'foo', 'bar': undefined }); + const docSnap = await getDoc(doc); expect(docSnap.data()).to.deep.equal({ a: { foo: 'foo' }, b: { foo: 'foo' } @@ -426,11 +419,14 @@ apiDescribe('`undefined` properties', (persistence: boolean) => { settings, { 'doc1': { nested: { foo: 'foo' } } }, async coll => { - const query = coll.where('nested', '==', { - foo: 'foo', - 'bar': undefined - }); - const querySnap = await query.get(); + const filteredQuery = query( + coll, + where('nested', '==', { + foo: 'foo', + 'bar': undefined + }) + ); + const querySnap = await getDocs(filteredQuery); expect(querySnap.size).to.equal(1); } ); @@ -442,10 +438,12 @@ apiDescribe('`undefined` properties', (persistence: boolean) => { settings, { 'doc1': { nested: { foo: 'foo' } } }, async coll => { - const query = coll - .orderBy('nested') - .startAt({ foo: 'foo', 'bar': undefined }); - const querySnap = await query.get(); + const orderedQuery = query( + coll, + orderBy('nested'), + startAt({ foo: 'foo', 'bar': undefined }) + ); + const querySnap = await getDocs(orderedQuery); expect(querySnap.size).to.equal(1); } ); diff --git a/packages/firestore/test/integration/api/get_options.test.ts b/packages/firestore/test/integration/api/get_options.test.ts index 59a0fc1489e..066802693d8 100644 --- a/packages/firestore/test/integration/api/get_options.test.ts +++ b/packages/firestore/test/integration/api/get_options.test.ts @@ -18,8 +18,21 @@ import { expect } from 'chai'; import { - apiDescribe, + deleteDoc, + disableNetwork, + doc, + getDoc, + getDocFromCache, + getDocFromServer, + getDocs, + onSnapshot, + setDoc, + getDocsFromCache, + getDocsFromServer +} from '../util/firebase_export'; +import { toDataMap, + apiDescribe, withTestCollection, withTestDocAndInitialData } from '../util/helpers'; @@ -28,8 +41,8 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get document while online with default get options', () => { const initialData = { key: 'value' }; return withTestDocAndInitialData(persistence, initialData, docRef => { - return docRef.get().then(doc => { - expect(doc.exists).to.be.true; + return getDoc(docRef).then(doc => { + expect(doc.exists()).to.be.true; expect(doc.metadata.fromCache).to.be.false; expect(doc.metadata.hasPendingWrites).to.be.false; expect(doc.data()).to.deep.equal(initialData); @@ -44,7 +57,7 @@ apiDescribe('GetOptions', (persistence: boolean) => { doc3: { key3: 'value3' } }; return withTestCollection(persistence, initialDocs, colRef => { - return colRef.get().then(qrySnap => { + return getDocs(colRef).then(qrySnap => { expect(qrySnap.metadata.fromCache).to.be.false; expect(qrySnap.metadata.hasPendingWrites).to.be.false; expect(qrySnap.docChanges().length).to.equal(3); @@ -55,16 +68,15 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get document while offline with default get options', () => { const initialData = { key: 'value' }; - return withTestDocAndInitialData(persistence, initialData, docRef => { + return withTestDocAndInitialData(persistence, initialData, (docRef, db) => { // Register a snapshot to force the data to stay in the cache and not be // garbage collected. - docRef.onSnapshot(() => {}); - return docRef - .get() - .then(ignored => docRef.firestore.disableNetwork()) - .then(() => docRef.get()) + onSnapshot(docRef, () => {}); + return getDoc(docRef) + .then(() => disableNetwork(db)) + .then(() => getDoc(docRef)) .then(doc => { - expect(doc.exists).to.be.true; + expect(doc.exists()).to.be.true; expect(doc.metadata.fromCache).to.be.true; expect(doc.metadata.hasPendingWrites).to.be.false; expect(doc.data()).to.deep.equal(initialData); @@ -78,21 +90,20 @@ apiDescribe('GetOptions', (persistence: boolean) => { doc2: { key2: 'value2' }, doc3: { key3: 'value3' } }; - return withTestCollection(persistence, initialDocs, colRef => { + return withTestCollection(persistence, initialDocs, (colRef, db) => { // Register a snapshot to force the data to stay in the cache and not be // garbage collected. - colRef.onSnapshot(() => {}); - return colRef - .get() - .then(ignored => colRef.firestore.disableNetwork()) + onSnapshot(colRef, () => {}); + return getDocs(colRef) + .then(() => disableNetwork(db)) .then(() => { // NB: since we're offline, the returned promises won't complete /* eslint-disable @typescript-eslint/no-floating-promises */ - colRef.doc('doc2').set({ key2b: 'value2b' }, { merge: true }); - colRef.doc('doc3').set({ key3b: 'value3b' }); - colRef.doc('doc4').set({ key4: 'value4' }); + setDoc(doc(colRef, 'doc2'), { key2b: 'value2b' }, { merge: true }); + setDoc(doc(colRef, 'doc3'), { key3b: 'value3b' }); + setDoc(doc(colRef, 'doc4'), { key4: 'value4' }); /* eslint-enable @typescript-eslint/no-floating-promises */ - return colRef.get(); + return getDocs(colRef); }) .then(qrySnap => { expect(qrySnap.metadata.fromCache).to.be.true; @@ -114,12 +125,11 @@ apiDescribe('GetOptions', (persistence: boolean) => { return withTestDocAndInitialData(persistence, initialData, docRef => { // Register a snapshot to force the data to stay in the cache and not be // garbage collected. - docRef.onSnapshot(() => {}); - return docRef - .get() - .then(ignored => docRef.get({ source: 'cache' })) + onSnapshot(docRef, () => {}); + return getDoc(docRef) + .then(() => getDocFromCache(docRef)) .then(doc => { - expect(doc.exists).to.be.true; + expect(doc.exists()).to.be.true; expect(doc.metadata.fromCache).to.be.true; expect(doc.metadata.hasPendingWrites).to.be.false; expect(doc.data()).to.deep.equal(initialData); @@ -136,10 +146,9 @@ apiDescribe('GetOptions', (persistence: boolean) => { return withTestCollection(persistence, initialDocs, colRef => { // Register a snapshot to force the data to stay in the cache and not be // garbage collected. - colRef.onSnapshot(() => {}); - return colRef - .get() - .then(ignored => colRef.get({ source: 'cache' })) + onSnapshot(colRef, () => {}); + return getDocs(colRef) + .then(() => getDocsFromCache(colRef)) .then(qrySnap => { expect(qrySnap.metadata.fromCache).to.be.true; expect(qrySnap.metadata.hasPendingWrites).to.be.false; @@ -152,16 +161,15 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get document while offline with source=cache', () => { const initialData = { key: 'value' }; - return withTestDocAndInitialData(persistence, initialData, docRef => { + return withTestDocAndInitialData(persistence, initialData, (docRef, db) => { // Register a snapshot to force the data to stay in the cache and not be // garbage collected. - docRef.onSnapshot(() => {}); - return docRef - .get() - .then(ignored => docRef.firestore.disableNetwork()) - .then(() => docRef.get({ source: 'cache' })) + onSnapshot(docRef, () => {}); + return getDoc(docRef) + .then(() => disableNetwork(db)) + .then(() => getDocFromCache(docRef)) .then(doc => { - expect(doc.exists).to.be.true; + expect(doc.exists()).to.be.true; expect(doc.metadata.fromCache).to.be.true; expect(doc.metadata.hasPendingWrites).to.be.false; expect(doc.data()).to.deep.equal(initialData); @@ -175,22 +183,21 @@ apiDescribe('GetOptions', (persistence: boolean) => { doc2: { key2: 'value2' }, doc3: { key3: 'value3' } }; - return withTestCollection(persistence, initialDocs, colRef => { + return withTestCollection(persistence, initialDocs, (colRef, db) => { // Register a snapshot to force the data to stay in the cache and not be // garbage collected. - colRef.onSnapshot(() => {}); - return colRef - .get() - .then(ignored => colRef.firestore.disableNetwork()) + onSnapshot(colRef, () => {}); + return getDocs(colRef) + .then(() => disableNetwork(db)) .then(() => { // NB: since we're offline, the returned promises won't complete /* eslint-disable @typescript-eslint/no-floating-promises */ - colRef.doc('doc2').set({ key2b: 'value2b' }, { merge: true }); - colRef.doc('doc3').set({ key3b: 'value3b' }); - colRef.doc('doc4').set({ key4: 'value4' }); + setDoc(doc(colRef, 'doc2'), { key2b: 'value2b' }, { merge: true }); + setDoc(doc(colRef, 'doc3'), { key3b: 'value3b' }); + setDoc(doc(colRef, 'doc4'), { key4: 'value4' }); /* eslint-enable @typescript-eslint/no-floating-promises */ - return colRef.get({ source: 'cache' }); + return getDocsFromCache(colRef); }) .then(qrySnap => { expect(qrySnap.metadata.fromCache).to.be.true; @@ -210,8 +217,8 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get document while online with source=server', () => { const initialData = { key: 'value' }; return withTestDocAndInitialData(persistence, initialData, docRef => { - return docRef.get({ source: 'server' }).then(doc => { - expect(doc.exists).to.be.true; + return getDocFromServer(docRef).then(doc => { + expect(doc.exists()).to.be.true; expect(doc.metadata.fromCache).to.be.false; expect(doc.metadata.hasPendingWrites).to.be.false; expect(doc.data()).to.deep.equal(initialData); @@ -226,7 +233,7 @@ apiDescribe('GetOptions', (persistence: boolean) => { doc3: { key3: 'value3' } }; return withTestCollection(persistence, initialDocs, colRef => { - return colRef.get({ source: 'server' }).then(qrySnap => { + return getDocsFromServer(colRef).then(qrySnap => { expect(qrySnap.metadata.fromCache).to.be.false; expect(qrySnap.metadata.hasPendingWrites).to.be.false; expect(qrySnap.docChanges().length).to.equal(3); @@ -237,17 +244,15 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get document while offline with source=server', () => { const initialData = { key: 'value' }; - return withTestDocAndInitialData(persistence, initialData, docRef => { - return docRef - .get({ source: 'server' }) - .then(ignored => {}) - .then(() => docRef.firestore.disableNetwork()) - .then(() => docRef.get({ source: 'server' })) + return withTestDocAndInitialData(persistence, initialData, (docRef, db) => { + return getDocFromServer(docRef) + .then(() => disableNetwork(db)) + .then(() => getDocFromServer(docRef)) .then( - doc => { + () => { expect.fail(); }, - expected => {} + () => {} ); }); }); @@ -258,20 +263,17 @@ apiDescribe('GetOptions', (persistence: boolean) => { doc2: { key2: 'value2' }, doc3: { key3: 'value3' } }; - return withTestCollection(persistence, initialDocs, colRef => { + return withTestCollection(persistence, initialDocs, (colRef, db) => { // force local cache of these return ( - colRef - .get() + getDocs(colRef) // now go offine. Note that if persistence is disabled, this will cause // the initialDocs to be garbage collected. - .then(ignored => colRef.firestore.disableNetwork()) - .then(() => colRef.get({ source: 'server' })) + .then(() => disableNetwork(db)) + .then(() => getDocsFromServer(colRef)) .then( - qrySnap => { - expect.fail(); - }, - expected => {} + () => expect.fail(), + () => {} ) ); }); @@ -280,50 +282,42 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get document while offline with different get options', () => { const initialData = { key: 'value' }; - return withTestDocAndInitialData(persistence, initialData, docRef => { + return withTestDocAndInitialData(persistence, initialData, (docRef, db) => { // Register a snapshot to force the data to stay in the cache and not be // garbage collected. - docRef.onSnapshot(() => {}); - return docRef - .get() - .then(ignored => docRef.firestore.disableNetwork()) + onSnapshot(docRef, () => {}); + return getDoc(docRef) + .then(() => disableNetwork(db)) .then(() => { // Create an initial listener for this query (to attempt to disrupt the // gets below) and wait for the listener to deliver its initial // snapshot before continuing. return new Promise((resolve, reject) => { - docRef.onSnapshot( - docSnap => { - resolve(); - }, - error => { - reject(); - } + onSnapshot( + docRef, + () => resolve(), + () => reject() ); }); }) - .then(() => docRef.get({ source: 'cache' })) + .then(() => getDocFromCache(docRef)) .then(doc => { - expect(doc.exists).to.be.true; + expect(doc.exists()).to.be.true; expect(doc.metadata.fromCache).to.be.true; expect(doc.metadata.hasPendingWrites).to.be.false; expect(doc.data()).to.deep.equal(initialData); - return Promise.resolve(); }) - .then(() => docRef.get()) + .then(() => getDoc(docRef)) .then(doc => { - expect(doc.exists).to.be.true; + expect(doc.exists()).to.be.true; expect(doc.metadata.fromCache).to.be.true; expect(doc.metadata.hasPendingWrites).to.be.false; expect(doc.data()).to.deep.equal(initialData); - return Promise.resolve(); }) - .then(() => docRef.get({ source: 'server' })) + .then(() => getDocFromServer(docRef)) .then( - doc => { - expect.fail(); - }, - expected => {} + () => expect.fail(), + () => {} ); }); }); @@ -334,39 +328,35 @@ apiDescribe('GetOptions', (persistence: boolean) => { doc2: { key2: 'value2' }, doc3: { key3: 'value3' } }; - return withTestCollection(persistence, initialDocs, colRef => { + return withTestCollection(persistence, initialDocs, (colRef, db) => { // Register a snapshot to force the data to stay in the cache and not be // garbage collected. - colRef.onSnapshot(() => {}); + onSnapshot(colRef, () => {}); return ( - colRef - .get() + getDocs(colRef) // now go offine. Note that if persistence is disabled, this will cause // the initialDocs to be garbage collected. - .then(ignored => colRef.firestore.disableNetwork()) + .then(() => disableNetwork(db)) .then(() => { // NB: since we're offline, the returned promises won't complete /* eslint-disable @typescript-eslint/no-floating-promises */ - colRef.doc('doc2').set({ key2b: 'value2b' }, { merge: true }); - colRef.doc('doc3').set({ key3b: 'value3b' }); - colRef.doc('doc4').set({ key4: 'value4' }); + setDoc(doc(colRef, 'doc2'), { key2b: 'value2b' }, { merge: true }); + setDoc(doc(colRef, 'doc3'), { key3b: 'value3b' }); + setDoc(doc(colRef, 'doc4'), { key4: 'value4' }); /* eslint-enable @typescript-eslint/no-floating-promises */ // Create an initial listener for this query (to attempt to disrupt the // gets below) and wait for the listener to deliver its initial // snapshot before continuing. return new Promise((resolve, reject) => { - colRef.onSnapshot( - qrySnap => { - resolve(); - }, - error => { - reject(); - } + onSnapshot( + colRef, + () => resolve(), + () => reject() ); }); }) - .then(() => colRef.get({ source: 'cache' })) + .then(() => getDocsFromCache(colRef)) .then(qrySnap => { expect(qrySnap.metadata.fromCache).to.be.true; expect(qrySnap.metadata.hasPendingWrites).to.be.true; @@ -379,7 +369,7 @@ apiDescribe('GetOptions', (persistence: boolean) => { doc4: { key4: 'value4' } }); }) - .then(() => colRef.get()) + .then(() => getDocs(colRef)) .then(qrySnap => { expect(qrySnap.metadata.fromCache).to.be.true; expect(qrySnap.metadata.hasPendingWrites).to.be.true; @@ -392,12 +382,10 @@ apiDescribe('GetOptions', (persistence: boolean) => { doc4: { key4: 'value4' } }); }) - .then(() => colRef.get({ source: 'server' })) + .then(() => getDocsFromServer(colRef)) .then( - qrySnap => { - expect.fail(); - }, - expected => {} + () => expect.fail(), + () => {} ) ); }); @@ -405,8 +393,8 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get non existing doc while online with default get options', () => { return withTestDocAndInitialData(persistence, null, docRef => { - return docRef.get().then(doc => { - expect(doc.exists).to.be.false; + return getDoc(docRef).then(doc => { + expect(doc.exists()).to.be.false; expect(doc.metadata.fromCache).to.be.false; expect(doc.metadata.hasPendingWrites).to.be.false; }); @@ -415,7 +403,7 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get non existing collection while online with default get options', () => { return withTestCollection(persistence, {}, colRef => { - return colRef.get().then(qrySnap => { + return getDocs(colRef).then(qrySnap => { //expect(qrySnap.count).to.equal(0); expect(qrySnap.empty).to.be.true; expect(qrySnap.docChanges().length).to.equal(0); @@ -426,17 +414,14 @@ apiDescribe('GetOptions', (persistence: boolean) => { }); it('get non existing doc while offline with default get options', () => { - return withTestDocAndInitialData(persistence, null, docRef => { + return withTestDocAndInitialData(persistence, null, (docRef, db) => { return ( - docRef.firestore - .disableNetwork() + disableNetwork(db) // Attempt to get doc. This will fail since there's nothing in cache. - .then(() => docRef.get()) + .then(() => getDoc(docRef)) .then( - doc => { - expect.fail(); - }, - expected => {} + () => expect.fail(), + () => {} ) ); }); @@ -447,13 +432,12 @@ apiDescribe('GetOptions', (persistence: boolean) => { // listener, we do not. // eslint-disable-next-line no-restricted-properties it.skip('get deleted doc while offline with default get options', () => { - return withTestDocAndInitialData(persistence, null, docRef => { - return docRef - .delete() - .then(() => docRef.firestore.disableNetwork()) - .then(() => docRef.get()) + return withTestDocAndInitialData(persistence, null, (docRef, db) => { + return deleteDoc(docRef) + .then(() => disableNetwork(db)) + .then(() => getDoc(docRef)) .then(doc => { - expect(doc.exists).to.be.false; + expect(doc.exists()).to.be.false; expect(doc.data()).to.be.undefined; expect(doc.metadata.fromCache).to.be.true; expect(doc.metadata.hasPendingWrites).to.be.false; @@ -462,10 +446,9 @@ apiDescribe('GetOptions', (persistence: boolean) => { }); it('get non existing collection while offline with default get options', () => { - return withTestCollection(persistence, {}, colRef => { - return colRef.firestore - .disableNetwork() - .then(() => colRef.get()) + return withTestCollection(persistence, {}, (colRef, db) => { + return disableNetwork(db) + .then(() => getDocs(colRef)) .then(qrySnap => { expect(qrySnap.empty).to.be.true; expect(qrySnap.docChanges().length).to.equal(0); @@ -478,18 +461,16 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get non existing doc while online with source=cache', () => { return withTestDocAndInitialData(persistence, null, docRef => { // Attempt to get doc. This will fail since there's nothing in cache. - return docRef.get({ source: 'cache' }).then( - doc => { - expect.fail(); - }, - expected => {} + return getDocFromCache(docRef).then( + () => expect.fail(), + () => {} ); }); }); it('get non existing collection while online with source=cache', () => { return withTestCollection(persistence, {}, colRef => { - return colRef.get({ source: 'cache' }).then(qrySnap => { + return getDocsFromCache(colRef).then(qrySnap => { expect(qrySnap.empty).to.be.true; expect(qrySnap.docChanges().length).to.equal(0); expect(qrySnap.metadata.fromCache).to.be.true; @@ -499,17 +480,15 @@ apiDescribe('GetOptions', (persistence: boolean) => { }); it('get non existing doc while offline with source=cache', () => { - return withTestDocAndInitialData(persistence, null, docRef => { + return withTestDocAndInitialData(persistence, null, (docRef, db) => { return ( - docRef.firestore - .disableNetwork() + disableNetwork(db) // Attempt to get doc. This will fail since there's nothing in cache. - .then(() => docRef.get({ source: 'cache' })) - .then( - doc => { - expect.fail(); - }, - expected => {} + .then(() => + getDocFromCache(docRef).then( + () => expect.fail(), + () => {} + ) ) ); }); @@ -520,15 +499,14 @@ apiDescribe('GetOptions', (persistence: boolean) => { (persistence ? it : it.skip)( 'get deleted doc while offline with source=cache', () => { - return withTestDocAndInitialData(persistence, null, docRef => { + return withTestDocAndInitialData(persistence, null, (docRef, db) => { return ( - docRef - .delete() - .then(() => docRef.firestore.disableNetwork()) + deleteDoc(docRef) + .then(() => disableNetwork(db)) // Should get a document with exists=false, fromCache=true - .then(() => docRef.get({ source: 'cache' })) + .then(() => getDocFromCache(docRef)) .then(doc => { - expect(doc.exists).to.be.false; + expect(doc.exists()).to.be.false; expect(doc.data()).to.be.undefined; expect(doc.metadata.fromCache).to.be.true; expect(doc.metadata.hasPendingWrites).to.be.false; @@ -539,10 +517,9 @@ apiDescribe('GetOptions', (persistence: boolean) => { ); it('get non existing collection while offline with source=cache', () => { - return withTestCollection(persistence, {}, colRef => { - return colRef.firestore - .disableNetwork() - .then(() => colRef.get({ source: 'cache' })) + return withTestCollection(persistence, {}, (colRef, db) => { + return disableNetwork(db) + .then(() => getDocsFromCache(colRef)) .then(qrySnap => { expect(qrySnap.empty).to.be.true; expect(qrySnap.docChanges().length).to.equal(0); @@ -554,8 +531,8 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get non existing doc while online with source=server', () => { return withTestDocAndInitialData(persistence, null, docRef => { - return docRef.get({ source: 'server' }).then(doc => { - expect(doc.exists).to.be.false; + return getDocFromServer(docRef).then(doc => { + expect(doc.exists()).to.be.false; expect(doc.metadata.fromCache).to.be.false; expect(doc.metadata.hasPendingWrites).to.be.false; }); @@ -563,8 +540,8 @@ apiDescribe('GetOptions', (persistence: boolean) => { }); it('get non existing collection while online with source=server', () => { - return withTestCollection(persistence, {}, colRef => { - return colRef.get({ source: 'server' }).then(qrySnap => { + return withTestCollection(persistence, {}, (colRef, db) => { + return getDocsFromServer(colRef).then(qrySnap => { expect(qrySnap.empty).to.be.true; expect(qrySnap.docChanges().length).to.equal(0); expect(qrySnap.metadata.fromCache).to.be.false; @@ -574,32 +551,26 @@ apiDescribe('GetOptions', (persistence: boolean) => { }); it('get non existing doc while offline with source=server', () => { - return withTestDocAndInitialData(persistence, null, docRef => { + return withTestDocAndInitialData(persistence, null, (docRef, db) => { return ( - docRef.firestore - .disableNetwork() + disableNetwork(db) // Attempt to get doc. This will fail since there's nothing in cache. - .then(() => docRef.get({ source: 'server' })) + .then(() => getDocFromServer(docRef)) .then( - doc => { - expect.fail(); - }, - expected => {} + () => expect.fail(), + () => {} ) ); }); }); it('get non existing collection while offline with source=server', () => { - return withTestCollection(persistence, {}, colRef => { - return colRef.firestore - .disableNetwork() - .then(() => colRef.get({ source: 'server' })) + return withTestCollection(persistence, {}, (colRef, db) => { + return disableNetwork(db) + .then(() => getDocsFromServer(colRef)) .then( - qrySnap => { - expect.fail(); - }, - expected => {} + () => expect.fail(), + () => {} ); }); }); diff --git a/packages/firestore/test/integration/api/numeric_transforms.test.ts b/packages/firestore/test/integration/api/numeric_transforms.test.ts index 50181ea4d66..e456e2ffe2d 100644 --- a/packages/firestore/test/integration/api/numeric_transforms.test.ts +++ b/packages/firestore/test/integration/api/numeric_transforms.test.ts @@ -15,33 +15,44 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { EventsAccumulator } from '../util/events_accumulator'; -import * as firebaseExport from '../util/firebase_export'; +import { + deleteField, + disableNetwork, + DocumentData, + DocumentSnapshot, + enableNetwork, + onSnapshot, + serverTimestamp, + setDoc, + updateDoc, + writeBatch, + DocumentReference, + increment, + Firestore +} from '../util/firebase_export'; import { apiDescribe, withTestDoc } from '../util/helpers'; -const FieldValue = firebaseExport.FieldValue; - const DOUBLE_EPSILON = 0.000001; apiDescribe('Numeric Transforms:', (persistence: boolean) => { // A document reference to read and write to. - let docRef: firestore.DocumentReference; + let docRef: DocumentReference; + + let db: Firestore; // Accumulator used to capture events during the test. - let accumulator: EventsAccumulator; + let accumulator: EventsAccumulator; // Listener registration for a listener maintained during the course of the // test. let unsubscribe: () => void; /** Writes some initialData and consumes the events generated. */ - async function writeInitialData( - initialData: firestore.DocumentData - ): Promise { - await docRef.set(initialData); + async function writeInitialData(initialData: DocumentData): Promise { + await setDoc(docRef, initialData); await accumulator.awaitLocalEvent(); const snapshot = await accumulator.awaitRemoteEvent(); expect(snapshot.data()).to.deep.equal(initialData); @@ -59,17 +70,19 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { * up when done. */ async function withTestSetup(test: () => Promise): Promise { - await withTestDoc(persistence, async doc => { + await withTestDoc(persistence, async (doc, firestore) => { docRef = doc; - accumulator = new EventsAccumulator(); - unsubscribe = docRef.onSnapshot( + db = firestore; + accumulator = new EventsAccumulator(); + unsubscribe = onSnapshot( + docRef, { includeMetadataChanges: true }, accumulator.storeEvent ); // wait for initial null snapshot to avoid potential races. const snapshot = await accumulator.awaitRemoteEvent(); - expect(snapshot.exists).to.be.false; + expect(snapshot.exists()).to.be.false; await test(); unsubscribe(); }); @@ -77,14 +90,14 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { it('create document with increment', async () => { await withTestSetup(async () => { - await docRef.set({ sum: FieldValue.increment(1337) }); + await setDoc(docRef, { sum: increment(1337) }); await expectLocalAndRemoteValue(1337); }); }); it('merge on non-existing document with increment', async () => { await withTestSetup(async () => { - await docRef.set({ sum: FieldValue.increment(1337) }, { merge: true }); + await setDoc(docRef, { sum: increment(1337) }, { merge: true }); await expectLocalAndRemoteValue(1337); }); }); @@ -92,7 +105,7 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { it('increment existing integer with integer', async () => { await withTestSetup(async () => { await writeInitialData({ sum: 1337 }); - await docRef.update('sum', FieldValue.increment(1)); + await updateDoc(docRef, 'sum', increment(1)); await expectLocalAndRemoteValue(1338); }); }); @@ -100,7 +113,7 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { it('increment existing double with double', async () => { await withTestSetup(async () => { await writeInitialData({ sum: 13.37 }); - await docRef.update('sum', FieldValue.increment(0.1)); + await updateDoc(docRef, 'sum', increment(0.1)); await expectLocalAndRemoteValue(13.47); }); }); @@ -108,7 +121,7 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { it('increment existing double with integer', async () => { await withTestSetup(async () => { await writeInitialData({ sum: 13.37 }); - await docRef.update('sum', FieldValue.increment(1)); + await updateDoc(docRef, 'sum', increment(1)); await expectLocalAndRemoteValue(14.37); }); }); @@ -116,7 +129,7 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { it('increment existing integer with double', async () => { await withTestSetup(async () => { await writeInitialData({ sum: 1337 }); - await docRef.update('sum', FieldValue.increment(0.1)); + await updateDoc(docRef, 'sum', increment(0.1)); await expectLocalAndRemoteValue(1337.1); }); }); @@ -124,7 +137,7 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { it('increment existing string with integer', async () => { await withTestSetup(async () => { await writeInitialData({ sum: 'overwrite' }); - await docRef.update('sum', FieldValue.increment(1337)); + await updateDoc(docRef, 'sum', increment(1337)); await expectLocalAndRemoteValue(1337); }); }); @@ -132,7 +145,7 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { it('increment existing string with double', async () => { await withTestSetup(async () => { await writeInitialData({ sum: 'overwrite' }); - await docRef.update('sum', FieldValue.increment(13.37)); + await updateDoc(docRef, 'sum', increment(13.37)); await expectLocalAndRemoteValue(13.37); }); }); @@ -140,7 +153,7 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { it('increments with set() and merge:true', async () => { await withTestSetup(async () => { await writeInitialData({ sum: 1 }); - await docRef.set({ sum: FieldValue.increment(1337) }, { merge: true }); + await setDoc(docRef, { sum: increment(1337) }, { merge: true }); await expectLocalAndRemoteValue(1338); }); }); @@ -149,12 +162,12 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { await withTestSetup(async () => { await writeInitialData({ sum: 0.0 }); - await docRef.firestore.disableNetwork(); + await disableNetwork(db); /* eslint-disable @typescript-eslint/no-floating-promises */ - docRef.update('sum', FieldValue.increment(0.1)); - docRef.update('sum', FieldValue.increment(0.01)); - docRef.update('sum', FieldValue.increment(0.001)); + updateDoc(docRef, 'sum', increment(0.1)); + updateDoc(docRef, 'sum', increment(0.01)); + updateDoc(docRef, 'sum', increment(0.001)); /* eslint-enable @typescript-eslint/no-floating-promises */ let snap = await accumulator.awaitLocalEvent(); @@ -164,7 +177,7 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { snap = await accumulator.awaitLocalEvent(); expect(snap.get('sum')).to.be.closeTo(0.111, DOUBLE_EPSILON); - await docRef.firestore.enableNetwork(); + await enableNetwork(db); snap = await accumulator.awaitRemoteEvent(); expect(snap.get('sum')).to.be.closeTo(0.111, DOUBLE_EPSILON); @@ -175,9 +188,9 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { await withTestSetup(async () => { await writeInitialData({ sum: 'overwrite' }); - const batch = docRef.firestore.batch(); - batch.update(docRef, 'sum', FieldValue.increment(1)); - batch.update(docRef, 'sum', FieldValue.increment(1)); + const batch = writeBatch(db); + batch.update(docRef, 'sum', increment(1)); + batch.update(docRef, 'sum', increment(1)); await batch.commit(); await expectLocalAndRemoteValue(2); @@ -188,10 +201,10 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { await withTestSetup(async () => { await writeInitialData({ sum: 'overwrite' }); - const batch = docRef.firestore.batch(); - batch.update(docRef, 'sum', FieldValue.increment(1)); - batch.update(docRef, 'sum', FieldValue.delete()); - batch.update(docRef, 'sum', FieldValue.increment(3)); + const batch = writeBatch(db); + batch.update(docRef, 'sum', increment(1)); + batch.update(docRef, 'sum', deleteField()); + batch.update(docRef, 'sum', increment(3)); await batch.commit(); await expectLocalAndRemoteValue(3); @@ -205,17 +218,17 @@ apiDescribe('Numeric Transforms:', (persistence: boolean) => { // In our original code, a NumericIncrementTransformOperation could cause us to decode the // ServerTimestamp as part of a PatchMutation, which triggered an assertion failure. await withTestSetup(async () => { - await docRef.firestore.disableNetwork(); + await disableNetwork(db); // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.set({ val: FieldValue.serverTimestamp() }); + setDoc(docRef, { val: serverTimestamp() }); let snap = await accumulator.awaitLocalEvent(); expect(snap.get('val', { serverTimestamps: 'estimate' })).to.not.be.null; // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.set({ val: FieldValue.increment(1) }); + setDoc(docRef, { val: increment(1) }); snap = await accumulator.awaitLocalEvent(); expect(snap.get('val')).to.equal(1); - await docRef.firestore.enableNetwork(); + await enableNetwork(db); snap = await accumulator.awaitRemoteEvent(); expect(snap.get('val')).to.equal(1); diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index a971b2946c0..703bd90b03b 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -15,13 +15,41 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { addEqualityMatcher } from '../../util/equality_matcher'; import { Deferred } from '../../util/promise'; import { EventsAccumulator } from '../util/events_accumulator'; -import * as firebaseExport from '../util/firebase_export'; +import { + addDoc, + Bytes, + collection, + collectionGroup, + deleteDoc, + disableNetwork, + doc, + DocumentChange, + DocumentChangeType, + documentId, + enableNetwork, + endAt, + endBefore, + GeoPoint, + getDocs, + limit, + limitToLast, + onSnapshot, + orderBy, + query, + QuerySnapshot, + setDoc, + startAfter, + startAt, + Timestamp, + updateDoc, + where, + writeBatch +} from '../util/firebase_export'; import { apiDescribe, toChangesArray, @@ -30,11 +58,6 @@ import { withTestDb } from '../util/helpers'; -const Blob = firebaseExport.Blob; -const FieldPath = firebaseExport.FieldPath; -const GeoPoint = firebaseExport.GeoPoint; -const Timestamp = firebaseExport.Timestamp; - apiDescribe('Queries', (persistence: boolean) => { addEqualityMatcher(); @@ -45,12 +68,9 @@ apiDescribe('Queries', (persistence: boolean) => { c: { k: 'c' } }; return withTestCollection(persistence, testDocs, collection => { - return collection - .limit(2) - .get() - .then(docs => { - expect(toDataArray(docs)).to.deep.equal([{ k: 'a' }, { k: 'b' }]); - }); + return getDocs(query(collection, limit(2))).then(docs => { + expect(toDataArray(docs)).to.deep.equal([{ k: 'a' }, { k: 'b' }]); + }); }); }); @@ -58,7 +78,9 @@ apiDescribe('Queries', (persistence: boolean) => { return withTestCollection(persistence, {}, async collection => { const expectedError = 'limitToLast() queries require specifying at least one orderBy() clause'; - expect(() => collection.limitToLast(2).get()).to.throw(expectedError); + expect(() => getDocs(query(collection, limitToLast(2)))).to.throw( + expectedError + ); }); }); @@ -70,16 +92,14 @@ apiDescribe('Queries', (persistence: boolean) => { d: { k: 'd', sort: 2 } }; return withTestCollection(persistence, testDocs, collection => { - return collection - .orderBy('sort', 'desc') - .limit(2) - .get() - .then(docs => { + return getDocs(query(collection, orderBy('sort', 'desc'), limit(2))).then( + docs => { expect(toDataArray(docs)).to.deep.equal([ { k: 'd', sort: 2 }, { k: 'c', sort: 1 } ]); - }); + } + ); }); }); @@ -91,16 +111,14 @@ apiDescribe('Queries', (persistence: boolean) => { d: { k: 'd', sort: 2 } }; return withTestCollection(persistence, testDocs, collection => { - return collection - .orderBy('sort', 'desc') - .limitToLast(2) - .get() - .then(docs => { - expect(toDataArray(docs)).to.deep.equal([ - { k: 'b', sort: 1 }, - { k: 'a', sort: 0 } - ]); - }); + return getDocs( + query(collection, orderBy('sort', 'desc'), limitToLast(2)) + ).then(docs => { + expect(toDataArray(docs)).to.deep.equal([ + { k: 'b', sort: 1 }, + { k: 'a', sort: 0 } + ]); + }); }); }); @@ -112,11 +130,11 @@ apiDescribe('Queries', (persistence: boolean) => { d: { k: 'd', sort: 2 } }; return withTestCollection(persistence, testDocs, async collection => { - const storeEvent = new EventsAccumulator(); - collection - .orderBy('sort', 'desc') - .limitToLast(2) - .onSnapshot(storeEvent.storeEvent); + const storeEvent = new EventsAccumulator(); + onSnapshot( + query(collection, orderBy('sort', 'desc'), limitToLast(2)), + storeEvent.storeEvent + ); let snapshot = await storeEvent.awaitEvent(); expect(toDataArray(snapshot)).to.deep.equal([ @@ -124,7 +142,7 @@ apiDescribe('Queries', (persistence: boolean) => { { k: 'a', sort: 0 } ]); - await collection.add({ k: 'e', sort: -1 }); + await addDoc(collection, { k: 'e', sort: -1 }); snapshot = await storeEvent.awaitEvent(); expect(toDataArray(snapshot)).to.deep.equal([ { k: 'a', sort: 0 }, @@ -148,19 +166,18 @@ apiDescribe('Queries', (persistence: boolean) => { }; return withTestCollection(persistence, testDocs, async collection => { // Setup `limit` query - const storeLimitEvent = new EventsAccumulator(); - let limitUnlisten = collection - .orderBy('sort', 'asc') - .limit(2) - .onSnapshot(storeLimitEvent.storeEvent); + const storeLimitEvent = new EventsAccumulator(); + const limitUnlisten = onSnapshot( + query(collection, orderBy('sort', 'asc'), limit(2)), + storeLimitEvent.storeEvent + ); // Setup mirroring `limitToLast` query - const storeLimitToLastEvent = - new EventsAccumulator(); - let limitToLastUnlisten = collection - .orderBy('sort', 'desc') - .limitToLast(2) - .onSnapshot(storeLimitToLastEvent.storeEvent); + const storeLimitToLastEvent = new EventsAccumulator(); + const limitToLastUnlisten = onSnapshot( + query(collection, orderBy('sort', 'desc'), limitToLast(2)), + storeLimitToLastEvent.storeEvent + ); // Verify both queries get expected results. let snapshot = await storeLimitEvent.awaitEvent(); @@ -176,10 +193,10 @@ apiDescribe('Queries', (persistence: boolean) => { // Unlisten then relisten limit query. limitUnlisten(); - limitUnlisten = collection - .orderBy('sort', 'asc') - .limit(2) - .onSnapshot(storeLimitEvent.storeEvent); + onSnapshot( + query(collection, orderBy('sort', 'asc'), limit(2)), + storeLimitEvent.storeEvent + ); // Verify `limit` query still works. snapshot = await storeLimitEvent.awaitEvent(); @@ -189,7 +206,7 @@ apiDescribe('Queries', (persistence: boolean) => { ]); // Add a document that would change the result set. - await collection.add({ k: 'e', sort: -1 }); + await addDoc(collection, { k: 'e', sort: -1 }); // Verify both queries get expected results. snapshot = await storeLimitEvent.awaitEvent(); @@ -205,11 +222,11 @@ apiDescribe('Queries', (persistence: boolean) => { // Unlisten to limitToLast, update a doc, then relisten limitToLast. limitToLastUnlisten(); - await collection.doc('a').update({ k: 'a', sort: -2 }); - limitToLastUnlisten = collection - .orderBy('sort', 'desc') - .limitToLast(2) - .onSnapshot(storeLimitToLastEvent.storeEvent); + await updateDoc(doc(collection, 'a'), { k: 'a', sort: -2 }); + onSnapshot( + query(collection, orderBy('sort', 'desc'), limitToLast(2)), + storeLimitToLastEvent.storeEvent + ); // Verify both queries get expected results. snapshot = await storeLimitEvent.awaitEvent(); @@ -250,19 +267,17 @@ apiDescribe('Queries', (persistence: boolean) => { } }; return withTestCollection(persistence, testDocs, coll => { - return coll - .where('foo', '>', 21.0) - .orderBy('foo', 'desc') - .get() - .then(docs => { - expect(docs.docs.map(d => d.id)).to.deep.equal([ - 'g', - 'f', - 'c', - 'b', - 'a' - ]); - }); + return getDocs( + query(coll, where('foo', '>', 21.0), orderBy('foo', 'desc')) + ).then(docs => { + expect(docs.docs.map(d => d.id)).to.deep.equal([ + 'g', + 'f', + 'c', + 'b', + 'a' + ]); + }); }); }); @@ -273,13 +288,11 @@ apiDescribe('Queries', (persistence: boolean) => { c: { null: false, nan: NaN } }; return withTestCollection(persistence, testDocs, coll => { - return coll - .where('null', '==', null) - .where('nan', '==', NaN) - .get() - .then(docs => { - expect(toDataArray(docs)).to.deep.equal([{ null: null, nan: NaN }]); - }); + return getDocs( + query(coll, where('null', '==', null), where('nan', '==', NaN)) + ).then(docs => { + expect(toDataArray(docs)).to.deep.equal([{ null: null, nan: NaN }]); + }); }); }); @@ -289,26 +302,23 @@ apiDescribe('Queries', (persistence: boolean) => { b: { inf: -Infinity } }; return withTestCollection(persistence, testDocs, coll => { - return coll - .where('inf', '==', Infinity) - .get() - .then(docs => { - expect(toDataArray(docs)).to.deep.equal([{ inf: Infinity }]); - }); + return getDocs(query(coll, where('inf', '==', Infinity))).then(docs => { + expect(toDataArray(docs)).to.deep.equal([{ inf: Infinity }]); + }); }); }); it('will not get metadata only updates', () => { const testDocs = { a: { v: 'a' }, b: { v: 'b' } }; return withTestCollection(persistence, testDocs, coll => { - const storeEvent = new EventsAccumulator(); + const storeEvent = new EventsAccumulator(); let unlisten: (() => void) | null = null; return Promise.all([ - coll.doc('a').set({ v: 'a' }), - coll.doc('b').set({ v: 'b' }) + setDoc(doc(coll, 'a'), { v: 'a' }), + setDoc(doc(coll, 'b'), { v: 'b' }) ]) .then(() => { - unlisten = coll.onSnapshot(storeEvent.storeEvent); + unlisten = onSnapshot(coll, storeEvent.storeEvent); return storeEvent.awaitEvent(); }) .then(querySnap => { @@ -316,11 +326,9 @@ apiDescribe('Queries', (persistence: boolean) => { { v: 'a' }, { v: 'b' } ]); - return coll.doc('a').set({ v: 'a1' }); - }) - .then(() => { - return storeEvent.awaitEvent(); + return setDoc(doc(coll, 'a'), { v: 'a1' }); }) + .then(() => storeEvent.awaitEvent()) .then(querySnap => { expect(toDataArray(querySnap)).to.deep.equal([ { v: 'a1' }, @@ -328,9 +336,7 @@ apiDescribe('Queries', (persistence: boolean) => { ]); return storeEvent.assertNoAdditionalEvents(); }) - .then(() => { - unlisten!(); - }); + .then(() => unlisten!()); }); }); @@ -341,8 +347,11 @@ apiDescribe('Queries', (persistence: boolean) => { 'c': { 'order': 3 } }; await withTestCollection(persistence, testDocs, async coll => { - const accumulator = new EventsAccumulator(); - const unlisten = coll.orderBy('order').onSnapshot(accumulator.storeEvent); + const accumulator = new EventsAccumulator(); + const unlisten = onSnapshot( + query(coll, orderBy('order')), + accumulator.storeEvent + ); await accumulator .awaitEvent() .then(querySnapshot => { @@ -352,14 +361,14 @@ apiDescribe('Queries', (persistence: boolean) => { verifyDocumentChange(changes[1], 'b', -1, 1, 'added'); verifyDocumentChange(changes[2], 'c', -1, 2, 'added'); }) - .then(() => coll.doc('b').set({ order: 4 })) + .then(() => setDoc(doc(coll, 'b'), { order: 4 })) .then(() => accumulator.awaitEvent()) .then(querySnapshot => { const changes = querySnapshot.docChanges(); expect(changes.length).to.equal(1); verifyDocumentChange(changes[0], 'b', 1, 2, 'modified'); }) - .then(() => coll.doc('c').delete()) + .then(() => deleteDoc(doc(coll, 'c'))) .then(() => accumulator.awaitEvent()) .then(querySnapshot => { const changes = querySnapshot.docChanges(); @@ -374,10 +383,11 @@ apiDescribe('Queries', (persistence: boolean) => { it('can listen for the same query with different options', () => { const testDocs = { a: { v: 'a' }, b: { v: 'b' } }; return withTestCollection(persistence, testDocs, coll => { - const storeEvent = new EventsAccumulator(); - const storeEventFull = new EventsAccumulator(); - const unlisten1 = coll.onSnapshot(storeEvent.storeEvent); - const unlisten2 = coll.onSnapshot( + const storeEvent = new EventsAccumulator(); + const storeEventFull = new EventsAccumulator(); + const unlisten1 = onSnapshot(coll, storeEvent.storeEvent); + const unlisten2 = onSnapshot( + coll, { includeMetadataChanges: true }, storeEventFull.storeEvent ); @@ -401,7 +411,7 @@ apiDescribe('Queries', (persistence: boolean) => { // was served from cache. await storeEventFull.awaitEvent(); } - return coll.doc('a').set({ v: 'a1' }); + return setDoc(doc(coll, 'a'), { v: 'a1' }); }) .then(() => { return storeEventFull.awaitEvents(2); @@ -434,7 +444,7 @@ apiDescribe('Queries', (persistence: boolean) => { }) .then(() => { storeEvent.allowAdditionalEvents(); - return coll.doc('b').set({ v: 'b1' }); + return setDoc(doc(coll, 'b'), { v: 'b1' }); }) .then(() => { return storeEvent.awaitEvent(); @@ -488,8 +498,8 @@ apiDescribe('Queries', (persistence: boolean) => { }; return withTestCollection(persistence, testDocs, coll => { // Make sure to issue the queries in parallel - const docs1Promise = coll.where('date', '>', date1).get(); - const docs2Promise = coll.where('date', '>', date2).get(); + const docs1Promise = getDocs(query(coll, where('date', '>', date1))); + const docs2Promise = getDocs(query(coll, where('date', '>', date2))); return Promise.all([docs1Promise, docs2Promise]).then(results => { const docs1 = results[0]; @@ -514,17 +524,18 @@ apiDescribe('Queries', (persistence: boolean) => { '4': { sort: 3, filter: false, key: '4' } }; return withTestCollection(persistence, testDocs, coll => { - const query = coll.where('key', '<', '4'); - const accum = new EventsAccumulator(); + const query1 = query(coll, where('key', '<', '4')); + const accum = new EventsAccumulator(); let unlisten2: () => void; - const unlisten1 = query.onSnapshot(result => { + const unlisten1 = onSnapshot(query1, result => { expect(toDataArray(result)).to.deep.equal([ testDocs[1], testDocs[2], testDocs[3] ]); - const query2 = coll.where('filter', '==', true); - unlisten2 = query2.onSnapshot( + const query2 = query(coll, where('filter', '==', true)); + unlisten2 = onSnapshot( + query2, { includeMetadataChanges: true }, @@ -556,8 +567,9 @@ apiDescribe('Queries', (persistence: boolean) => { foo: { a: 'b', v: 2 } }; return withTestCollection(persistence, initialDoc, async coll => { - const accum = new EventsAccumulator(); - const unlisten = coll.onSnapshot( + const accum = new EventsAccumulator(); + const unlisten = onSnapshot( + coll, { includeMetadataChanges: true }, accum.storeEvent ); @@ -567,7 +579,7 @@ apiDescribe('Queries', (persistence: boolean) => { expect(toDataArray(results1)).to.deep.equal([initialDoc['foo']]); }); // eslint-disable-next-line @typescript-eslint/no-floating-promises - coll.doc('foo').set(modifiedDoc['foo']); + setDoc(doc(coll, 'foo'), modifiedDoc['foo']); await accum.awaitEvents(2).then(events => { const results1 = events[0]; @@ -595,16 +607,13 @@ apiDescribe('Queries', (persistence: boolean) => { return withTestCollection(persistence, testDocs, coll => { // Ideally this would be descending to validate it's different than // the default, but that requires an extra index - return coll - .orderBy(FieldPath.documentId()) - .get() - .then(docs => { - expect(toDataArray(docs)).to.deep.equal([ - testDocs['a'], - testDocs['b'], - testDocs['c'] - ]); - }); + return getDocs(query(coll, orderBy(documentId()))).then(docs => { + expect(toDataArray(docs)).to.deep.equal([ + testDocs['a'], + testDocs['b'], + testDocs['c'] + ]); + }); }); }); @@ -616,15 +625,16 @@ apiDescribe('Queries', (persistence: boolean) => { bb: { key: 'bb' } }; return withTestCollection(persistence, testDocs, coll => { - return coll - .where(FieldPath.documentId(), '==', 'ab') - .get() + return getDocs(query(coll, where(documentId(), '==', 'ab'))) .then(docs => { expect(toDataArray(docs)).to.deep.equal([testDocs['ab']]); - return coll - .where(FieldPath.documentId(), '>', 'aa') - .where(FieldPath.documentId(), '<=', 'ba') - .get(); + return getDocs( + query( + coll, + where(documentId(), '>', 'aa'), + where(documentId(), '<=', 'ba') + ) + ); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([ @@ -643,15 +653,16 @@ apiDescribe('Queries', (persistence: boolean) => { bb: { key: 'bb' } }; return withTestCollection(persistence, testDocs, coll => { - return coll - .where(FieldPath.documentId(), '==', coll.doc('ab')) - .get() + return getDocs(query(coll, where(documentId(), '==', doc(coll, 'ab')))) .then(docs => { expect(toDataArray(docs)).to.deep.equal([testDocs['ab']]); - return coll - .where(FieldPath.documentId(), '>', coll.doc('aa')) - .where(FieldPath.documentId(), '<=', coll.doc('ba')) - .get(); + return getDocs( + query( + coll, + where(documentId(), '>', doc(coll, 'aa')), + where(documentId(), '<=', doc(coll, 'ba')) + ) + ); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([ @@ -663,10 +674,11 @@ apiDescribe('Queries', (persistence: boolean) => { }); it('can query while reconnecting to network', () => { - return withTestCollection(persistence, /* docs= */ {}, coll => { + return withTestCollection(persistence, /* docs= */ {}, (coll, db) => { const deferred = new Deferred(); - const unregister = coll.onSnapshot( + const unregister = onSnapshot( + coll, { includeMetadataChanges: true }, snapshot => { if (!snapshot.empty && !snapshot.metadata.fromCache) { @@ -674,12 +686,9 @@ apiDescribe('Queries', (persistence: boolean) => { } } ); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - coll.firestore.disableNetwork().then(() => { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - coll.doc().set({ a: 1 }); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - coll.firestore.enableNetwork(); + void disableNetwork(db).then(() => { + void setDoc(doc(coll), { a: 1 }); + void enableNetwork(db); }); return deferred.promise.then(unregister); @@ -687,10 +696,10 @@ apiDescribe('Queries', (persistence: boolean) => { }); it('trigger with isFromCache=true when offline', () => { - return withTestCollection(persistence, { a: { foo: 1 } }, coll => { - const firestore = coll.firestore; - const accum = new EventsAccumulator(); - const unregister = coll.onSnapshot( + return withTestCollection(persistence, { a: { foo: 1 } }, (coll, db) => { + const accum = new EventsAccumulator(); + const unregister = onSnapshot( + coll, { includeMetadataChanges: true }, accum.storeEvent ); @@ -704,13 +713,13 @@ apiDescribe('Queries', (persistence: boolean) => { ]); expect(querySnap.metadata.fromCache).to.be.false; }) - .then(() => firestore.disableNetwork()) + .then(() => disableNetwork(db)) .then(() => accum.awaitEvent()) .then(querySnap => { // offline event with fromCache = true expect(querySnap.metadata.fromCache).to.be.true; }) - .then(() => firestore.enableNetwork()) + .then(() => enableNetwork(db)) .then(() => accum.awaitEvent()) .then(querySnap => { // back online event with fromCache = false @@ -742,11 +751,13 @@ apiDescribe('Queries', (persistence: boolean) => { delete expected.c; delete expected.i; delete expected.j; - const snapshot = await coll.where('zip', '!=', 98101).get(); + const snapshot = await getDocs(query(coll, where('zip', '!=', 98101))); expect(toDataArray(snapshot)).to.deep.equal(Object.values(expected)); // With objects. - const snapshot2 = await coll.where('zip', '!=', { code: 500 }).get(); + const snapshot2 = await getDocs( + query(coll, where('zip', '!=', { code: 500 })) + ); expected = { ...testDocs }; delete expected.h; delete expected.i; @@ -754,14 +765,16 @@ apiDescribe('Queries', (persistence: boolean) => { expect(toDataArray(snapshot2)).to.deep.equal(Object.values(expected)); // With null. - const snapshot3 = await coll.where('zip', '!=', null).get(); + const snapshot3 = await getDocs(query(coll, where('zip', '!=', null))); expected = { ...testDocs }; delete expected.i; delete expected.j; expect(toDataArray(snapshot3)).to.deep.equal(Object.values(expected)); // With NaN. - const snapshot4 = await coll.where('zip', '!=', Number.NaN).get(); + const snapshot4 = await getDocs( + query(coll, where('zip', '!=', Number.NaN)) + ); expected = { ...testDocs }; delete expected.a; delete expected.i; @@ -778,9 +791,9 @@ apiDescribe('Queries', (persistence: boolean) => { bb: { key: 'bb' } }; await withTestCollection(persistence, testDocs, async coll => { - const snapshot = await coll - .where(FieldPath.documentId(), '!=', 'aa') - .get(); + const snapshot = await getDocs( + query(coll, where(documentId(), '!=', 'aa')) + ); expect(toDataArray(snapshot)).to.deep.equal([ { key: 'ab' }, @@ -802,7 +815,9 @@ apiDescribe('Queries', (persistence: boolean) => { await withTestCollection(persistence, testDocs, async coll => { // Search for 42 - const snapshot = await coll.where('array', 'array-contains', 42).get(); + const snapshot = await getDocs( + query(coll, where('array', 'array-contains', 42)) + ); expect(toDataArray(snapshot)).to.deep.equal([ { array: [42] }, { array: ['a', 42, 'c'] }, @@ -812,13 +827,15 @@ apiDescribe('Queries', (persistence: boolean) => { // NOTE: The backend doesn't currently support null, NaN, objects, or // arrays, so there isn't much of anything else interesting to test. // With null. - const snapshot3 = await coll.where('zip', 'array-contains', null).get(); + const snapshot3 = await getDocs( + query(coll, where('zip', 'array-contains', null)) + ); expect(toDataArray(snapshot3)).to.deep.equal([]); // With NaN. - const snapshot4 = await coll - .where('zip', 'array-contains', Number.NaN) - .get(); + const snapshot4 = await getDocs( + query(coll, where('zip', 'array-contains', Number.NaN)) + ); expect(toDataArray(snapshot4)).to.deep.equal([]); }); }); @@ -837,9 +854,9 @@ apiDescribe('Queries', (persistence: boolean) => { }; await withTestCollection(persistence, testDocs, async coll => { - const snapshot = await coll - .where('zip', 'in', [98101, 98103, [98101, 98102]]) - .get(); + const snapshot = await getDocs( + query(coll, where('zip', 'in', [98101, 98103, [98101, 98102]])) + ); expect(toDataArray(snapshot)).to.deep.equal([ { zip: 98101 }, { zip: 98103 }, @@ -847,25 +864,31 @@ apiDescribe('Queries', (persistence: boolean) => { ]); // With objects. - const snapshot2 = await coll.where('zip', 'in', [{ code: 500 }]).get(); + const snapshot2 = await getDocs( + query(coll, where('zip', 'in', [{ code: 500 }])) + ); expect(toDataArray(snapshot2)).to.deep.equal([{ zip: { code: 500 } }]); // With null. - const snapshot3 = await coll.where('zip', 'in', [null]).get(); + const snapshot3 = await getDocs(query(coll, where('zip', 'in', [null]))); expect(toDataArray(snapshot3)).to.deep.equal([]); // With null and a value. - const snapshot4 = await coll.where('zip', 'in', [98101, null]).get(); + const snapshot4 = await getDocs( + query(coll, where('zip', 'in', [98101, null])) + ); expect(toDataArray(snapshot4)).to.deep.equal([{ zip: 98101 }]); // With NaN. - const snapshot5 = await coll.where('zip', 'in', [Number.NaN]).get(); + const snapshot5 = await getDocs( + query(coll, where('zip', 'in', [Number.NaN])) + ); expect(toDataArray(snapshot5)).to.deep.equal([]); // With NaN and a value. - const snapshot6 = await coll - .where('zip', 'in', [98101, Number.NaN]) - .get(); + const snapshot6 = await getDocs( + query(coll, where('zip', 'in', [98101, Number.NaN])) + ); expect(toDataArray(snapshot6)).to.deep.equal([{ zip: 98101 }]); }); }); @@ -878,9 +901,9 @@ apiDescribe('Queries', (persistence: boolean) => { bb: { key: 'bb' } }; await withTestCollection(persistence, testDocs, async coll => { - const snapshot = await coll - .where(FieldPath.documentId(), 'in', ['aa', 'ab']) - .get(); + const snapshot = await getDocs( + query(coll, where(documentId(), 'in', ['aa', 'ab'])) + ); expect(toDataArray(snapshot)).to.deep.equal([ { key: 'aa' }, @@ -913,15 +936,15 @@ apiDescribe('Queries', (persistence: boolean) => { delete expected.f; delete expected.i; delete expected.j; - const snapshot = await coll - .where('zip', 'not-in', [98101, 98103, [98101, 98102]]) - .get(); + const snapshot = await getDocs( + query(coll, where('zip', 'not-in', [98101, 98103, [98101, 98102]])) + ); expect(toDataArray(snapshot)).to.deep.equal(Object.values(expected)); // With objects. - const snapshot2 = await coll - .where('zip', 'not-in', [{ code: 500 }]) - .get(); + const snapshot2 = await getDocs( + query(coll, where('zip', 'not-in', [{ code: 500 }])) + ); expected = { ...testDocs }; delete expected.h; delete expected.i; @@ -929,11 +952,15 @@ apiDescribe('Queries', (persistence: boolean) => { expect(toDataArray(snapshot2)).to.deep.equal(Object.values(expected)); // With null. - const snapshot3 = await coll.where('zip', 'not-in', [null]).get(); + const snapshot3 = await getDocs( + query(coll, where('zip', 'not-in', [null])) + ); expect(toDataArray(snapshot3)).to.deep.equal([]); // With NaN. - const snapshot4 = await coll.where('zip', 'not-in', [Number.NaN]).get(); + const snapshot4 = await getDocs( + query(coll, where('zip', 'not-in', [Number.NaN])) + ); expected = { ...testDocs }; delete expected.a; delete expected.i; @@ -941,9 +968,9 @@ apiDescribe('Queries', (persistence: boolean) => { expect(toDataArray(snapshot4)).to.deep.equal(Object.values(expected)); // With NaN and a number. - const snapshot5 = await coll - .where('zip', 'not-in', [Number.NaN, 98101]) - .get(); + const snapshot5 = await getDocs( + query(coll, where('zip', 'not-in', [Number.NaN, 98101])) + ); expected = { ...testDocs }; delete expected.a; delete expected.c; @@ -961,9 +988,9 @@ apiDescribe('Queries', (persistence: boolean) => { bb: { key: 'bb' } }; await withTestCollection(persistence, testDocs, async coll => { - const snapshot = await coll - .where(FieldPath.documentId(), 'not-in', ['aa', 'ab']) - .get(); + const snapshot = await getDocs( + query(coll, where(documentId(), 'not-in', ['aa', 'ab'])) + ); expect(toDataArray(snapshot)).to.deep.equal([ { key: 'ba' }, @@ -986,9 +1013,9 @@ apiDescribe('Queries', (persistence: boolean) => { }; await withTestCollection(persistence, testDocs, async coll => { - const snapshot = await coll - .where('array', 'array-contains-any', [42, 43]) - .get(); + const snapshot = await getDocs( + query(coll, where('array', 'array-contains-any', [42, 43])) + ); expect(toDataArray(snapshot)).to.deep.equal([ { array: [42] }, { array: ['a', 42, 'c'] }, @@ -997,63 +1024,63 @@ apiDescribe('Queries', (persistence: boolean) => { ]); // With objects. - const snapshot2 = await coll - .where('array', 'array-contains-any', [{ a: 42 }]) - .get(); + const snapshot2 = await getDocs( + query(coll, where('array', 'array-contains-any', [{ a: 42 }])) + ); expect(toDataArray(snapshot2)).to.deep.equal([{ array: [{ a: 42 }] }]); // With null. - const snapshot3 = await coll - .where('array', 'array-contains-any', [null]) - .get(); + const snapshot3 = await getDocs( + query(coll, where('array', 'array-contains-any', [null])) + ); expect(toDataArray(snapshot3)).to.deep.equal([]); // With null and a value. - const snapshot4 = await coll - .where('array', 'array-contains-any', [43, null]) - .get(); + const snapshot4 = await getDocs( + query(coll, where('array', 'array-contains-any', [43, null])) + ); expect(toDataArray(snapshot4)).to.deep.equal([{ array: [43] }]); // With NaN. - const snapshot5 = await coll - .where('array', 'array-contains-any', [Number.NaN]) - .get(); + const snapshot5 = await getDocs( + query(coll, where('array', 'array-contains-any', [Number.NaN])) + ); expect(toDataArray(snapshot5)).to.deep.equal([]); // With NaN and a value. - const snapshot6 = await coll - .where('array', 'array-contains-any', [43, Number.NaN]) - .get(); + const snapshot6 = await getDocs( + query(coll, where('array', 'array-contains-any', [43, Number.NaN])) + ); expect(toDataArray(snapshot6)).to.deep.equal([{ array: [43] }]); }); }); it('can query collection groups', async () => { await withTestDb(persistence, async db => { - // Use .doc() to get a random collection group name to use but ensure it starts with 'b' for - // predictable ordering. - const collectionGroup = 'b' + db.collection('foo').doc().id; + // Use doc() to get a random collection group name to use but ensure it + // starts with 'b' for predictable ordering. + const cg = 'b' + doc(collection(db, 'foo')).id; const docPaths = [ - `abc/123/${collectionGroup}/cg-doc1`, - `abc/123/${collectionGroup}/cg-doc2`, - `${collectionGroup}/cg-doc3`, - `${collectionGroup}/cg-doc4`, - `def/456/${collectionGroup}/cg-doc5`, - `${collectionGroup}/virtual-doc/nested-coll/not-cg-doc`, - `x${collectionGroup}/not-cg-doc`, - `${collectionGroup}x/not-cg-doc`, - `abc/123/${collectionGroup}x/not-cg-doc`, - `abc/123/x${collectionGroup}/not-cg-doc`, - `abc/${collectionGroup}` + `abc/123/${cg}/cg-doc1`, + `abc/123/${cg}/cg-doc2`, + `${cg}/cg-doc3`, + `${cg}/cg-doc4`, + `def/456/${cg}/cg-doc5`, + `${cg}/virtual-doc/nested-coll/not-cg-doc`, + `x${cg}/not-cg-doc`, + `${cg}x/not-cg-doc`, + `abc/123/${cg}x/not-cg-doc`, + `abc/123/x${cg}/not-cg-doc`, + `abc/${cg}` ]; - const batch = db.batch(); + const batch = writeBatch(db); for (const docPath of docPaths) { - batch.set(db.doc(docPath), { x: 1 }); + batch.set(doc(db, docPath), { x: 1 }); } await batch.commit(); - const querySnapshot = await db.collectionGroup(collectionGroup).get(); + const querySnapshot = await getDocs(collectionGroup(db, cg)); expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ 'cg-doc1', 'cg-doc2', @@ -1066,103 +1093,111 @@ apiDescribe('Queries', (persistence: boolean) => { it('can query collection groups with startAt / endAt by arbitrary documentId', async () => { await withTestDb(persistence, async db => { - // Use .doc() to get a random collection group name to use but ensure it starts with 'b' for - // predictable ordering. - const collectionGroup = 'b' + db.collection('foo').doc().id; + // Use doc() to get a random collection group name to use but ensure it + // starts with 'b' for predictable ordering. + const cg = 'b' + doc(collection(db, 'foo')).id; const docPaths = [ - `a/a/${collectionGroup}/cg-doc1`, - `a/b/a/b/${collectionGroup}/cg-doc2`, - `a/b/${collectionGroup}/cg-doc3`, - `a/b/c/d/${collectionGroup}/cg-doc4`, - `a/c/${collectionGroup}/cg-doc5`, - `${collectionGroup}/cg-doc6`, + `a/a/${cg}/cg-doc1`, + `a/b/a/b/${cg}/cg-doc2`, + `a/b/${cg}/cg-doc3`, + `a/b/c/d/${cg}/cg-doc4`, + `a/c/${cg}/cg-doc5`, + `${cg}/cg-doc6`, `a/b/nope/nope` ]; - const batch = db.batch(); + const batch = writeBatch(db); for (const docPath of docPaths) { - batch.set(db.doc(docPath), { x: 1 }); + batch.set(doc(db, docPath), { x: 1 }); } await batch.commit(); - let querySnapshot = await db - .collectionGroup(collectionGroup) - .orderBy(FieldPath.documentId()) - .startAt(`a/b`) - .endAt('a/b0') - .get(); + let querySnapshot = await getDocs( + query( + collectionGroup(db, cg), + orderBy(documentId()), + startAt(`a/b`), + endAt('a/b0') + ) + ); expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ 'cg-doc2', 'cg-doc3', 'cg-doc4' ]); - querySnapshot = await db - .collectionGroup(collectionGroup) - .orderBy(FieldPath.documentId()) - .startAfter('a/b') - .endBefore(`a/b/${collectionGroup}/cg-doc3`) - .get(); + querySnapshot = await getDocs( + query( + collectionGroup(db, cg), + orderBy(documentId()), + startAfter('a/b'), + endBefore(`a/b/${cg}/cg-doc3`) + ) + ); expect(querySnapshot.docs.map(d => d.id)).to.deep.equal(['cg-doc2']); }); }); it('can query collection groups with where filters on arbitrary documentId', async () => { await withTestDb(persistence, async db => { - // Use .doc() to get a random collection group name to use but ensure it starts with 'b' for - // predictable ordering. - const collectionGroup = 'b' + db.collection('foo').doc().id; + // Use doc() to get a random collection group name to use but ensure it + // starts with 'b' for predictable ordering. + const cg = 'b' + doc(collection(db, 'foo')).id; const docPaths = [ - `a/a/${collectionGroup}/cg-doc1`, - `a/b/a/b/${collectionGroup}/cg-doc2`, - `a/b/${collectionGroup}/cg-doc3`, - `a/b/c/d/${collectionGroup}/cg-doc4`, - `a/c/${collectionGroup}/cg-doc5`, - `${collectionGroup}/cg-doc6`, + `a/a/${cg}/cg-doc1`, + `a/b/a/b/${cg}/cg-doc2`, + `a/b/${cg}/cg-doc3`, + `a/b/c/d/${cg}/cg-doc4`, + `a/c/${cg}/cg-doc5`, + `${cg}/cg-doc6`, `a/b/nope/nope` ]; - const batch = db.batch(); + const batch = writeBatch(db); for (const docPath of docPaths) { - batch.set(db.doc(docPath), { x: 1 }); + batch.set(doc(db, docPath), { x: 1 }); } await batch.commit(); - let querySnapshot = await db - .collectionGroup(collectionGroup) - .where(FieldPath.documentId(), '>=', `a/b`) - .where(FieldPath.documentId(), '<=', 'a/b0') - .get(); + let querySnapshot = await getDocs( + query( + collectionGroup(db, cg), + where(documentId(), '>=', `a/b`), + where(documentId(), '<=', 'a/b0') + ) + ); expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ 'cg-doc2', 'cg-doc3', 'cg-doc4' ]); - querySnapshot = await db - .collectionGroup(collectionGroup) - .where(FieldPath.documentId(), '>', `a/b`) - .where(FieldPath.documentId(), '<', `a/b/${collectionGroup}/cg-doc3`) - .get(); + querySnapshot = await getDocs( + query( + collectionGroup(db, cg), + where(documentId(), '>', `a/b`), + where(documentId(), '<', `a/b/${cg}/cg-doc3`) + ) + ); expect(querySnapshot.docs.map(d => d.id)).to.deep.equal(['cg-doc2']); }); }); it('can query custom types', () => { - return withTestCollection(persistence, {}, async ref => { + return withTestCollection(persistence, {}, async (ref, db) => { const data = { - ref: ref.firestore.doc('f/c'), + ref: doc(db, 'f/c'), geoPoint: new GeoPoint(0, 0), - buffer: Blob.fromBase64String('Zm9v'), + buffer: Bytes.fromBase64String('Zm9v'), time: Timestamp.now(), array: [ - ref.firestore.doc('f/c'), + doc(db, 'f/c'), new GeoPoint(0, 0), - Blob.fromBase64String('Zm9v'), + Bytes.fromBase64String('Zm9v'), Timestamp.now() ] }; - await ref.add({ data }); + await addDoc(ref, { data }); // In https://github.com/firebase/firebase-js-sdk/issues/1524, a // customer was not able to unlisten from a query that contained a @@ -1171,11 +1206,11 @@ apiDescribe('Queries', (persistence: boolean) => { // for Queries created via the API layer versus Queries read from // persistence. To simulate this issue, we have to listen and unlisten // to the same query twice. - const query = ref.where('data', '==', data); + const query1 = query(ref, where('data', '==', data)); for (let i = 0; i < 2; ++i) { const deferred = new Deferred(); - const unsubscribe = query.onSnapshot(snapshot => { + const unsubscribe = onSnapshot(query1, snapshot => { expect(snapshot.size).to.equal(1); deferred.resolve(); }); @@ -1195,19 +1230,21 @@ apiDescribe('Queries', (persistence: boolean) => { }; return withTestCollection(persistence, testDocs, async coll => { - await coll.get(); // Populate the cache - const snapshot = await coll.where('map.nested', '==', 'foo').get(); + await getDocs(query(coll)); // Populate the cache + const snapshot = await getDocs( + query(coll, where('map.nested', '==', 'foo')) + ); expect(toDataArray(snapshot)).to.deep.equal([{ map: { nested: 'foo' } }]); }); }); }); function verifyDocumentChange( - change: firestore.DocumentChange, + change: DocumentChange, id: string, oldIndex: number, newIndex: number, - type: firestore.DocumentChangeType + type: DocumentChangeType ): void { expect(change.doc.id).to.equal(id); expect(change.type).to.equal(type); diff --git a/packages/firestore/test/integration/api/server_timestamp.test.ts b/packages/firestore/test/integration/api/server_timestamp.test.ts index 4c451e6a91a..6c04b8b7670 100644 --- a/packages/firestore/test/integration/api/server_timestamp.test.ts +++ b/packages/firestore/test/integration/api/server_timestamp.test.ts @@ -15,40 +15,51 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { EventsAccumulator } from '../util/events_accumulator'; -import * as firebaseExport from '../util/firebase_export'; +import { + disableNetwork, + DocumentReference, + DocumentSnapshot, + enableNetwork, + Firestore, + FirestoreError, + onSnapshot, + runTransaction, + serverTimestamp, + setDoc, + Timestamp, + updateDoc +} from '../util/firebase_export'; import { apiDescribe, withTestDoc } from '../util/helpers'; // Allow custom types for testing. // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyTestData = any; -const Timestamp = firebaseExport.Timestamp; -const FieldValue = firebaseExport.FieldValue; - apiDescribe('Server Timestamps', (persistence: boolean) => { // Data written in tests via set(). const setData = { a: 42, - when: FieldValue.serverTimestamp(), - deep: { when: FieldValue.serverTimestamp() } + when: serverTimestamp(), + deep: { when: serverTimestamp() } }; // base and update data used for update() tests. const initialData = { a: 42 }; const updateData = { - when: FieldValue.serverTimestamp(), - deep: { when: FieldValue.serverTimestamp() } + when: serverTimestamp(), + deep: { when: serverTimestamp() } }; // A document reference to read and write to. - let docRef: firestore.DocumentReference; + let docRef: DocumentReference; + + let firestore: Firestore; // Accumulator used to capture events during the test. - let accumulator: EventsAccumulator; + let accumulator: EventsAccumulator; // Listener registration for a listener maintained during the course of the // test. @@ -61,8 +72,7 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { /** Writes initialData and waits for the corresponding snapshot. */ function writeInitialData(): Promise { - return docRef - .set(initialData) + return setDoc(docRef, initialData) .then(() => accumulator.awaitEvent()) .then(initialDataSnap => { expect(initialDataSnap.data()).to.deep.equal(initialData); @@ -70,8 +80,8 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { } /** Verifies a snapshot containing setData but with resolved server timestamps. */ - function verifyTimestampsAreResolved(snap: firestore.DocumentSnapshot): void { - expect(snap.exists).to.equal(true); + function verifyTimestampsAreResolved(snap: DocumentSnapshot): void { + expect(snap.exists()).to.equal(true); const when = snap.get('when'); expect(when).to.be.an.instanceof(Timestamp); // Tolerate up to 60 seconds of clock skew between client and server @@ -86,16 +96,14 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { } /** Verifies a snapshot containing setData but with null for the timestamps. */ - function verifyTimestampsAreNull(snap: firestore.DocumentSnapshot): void { - expect(snap.exists).to.equal(true); + function verifyTimestampsAreNull(snap: DocumentSnapshot): void { + expect(snap.exists()).to.equal(true); expect(snap.data()).to.deep.equal(expectedDataWithTimestamp(null)); } /** Verifies a snapshot containing setData but with local estimates for server timestamps. */ - function verifyTimestampsAreEstimates( - snap: firestore.DocumentSnapshot - ): void { - expect(snap.exists).to.equal(true); + function verifyTimestampsAreEstimates(snap: DocumentSnapshot): void { + expect(snap.exists()).to.equal(true); const when = snap.get('when', { serverTimestamps: 'estimate' }); expect(when).to.be.an.instanceof(Timestamp); // Validate the rest of the document. @@ -109,12 +117,14 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { * up when done. */ function withTestSetup(test: () => Promise): Promise { - return withTestDoc(persistence, doc => { + return withTestDoc(persistence, (doc, db) => { // Set variables for use during test. docRef = doc; + firestore = db; - accumulator = new EventsAccumulator(); - unsubscribe = docRef.onSnapshot( + accumulator = new EventsAccumulator(); + unsubscribe = onSnapshot( + docRef, { includeMetadataChanges: true }, accumulator.storeEvent ); @@ -123,7 +133,7 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { return accumulator .awaitEvent() .then(docSnap => { - expect(docSnap.exists).to.equal(false); + expect(docSnap.exists()).to.equal(false); }) .then(() => test()) .then(() => { @@ -134,8 +144,7 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { it('work via set()', () => { return withTestSetup(() => { - return docRef - .set(setData) + return setDoc(docRef, setData) .then(() => accumulator.awaitLocalEvent()) .then(snapshot => verifyTimestampsAreNull(snapshot)) .then(() => accumulator.awaitRemoteEvent()) @@ -146,7 +155,7 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { it('work via update()', () => { return withTestSetup(() => { return writeInitialData() - .then(() => docRef.update(updateData)) + .then(() => updateDoc(docRef, updateData)) .then(() => accumulator.awaitLocalEvent()) .then(snapshot => verifyTimestampsAreNull(snapshot)) .then(() => accumulator.awaitRemoteEvent()) @@ -155,14 +164,13 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { }); it('work via transaction set()', () => { - return withTestSetup(() => { - return docRef.firestore - .runTransaction(async txn => { - txn.set(docRef, setData); - }) + return withTestSetup(() => + runTransaction(firestore, async txn => { + txn.set(docRef, setData); + }) .then(() => accumulator.awaitRemoteEvent()) - .then(snapshot => verifyTimestampsAreResolved(snapshot)); - }); + .then(snapshot => verifyTimestampsAreResolved(snapshot)) + ); }); it('work via transaction update()', () => { @@ -170,7 +178,7 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { return writeInitialData() .then(() => accumulator.awaitRemoteEvent()) .then(() => - docRef.firestore.runTransaction(async txn => { + runTransaction(firestore, async txn => { txn.update(docRef, updateData); }) ) @@ -182,7 +190,7 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { it('can return estimated value', () => { return withTestSetup(() => { return writeInitialData() - .then(() => docRef.update(updateData)) + .then(() => updateDoc(docRef, updateData)) .then(() => accumulator.awaitLocalEvent()) .then(snapshot => verifyTimestampsAreEstimates(snapshot)); }); @@ -193,7 +201,7 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { return writeInitialData() .then(() => // Change field 'a' from a number type to a server timestamp. - docRef.update('a', FieldValue.serverTimestamp()) + updateDoc(docRef, 'a', serverTimestamp()) ) .then(() => accumulator.awaitLocalEvent()) .then(snapshot => { @@ -208,14 +216,14 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { it('can return previous value through consecutive updates', () => { return withTestSetup(() => { return writeInitialData() - .then(() => docRef.firestore.disableNetwork()) + .then(() => disableNetwork(firestore)) .then(() => { // We set up two consecutive writes with server timestamps. // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.update('a', FieldValue.serverTimestamp()); + updateDoc(docRef, 'a', serverTimestamp()); // include b=1 to ensure there's a change resulting in a new snapshot. // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.update('a', FieldValue.serverTimestamp(), 'b', 1); + updateDoc(docRef, 'a', serverTimestamp(), 'b', 1); return accumulator.awaitLocalEvents(2); }) .then(snapshots => { @@ -226,7 +234,7 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { expect( snapshots[1].get('a', { serverTimestamps: 'previous' }) ).to.equal(42); - return docRef.firestore.enableNetwork(); + return enableNetwork(firestore); }) .then(() => accumulator.awaitRemoteEvent()) .then(remoteSnapshot => { @@ -238,15 +246,15 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { it('uses previous value from local mutation', () => { return withTestSetup(() => { return writeInitialData() - .then(() => docRef.firestore.disableNetwork()) + .then(() => disableNetwork(firestore)) .then(() => { // We set up three consecutive writes. // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.update('a', FieldValue.serverTimestamp()); + updateDoc(docRef, 'a', serverTimestamp()); // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.update('a', 1337); + updateDoc(docRef, 'a', 1337); // eslint-disable-next-line @typescript-eslint/no-floating-promises - docRef.update('a', FieldValue.serverTimestamp()); + updateDoc(docRef, 'a', serverTimestamp()); return accumulator.awaitLocalEvents(3); }) .then(snapshots => { @@ -258,7 +266,7 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { expect( snapshots[2].get('a', { serverTimestamps: 'previous' }) ).to.equal(1337); - return docRef.firestore.enableNetwork(); + return enableNetwork(firestore); }) .then(() => accumulator.awaitRemoteEvent()) .then(remoteSnapshot => { @@ -269,11 +277,9 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { it('fail via update() on nonexistent document.', () => { return withTestSetup(() => { - return docRef.update(updateData).then( - () => { - return Promise.reject('Should not have succeeded!'); - }, - (error: firestore.FirestoreError) => { + return updateDoc(docRef, updateData).then( + () => Promise.reject('Should not have succeeded!'), + (error: FirestoreError) => { expect(error.code).to.equal('not-found'); } ); @@ -281,19 +287,15 @@ apiDescribe('Server Timestamps', (persistence: boolean) => { }); it('fail via transaction update() on nonexistent document.', () => { - return withTestSetup(() => { - return docRef.firestore - .runTransaction(async txn => { - txn.update(docRef, updateData); - }) - .then( - () => { - return Promise.reject('Should not have succeeded!'); - }, - (error: firestore.FirestoreError) => { - expect(error.code).to.equal('not-found'); - } - ); - }); + return withTestSetup(() => + runTransaction(firestore, async txn => { + txn.update(docRef, updateData); + }).then( + () => Promise.reject('Should not have succeeded!'), + (error: FirestoreError) => { + expect(error.code).to.equal('not-found'); + } + ) + ); }); }); diff --git a/packages/firestore/test/integration/api/smoke.test.ts b/packages/firestore/test/integration/api/smoke.test.ts index a570cf3d92e..bcb0f7788a5 100644 --- a/packages/firestore/test/integration/api/smoke.test.ts +++ b/packages/firestore/test/integration/api/smoke.test.ts @@ -15,18 +15,35 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { EventsAccumulator } from '../util/events_accumulator'; -import * as integrationHelpers from '../util/helpers'; - -const apiDescribe = integrationHelpers.apiDescribe; +import { + collection, + doc, + DocumentReference, + DocumentSnapshot, + getDoc, + getDocs, + onSnapshot, + orderBy, + query, + QuerySnapshot, + setDoc, + where +} from '../util/firebase_export'; +import { + apiDescribe, + toDataArray, + withTestCollection, + withTestDbs, + withTestDoc +} from '../util/helpers'; apiDescribe('Smoke Test', (persistence: boolean) => { it('can write a single document', () => { - return integrationHelpers.withTestDoc(persistence, ref => { - return ref.set({ + return withTestDoc(persistence, ref => { + return setDoc(ref, { name: 'Patryk', message: 'We are actually writing data!' }); @@ -34,89 +51,72 @@ apiDescribe('Smoke Test', (persistence: boolean) => { }); it('can read a written document', () => { - return integrationHelpers.withTestDoc(persistence, ref => { + return withTestDoc(persistence, ref => { const data = { name: 'Patryk', message: 'We are actually writing data!' }; - return ref - .set(data) - .then(() => { - return ref.get(); - }) - .then((doc: firestore.DocumentSnapshot) => { + return setDoc(ref, data) + .then(() => getDoc(ref)) + .then(doc => { expect(doc.data()).to.deep.equal(data); }); }); }); it('can read a written document with DocumentKey', () => { - return integrationHelpers.withTestDoc(persistence, ref1 => { - const ref2 = ref1.firestore.collection('users').doc(); + return withTestDoc(persistence, (ref1, db) => { + const ref2 = doc(collection(db, 'users')); const data = { user: ref2, message: 'We are writing data' }; - return ref2.set({ name: 'patryk' }).then(() => { - return ref1 - .set(data) - .then(() => { - return ref1.get(); - }) - .then((doc: firestore.DocumentSnapshot) => { + return setDoc(ref2, { name: 'patryk' }).then(() => { + return setDoc(ref1, data) + .then(() => getDoc(ref1)) + .then(doc => { const recv = doc.data()!; expect(recv['message']).to.deep.equal(data.message); const user = recv['user']; - // Make sure it looks like a DocumentRef. - expect(user.set).to.be.an.instanceof(Function); - expect(user.onSnapshot).to.be.an.instanceof(Function); - expect(user.id).to.deep.equal(ref2.id); + expect(user).to.be.an.instanceof(DocumentReference); }); }); }); }); it('will fire local and remote events', () => { - return integrationHelpers.withTestDbs( - persistence, - 2, - ([reader, writer]) => { - const readerRef = reader.collection('rooms/firestore/messages').doc(); - const writerRef = writer.doc(readerRef.path); - const data = { - name: 'Patryk', - message: 'We are actually writing data!' - }; - - const accum = new EventsAccumulator(); - return writerRef.set(data).then(() => { - const unlisten = readerRef.onSnapshot(accum.storeEvent); - return accum - .awaitEvent() - .then(docSnap => { - expect(docSnap.exists).to.equal(true); - expect(docSnap.data()).to.deep.equal(data); - }) - .then(() => unlisten()); - }); - } - ); - }); + return withTestDbs(persistence, 2, ([reader, writer]) => { + const readerRef = doc(collection(reader, 'rooms/firestore/messages')); + const writerRef = doc(writer, readerRef.path); + const data = { + name: 'Patryk', + message: 'We are actually writing data!' + }; - it('will fire value events for empty collections', () => { - return integrationHelpers.withTestCollection( - persistence, - {}, - collection => { - const accum = new EventsAccumulator(); - const unlisten = collection.onSnapshot(accum.storeEvent); + const accum = new EventsAccumulator(); + return setDoc(writerRef, data).then(() => { + const unlisten = onSnapshot(readerRef, accum.storeEvent); return accum .awaitEvent() - .then(querySnap => { - expect(querySnap.empty).to.equal(true); - expect(querySnap.size).to.equal(0); - expect(querySnap.docs.length).to.equal(0); + .then(docSnap => { + expect(docSnap.exists()).to.equal(true); + expect(docSnap.data()).to.deep.equal(data); }) .then(() => unlisten()); - } - ); + }); + }); + }); + + it('will fire value events for empty collections', () => { + return withTestCollection(persistence, {}, collection => { + const accum = new EventsAccumulator(); + const unlisten = onSnapshot(collection, accum.storeEvent); + return accum + .awaitEvent() + .then(querySnap => { + expect(querySnap.empty).to.equal(true); + expect(querySnap.size).to.equal(0); + expect(querySnap.docs.length).to.equal(0); + }) + .then(() => unlisten()); + }); }); it('can get collection query', () => { @@ -128,11 +128,11 @@ apiDescribe('Smoke Test', (persistence: boolean) => { '2': { name: 'Gil', message: 'Yep!' }, '3': { name: 'Jonny', message: 'Crazy!' } }; - return integrationHelpers.withTestCollection(persistence, testDocs, ref => { - return ref.get().then(result => { + return withTestCollection(persistence, testDocs, ref => { + return getDocs(ref).then(result => { expect(result.empty).to.equal(false); expect(result.size).to.equal(3); - expect(integrationHelpers.toDataArray(result)).to.deep.equal([ + expect(toDataArray(result)).to.deep.equal([ testDocs[1], testDocs[2], testDocs[3] @@ -151,19 +151,19 @@ apiDescribe('Smoke Test', (persistence: boolean) => { '3': { sort: 2, filter: true, key: '3' }, '4': { sort: 3, filter: false, key: '4' } }; - return integrationHelpers.withTestCollection( - persistence, - testDocs, - coll => { - const query = coll.where('filter', '==', true).orderBy('sort', 'desc'); - return query.get().then(result => { - expect(integrationHelpers.toDataArray(result)).to.deep.equal([ - testDocs[2], - testDocs[3], - testDocs[1] - ]); - }); - } - ); + return withTestCollection(persistence, testDocs, coll => { + const filteredQuery = query( + coll, + where('filter', '==', true), + orderBy('sort', 'desc') + ); + return getDocs(filteredQuery).then(result => { + expect(toDataArray(result)).to.deep.equal([ + testDocs[2], + testDocs[3], + testDocs[1] + ]); + }); + }); }); }); diff --git a/packages/firestore/test/integration/api/transactions.test.ts b/packages/firestore/test/integration/api/transactions.test.ts index 56cd6b7ce6c..9e6e1489419 100644 --- a/packages/firestore/test/integration/api/transactions.test.ts +++ b/packages/firestore/test/integration/api/transactions.test.ts @@ -15,19 +15,29 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; -import * as firebaseExport from '../util/firebase_export'; -import * as integrationHelpers from '../util/helpers'; +import { + DocumentData, + FieldPath, + QueryDocumentSnapshot, + Transaction, + collection, + doc, + DocumentReference, + DocumentSnapshot, + Firestore, + FirestoreError, + getDoc, + runTransaction, + setDoc +} from '../util/firebase_export'; +import { apiDescribe, withTestDb } from '../util/helpers'; -const FieldPath = firebaseExport.FieldPath; - -const apiDescribe = integrationHelpers.apiDescribe; apiDescribe('Database transactions', (persistence: boolean) => { type TransactionStage = ( - transaction: firestore.Transaction, - docRef: firestore.DocumentReference + transaction: Transaction, + docRef: DocumentReference ) => void; /** @@ -36,43 +46,43 @@ apiDescribe('Database transactions', (persistence: boolean) => { * result in the document being set to the value specified by `set2()`. */ async function delete1( - transaction: firestore.Transaction, - docRef: firestore.DocumentReference + transaction: Transaction, + docRef: DocumentReference ): Promise { transaction.delete(docRef); } async function update1( - transaction: firestore.Transaction, - docRef: firestore.DocumentReference + transaction: Transaction, + docRef: DocumentReference ): Promise { transaction.update(docRef, { foo: 'bar1' }); } async function update2( - transaction: firestore.Transaction, - docRef: firestore.DocumentReference + transaction: Transaction, + docRef: DocumentReference ): Promise { transaction.update(docRef, { foo: 'bar2' }); } async function set1( - transaction: firestore.Transaction, - docRef: firestore.DocumentReference + transaction: Transaction, + docRef: DocumentReference ): Promise { transaction.set(docRef, { foo: 'bar1' }); } async function set2( - transaction: firestore.Transaction, - docRef: firestore.DocumentReference + transaction: Transaction, + docRef: DocumentReference ): Promise { transaction.set(docRef, { foo: 'bar2' }); } async function get( - transaction: firestore.Transaction, - docRef: firestore.DocumentReference + transaction: Transaction, + docRef: DocumentReference ): Promise { await transaction.get(docRef); } @@ -88,9 +98,9 @@ apiDescribe('Database transactions', (persistence: boolean) => { * transaction to run and assert that the end result matches the input. */ class TransactionTester { - constructor(readonly db: firestore.FirebaseFirestore) {} + constructor(readonly db: Firestore) {} - private docRef!: firestore.DocumentReference; + private docRef!: DocumentReference; private fromExistingDoc: boolean = false; private stages: TransactionStage[] = []; @@ -113,8 +123,8 @@ apiDescribe('Database transactions', (persistence: boolean) => { try { await this.prepareDoc(); await this.runTransaction(); - const snapshot = await this.docRef.get(); - expect(snapshot.exists).to.equal(true); + const snapshot = await getDoc(this.docRef); + expect(snapshot.exists()).to.equal(true); expect(snapshot.data()).to.deep.equal(expected); } catch (err) { expect.fail( @@ -131,8 +141,8 @@ apiDescribe('Database transactions', (persistence: boolean) => { try { await this.prepareDoc(); await this.runTransaction(); - const snapshot = await this.docRef.get(); - expect(snapshot.exists).to.equal(false); + const snapshot = await getDoc(this.docRef); + expect(snapshot.exists()).to.equal(false); } catch (err) { expect.fail( 'Expected the sequence (' + @@ -151,7 +161,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { await this.runTransaction(); succeeded = true; } catch (err) { - expect((err as firestore.FirestoreError).code).to.equal(expected); + expect((err as FirestoreError).code).to.equal(expected); } if (succeeded) { expect.fail( @@ -165,14 +175,14 @@ apiDescribe('Database transactions', (persistence: boolean) => { } private async prepareDoc(): Promise { - this.docRef = this.db.collection('tester-docref').doc(); + this.docRef = doc(collection(this.db, 'tester-docref')); if (this.fromExistingDoc) { - await this.docRef.set({ foo: 'bar0' }); + await setDoc(this.docRef, { foo: 'bar0' }); } } private async runTransaction(): Promise { - await this.db.runTransaction(async transaction => { + await runTransaction(this.db, async transaction => { for (const stage of this.stages) { await stage(transaction, this.docRef); } @@ -182,7 +192,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { private cleanupTester(): void { this.stages = []; // Set the docRef to something else to lose the original reference. - this.docRef = this.db.collection('reset').doc(); + this.docRef = doc(collection(this.db, 'reset')); } private listStages(stages: TransactionStage[]): string { @@ -205,7 +215,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { } it('runs transactions after getting existing document', async () => { - return integrationHelpers.withTestDb(persistence, async db => { + return withTestDb(persistence, async db => { const tt = new TransactionTester(db); await tt.withExistingDoc().run(get, delete1, delete1).expectNoDoc(); @@ -241,7 +251,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); it('runs transactions after getting non-existent document', async () => { - return integrationHelpers.withTestDb(persistence, async db => { + return withTestDb(persistence, async db => { const tt = new TransactionTester(db); await tt.withNonexistentDoc().run(get, delete1, delete1).expectNoDoc(); @@ -280,7 +290,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); it('runs transactions on existing document', async () => { - return integrationHelpers.withTestDb(persistence, async db => { + return withTestDb(persistence, async db => { const tt = new TransactionTester(db); await tt.withExistingDoc().run(delete1, delete1).expectNoDoc(); @@ -304,7 +314,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); it('runs transactions on non-existent document', async () => { - return integrationHelpers.withTestDb(persistence, async db => { + return withTestDb(persistence, async db => { const tt = new TransactionTester(db); await tt.withNonexistentDoc().run(delete1, delete1).expectNoDoc(); @@ -337,23 +347,20 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); it('set document with merge', () => { - return integrationHelpers.withTestDb(persistence, db => { - const doc = db.collection('towns').doc(); - return db - .runTransaction(async transaction => { - transaction.set(doc, { a: 'b', nested: { a: 'b' } }).set( - doc, - { c: 'd', nested: { c: 'd' } }, - { - merge: true - } - ); - }) - .then(() => { - return doc.get(); - }) + return withTestDb(persistence, db => { + const docRef = doc(collection(db, 'towns')); + return runTransaction(db, async transaction => { + transaction.set(docRef, { a: 'b', nested: { a: 'b' } }).set( + docRef, + { c: 'd', nested: { c: 'd' } }, + { + merge: true + } + ); + }) + .then(() => getDoc(docRef)) .then(snapshot => { - expect(snapshot.exists).to.equal(true); + expect(snapshot.exists()).to.equal(true); expect(snapshot.data()).to.deep.equal({ a: 'b', c: 'd', @@ -375,20 +382,19 @@ apiDescribe('Database transactions', (persistence: boolean) => { 'is.admin': true }; - return integrationHelpers.withTestDb(persistence, db => { - const doc = db.collection('counters').doc(); - return db - .runTransaction(async transaction => { - transaction.set(doc, initialData); - transaction.update( - doc, - 'owner.name', - 'Sebastian', - new FieldPath('is.admin'), - true - ); - }) - .then(() => doc.get()) + return withTestDb(persistence, db => { + const docRef = doc(collection(db, 'counters')); + return runTransaction(db, async transaction => { + transaction.set(docRef, initialData); + transaction.update( + docRef, + 'owner.name', + 'Sebastian', + new FieldPath('is.admin'), + true + ); + }) + .then(() => getDoc(docRef)) .then(docSnapshot => { expect(docSnapshot.exists).to.be.ok; expect(docSnapshot.data()).to.deep.equal(finalData); @@ -397,16 +403,15 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); it('retry when a document that was read without being written changes', () => { - return integrationHelpers.withTestDb(persistence, db => { - const doc1 = db.collection('counters').doc(); - const doc2 = db.collection('counters').doc(); + return withTestDb(persistence, db => { + const doc1 = doc(collection(db, 'counters')); + const doc2 = doc(collection(db, 'counters')); let tries = 0; - return doc1 - .set({ - count: 15 - }) + return setDoc(doc1, { + count: 15 + }) .then(() => { - return db.runTransaction(transaction => { + return runTransaction(db, transaction => { ++tries; // Get the first doc. @@ -417,7 +422,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { // transaction is tried, this will bump the version, which // will cause the write to doc2 to fail. The second time, it // will be a no-op and not bump the version. - .then(() => doc1.set({ count: 1234 })) + .then(() => setDoc(doc1, { count: 1234 })) // Now try to update the other doc from within the // transaction. // This should fail once, because we read 15 earlier. @@ -426,7 +431,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); }) .then(async () => { - const snapshot = await doc1.get(); + const snapshot = await getDoc(doc1); expect(tries).to.equal(2); expect(snapshot.data()!['count']).to.equal(1234); }); @@ -434,17 +439,16 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); it('cannot read after writing', () => { - return integrationHelpers.withTestDb(persistence, db => { - return db - .runTransaction(transaction => { - const doc = db.collection('anything').doc(); - transaction.set(doc, { foo: 'bar' }); - return transaction.get(doc); - }) + return withTestDb(persistence, db => { + return runTransaction(db, transaction => { + const docRef = doc(collection(db, 'anything')); + transaction.set(docRef, { foo: 'bar' }); + return transaction.get(docRef); + }) .then(() => { expect.fail('transaction should fail'); }) - .catch((err: firestore.FirestoreError) => { + .catch((err: FirestoreError) => { expect(err).to.exist; expect(err.code).to.equal('invalid-argument'); expect(err.message).to.contain( @@ -458,24 +462,23 @@ apiDescribe('Database transactions', (persistence: boolean) => { 'cannot read non-existent document then update, even if ' + 'document is written after the read', () => { - return integrationHelpers.withTestDb(persistence, db => { - return db - .runTransaction(transaction => { - const doc = db.collection('nonexistent').doc(); - return ( - transaction - // Get and update a document that doesn't exist so that the transaction fails. - .get(doc) - // Do a write outside of the transaction. - .then(() => doc.set({ count: 1234 })) - // Now try to update the doc from within the transaction. This - // should fail, because the document didn't exist at the start - // of the transaction. - .then(() => transaction.update(doc, { count: 16 })) - ); - }) + return withTestDb(persistence, db => { + return runTransaction(db, transaction => { + const docRef = doc(collection(db, 'nonexistent')); + return ( + transaction + // Get and update a document that doesn't exist so that the transaction fails. + .get(docRef) + // Do a write outside of the transaction. + .then(() => setDoc(docRef, { count: 1234 })) + // Now try to update the doc from within the transaction. This + // should fail, because the document didn't exist at the start + // of the transaction. + .then(() => transaction.update(docRef, { count: 16 })) + ); + }) .then(() => expect.fail('transaction should fail')) - .catch((err: firestore.FirestoreError) => { + .catch((err: FirestoreError) => { expect(err).to.exist; expect(err.code).to.equal('invalid-argument'); expect(err.message).to.contain( @@ -503,10 +506,9 @@ apiDescribe('Database transactions', (persistence: boolean) => { it(badReturn + ' is rejected', () => { // Intentionally returning bad type. // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fn = ((txn: firestore.Transaction) => badReturn) as any; - return integrationHelpers.withTestDb(persistence, db => { - return db - .runTransaction(fn) + const fn = ((txn: Transaction) => badReturn) as any; + return withTestDb(persistence, db => { + return runTransaction(db, fn) .then(() => expect.fail('transaction should fail')) .catch(err => { expect(err).to.exist; @@ -520,17 +522,16 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); it('can have gets without mutations', () => { - return integrationHelpers.withTestDb(persistence, db => { - const docRef = db.collection('foo').doc(); - const docRef2 = db.collection('foo').doc(); - return docRef - .set({ foo: 'bar' }) - .then(() => { - return db.runTransaction(async txn => { + return withTestDb(persistence, db => { + const docRef = doc(collection(db, 'foo')); + const docRef2 = doc(collection(db, 'foo')); + return setDoc(docRef, { foo: 'bar' }) + .then(() => + runTransaction(db, async txn => { await txn.get(docRef2); return txn.get(docRef); - }); - }) + }) + ) .then(snapshot => { expect(snapshot).to.exist; expect(snapshot.data()!['foo']).to.equal('bar'); @@ -539,22 +540,21 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); it('does not retry on permanent errors', () => { - return integrationHelpers.withTestDb(persistence, db => { + return withTestDb(persistence, db => { let counter = 0; - return db - .runTransaction(transaction => { - // Make a transaction that should fail with a permanent error. - counter++; - const doc = db.collection('nonexistent').doc(); - return ( - transaction - // Get and update a document that doesn't exist so that the transaction fails. - .get(doc) - .then(() => transaction.update(doc, { count: 16 })) - ); - }) + return runTransaction(db, transaction => { + // Make a transaction that should fail with a permanent error. + counter++; + const docRef = doc(collection(db, 'nonexistent')); + return ( + transaction + // Get and update a document that doesn't exist so that the transaction fails. + .get(docRef) + .then(() => transaction.update(docRef, { count: 16 })) + ); + }) .then(() => expect.fail('transaction should fail')) - .catch((err: firestore.FirestoreError) => { + .catch((err: FirestoreError) => { expect(err.code).to.equal('invalid-argument'); expect(counter).to.equal(1); }); @@ -562,58 +562,52 @@ apiDescribe('Database transactions', (persistence: boolean) => { }); it('are successful with no transaction operations', () => { - return integrationHelpers.withTestDb(persistence, db => { - return db.runTransaction(async txn => {}); + return withTestDb(persistence, db => { + return runTransaction(db, async txn => {}); }); }); it('are cancelled on rejected promise', () => { - return integrationHelpers.withTestDb(persistence, db => { - const doc = db.collection('towns').doc(); + return withTestDb(persistence, db => { + const docRef = doc(collection(db, 'towns')); let counter = 0; - return db - .runTransaction(transaction => { - counter++; - transaction.set(doc, { foo: 'bar' }); - return Promise.reject('no'); - }) + return runTransaction(db, transaction => { + counter++; + transaction.set(docRef, { foo: 'bar' }); + return Promise.reject('no'); + }) .then(() => expect.fail('transaction should fail')) .catch(err => { expect(err).to.exist; expect(err).to.equal('no'); expect(counter).to.equal(1); - return doc.get(); + return getDoc(docRef); }) .then(snapshot => { - expect((snapshot as firestore.DocumentSnapshot).exists).to.equal( - false - ); + expect((snapshot as DocumentSnapshot).exists()).to.equal(false); }); }); }); it('are cancelled on throw', () => { - return integrationHelpers.withTestDb(persistence, db => { - const doc = db.collection('towns').doc(); + return withTestDb(persistence, db => { + const docRef = doc(collection(db, 'towns')); const failure = new Error('no'); let count = 0; - return db - .runTransaction(transaction => { - count++; - transaction.set(doc, { foo: 'bar' }); - throw failure; - }) + return runTransaction(db, transaction => { + count++; + transaction.set(docRef, { foo: 'bar' }); + throw failure; + }) .then(() => expect.fail('transaction should fail')) .catch(err => { expect(err).to.exist; expect(err).to.equal(failure); expect(count).to.equal(1); - return doc.get(); + return getDoc(docRef); }) .then(snapshot => { - expect((snapshot as firestore.DocumentSnapshot).exists).to.equal( - false - ); + expect((snapshot as DocumentSnapshot).exists()).to.equal(false); }); }); }); @@ -629,33 +623,25 @@ apiDescribe('Database transactions', (persistence: boolean) => { } it('for Transaction.set() and Transaction.get()', () => { - return integrationHelpers.withTestDb(persistence, db => { - const docRef = db - .collection('posts') - .doc() - .withConverter({ - toFirestore(post: Post): firestore.DocumentData { - return { title: post.title, author: post.author }; - }, - fromFirestore( - snapshot: firestore.QueryDocumentSnapshot, - options: firestore.SnapshotOptions - ): Post { - const data = snapshot.data(options); - return new Post(data.title, data.author); - } + return withTestDb(persistence, db => { + const docRef = doc(collection(db, 'posts')).withConverter({ + toFirestore(post: Post): DocumentData { + return { title: post.title, author: post.author }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): Post { + const data = snapshot.data(); + return new Post(data.title, data.author); + } + }); + return setDoc(docRef, new Post('post', 'author')).then(() => { + return runTransaction(db, async transaction => { + const snapshot = await transaction.get(docRef); + expect(snapshot.data()!.byline()).to.equal('post, by author'); + transaction.set(docRef, new Post('new post', 'author')); + }).then(async () => { + const snapshot = await getDoc(docRef); + expect(snapshot.data()!.byline()).to.equal('new post, by author'); }); - return docRef.set(new Post('post', 'author')).then(() => { - return db - .runTransaction(async transaction => { - const snapshot = await transaction.get(docRef); - expect(snapshot.data()!.byline()).to.equal('post, by author'); - transaction.set(docRef, new Post('new post', 'author')); - }) - .then(async () => { - const snapshot = await docRef.get(); - expect(snapshot.data()!.byline()).to.equal('new post, by author'); - }); }); }); }); diff --git a/packages/firestore/test/integration/api/type.test.ts b/packages/firestore/test/integration/api/type.test.ts index bf91dce4221..48b8d0ac4c8 100644 --- a/packages/firestore/test/integration/api/type.test.ts +++ b/packages/firestore/test/integration/api/type.test.ts @@ -15,54 +15,63 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { addEqualityMatcher } from '../../util/equality_matcher'; import { EventsAccumulator } from '../util/events_accumulator'; -import * as firebaseExport from '../util/firebase_export'; +import { + Bytes, + collection, + doc, + DocumentSnapshot, + Firestore, + GeoPoint, + getDoc, + getDocs, + onSnapshot, + QuerySnapshot, + runTransaction, + setDoc, + Timestamp, + updateDoc +} from '../util/firebase_export'; import { apiDescribe, withTestDb, withTestDoc } from '../util/helpers'; -const Blob = firebaseExport.Blob; -const GeoPoint = firebaseExport.GeoPoint; -const Timestamp = firebaseExport.Timestamp; - apiDescribe('Firestore', (persistence: boolean) => { addEqualityMatcher(); async function expectRoundtrip( - db: firestore.FirebaseFirestore, + db: Firestore, data: {}, validateSnapshots = true, expectedData?: {} - ): Promise { + ): Promise { expectedData = expectedData ?? data; - const collection = db.collection(db.collection('a').doc().id); - const doc = collection.doc(); + const collRef = collection(db, doc(collection(db, 'a')).id); + const docRef = doc(collRef); - await doc.set(data); - let docSnapshot = await doc.get(); + await setDoc(docRef, data); + let docSnapshot = await getDoc(docRef); expect(docSnapshot.data()).to.deep.equal(expectedData); - await doc.update(data); - docSnapshot = await doc.get(); + await updateDoc(docRef, data); + docSnapshot = await getDoc(docRef); expect(docSnapshot.data()).to.deep.equal(expectedData); // Validate that the transaction API returns the same types - await db.runTransaction(async transaction => { - docSnapshot = await transaction.get(doc); + await runTransaction(db, async transaction => { + docSnapshot = await transaction.get(docRef); expect(docSnapshot.data()).to.deep.equal(expectedData); }); if (validateSnapshots) { - let querySnapshot = await collection.get(); + let querySnapshot = await getDocs(collRef); docSnapshot = querySnapshot.docs[0]; expect(docSnapshot.data()).to.deep.equal(expectedData); - const eventsAccumulator = - new EventsAccumulator(); - const unlisten = collection.onSnapshot(eventsAccumulator.storeEvent); + const eventsAccumulator = new EventsAccumulator(); + const unlisten = onSnapshot(collRef, eventsAccumulator.storeEvent); querySnapshot = await eventsAccumulator.awaitEvent(); docSnapshot = querySnapshot.docs[0]; expect(docSnapshot.data()).to.deep.equal(expectedData); @@ -121,7 +130,7 @@ apiDescribe('Firestore', (persistence: boolean) => { it('can read and write bytes fields', () => { return withTestDb(persistence, async db => { const docSnapshot = await expectRoundtrip(db, { - bytes: Blob.fromUint8Array(new Uint8Array([0, 1, 255])) + bytes: Bytes.fromUint8Array(new Uint8Array([0, 1, 255])) }); const blob = docSnapshot.data()!['bytes']; @@ -158,14 +167,14 @@ apiDescribe('Firestore', (persistence: boolean) => { }); it('can read and write document references', () => { - return withTestDoc(persistence, async doc => { - await expectRoundtrip(doc.firestore, { a: 42, ref: doc }); + return withTestDoc(persistence, async (doc, db) => { + await expectRoundtrip(db, { a: 42, ref: doc }); }); }); it('can read and write document references in an array', () => { - return withTestDoc(persistence, async doc => { - await expectRoundtrip(doc.firestore, { a: 42, refs: [doc] }); + return withTestDoc(persistence, async (doc, db) => { + await expectRoundtrip(db, { a: 42, refs: [doc] }); }); }); }); diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index e47578ee1b4..a61b5595ec0 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -15,11 +15,42 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { Deferred } from '../../util/promise'; -import * as firebaseExport from '../util/firebase_export'; +import { + arrayRemove, + arrayUnion, + collectionGroup, + connectFirestoreEmulator, + deleteField, + documentId, + enableIndexedDbPersistence, + endAt, + endBefore, + increment, + limit, + limitToLast, + newTestFirestore, + startAfter, + startAt, + writeBatch, + collection, + disableNetwork, + doc, + DocumentSnapshot, + enableNetwork, + Firestore, + getDoc, + onSnapshot, + orderBy, + query, + runTransaction, + serverTimestamp, + setDoc, + updateDoc, + where +} from '../util/firebase_export'; import { apiDescribe, withAlternateTestDb, @@ -28,10 +59,6 @@ import { } from '../util/helpers'; import { ALT_PROJECT_ID, DEFAULT_PROJECT_ID } from '../util/settings'; -const FieldPath = firebaseExport.FieldPath; -const FieldValue = firebaseExport.FieldValue; -const newTestFirestore = firebaseExport.newTestFirestore; - // We're using 'as any' to pass invalid values to APIs for testing purposes. /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -39,17 +66,17 @@ interface ValidationIt { ( persistence: boolean, message: string, - testFunction: (db: firestore.FirebaseFirestore) => void | Promise + testFunction: (db: Firestore) => void | Promise ): void; skip: ( persistence: boolean, message: string, - testFunction: (db: firestore.FirebaseFirestore) => void | Promise + testFunction: (db: Firestore) => void | Promise ) => void; only: ( persistence: boolean, message: string, - testFunction: (db: firestore.FirebaseFirestore) => void | Promise + testFunction: (db: Firestore) => void | Promise ) => void; } @@ -59,7 +86,7 @@ const validationIt: ValidationIt = Object.assign( ( persistence: boolean, message: string, - testFunction: (db: firestore.FirebaseFirestore) => void | Promise + testFunction: (db: Firestore) => void | Promise ) => { it(message, () => { return withTestDb(persistence, async db => { @@ -74,7 +101,7 @@ const validationIt: ValidationIt = Object.assign( skip( persistence: boolean, message: string, - _: (db: firestore.FirebaseFirestore) => void | Promise + _: (db: Firestore) => void | Promise ): void { // eslint-disable-next-line no-restricted-properties it.skip(message, () => {}); @@ -82,7 +109,7 @@ const validationIt: ValidationIt = Object.assign( only( persistence: boolean, message: string, - testFunction: (db: firestore.FirebaseFirestore) => void | Promise + testFunction: (db: Firestore) => void | Promise ): void { // eslint-disable-next-line no-restricted-properties it.only(message, () => { @@ -116,9 +143,9 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'disallows changing settings after use', async db => { - await db.doc('foo/bar').set({}); + await setDoc(doc(db, 'foo/bar'), {}); expect(() => - db.settings({ host: 'something-else.example.com' }) + connectFirestoreEmulator(db, 'something-else.example.com', 8080) ).to.throw( 'Firestore has already been started and its settings can no ' + 'longer be changed. You can only modify settings before calling any other ' + @@ -128,22 +155,22 @@ apiDescribe('Validation:', (persistence: boolean) => { ); validationIt(persistence, 'enforces minimum cache size', () => { - const db = newTestFirestore('test-project'); - expect(() => db.settings({ cacheSizeBytes: 1 })).to.throw( - 'cacheSizeBytes must be at least 1048576' - ); + expect(() => + newTestFirestore('test-project', undefined, { cacheSizeBytes: 1 }) + ).to.throw('cacheSizeBytes must be at least 1048576'); }); validationIt(persistence, 'garbage collection can be disabled', () => { - const db = newTestFirestore('test-project'); // Verify that this doesn't throw. - db.settings({ cacheSizeBytes: /* CACHE_SIZE_UNLIMITED= */ -1 }); + newTestFirestore('test-project', undefined, { + cacheSizeBytes: /* CACHE_SIZE_UNLIMITED= */ -1 + }); }); validationIt(persistence, 'useEmulator can set host and port', () => { const db = newTestFirestore('test-project'); // Verify that this doesn't throw. - db.useEmulator('localhost', 9000); + connectFirestoreEmulator(db, 'localhost', 9000); }); validationIt( @@ -153,8 +180,10 @@ apiDescribe('Validation:', (persistence: boolean) => { const errorMsg = 'Firestore has already been started and its settings can no longer be changed.'; - await db.doc('foo/bar').set({}); - expect(() => db.useEmulator('localhost', 9000)).to.throw(errorMsg); + await setDoc(doc(db, 'foo/bar'), {}); + expect(() => connectFirestoreEmulator(db, 'localhost', 9000)).to.throw( + errorMsg + ); } ); @@ -164,7 +193,9 @@ apiDescribe('Validation:', (persistence: boolean) => { () => { const db = newTestFirestore('test-project'); // Verify that this doesn't throw. - db.useEmulator('localhost', 9000, { mockUserToken: { sub: 'foo' } }); + connectFirestoreEmulator(db, 'localhost', 9000, { + mockUserToken: { sub: 'foo' } + }); } ); @@ -174,7 +205,7 @@ apiDescribe('Validation:', (persistence: boolean) => { () => { const db = newTestFirestore('test-project'); // Verify that this doesn't throw. - db.useEmulator('localhost', 9000, { + connectFirestoreEmulator(db, 'localhost', 9000, { mockUserToken: 'my-mock-user-token' }); } @@ -187,7 +218,9 @@ apiDescribe('Validation:', (persistence: boolean) => { const errorMsg = "mockUserToken must contain 'sub' or 'user_id' field!"; expect(() => - db.useEmulator('localhost', 9000, { mockUserToken: {} as any }) + connectFirestoreEmulator(db, 'localhost', 9000, { + mockUserToken: {} as any + }) ).to.throw(errorMsg); } ); @@ -201,9 +234,9 @@ apiDescribe('Validation:', (persistence: boolean) => { // Calling `enablePersistence()` itself counts as use, so we should only // need this method when persistence is not enabled. if (!persistence) { - db.doc('foo/bar'); + doc(db, 'foo/bar'); } - expect(() => db.enablePersistence()).to.throw( + expect(() => enableIndexedDbPersistence(db)).to.throw( 'Firestore has already been started and persistence can no ' + 'longer be enabled. You can only enable persistence before ' + 'calling any other methods on a Firestore object.' @@ -213,34 +246,27 @@ apiDescribe('Validation:', (persistence: boolean) => { it("fails transaction if function doesn't return a Promise.", () => { return withTestDb(persistence, db => { - return db - .runTransaction(() => 5 as any) - .then( - x => expect.fail('Transaction should fail'), - err => { - expect(err.message).to.equal( - 'Transaction callback must return a Promise' - ); - } - ); + return runTransaction(db, () => 5 as any).then( + () => expect.fail('Transaction should fail'), + err => { + expect(err.message).to.equal( + 'Transaction callback must return a Promise' + ); + } + ); }); }); }); describe('Collection paths', () => { validationIt(persistence, 'must be non-empty strings', db => { - const baseDocRef = db.doc('foo/bar'); - expect(() => db.collection('')).to.throw( - 'Function Firestore.collection() cannot be called with an empty path.' - ); - expect(() => baseDocRef.collection('')).to.throw( - 'Function DocumentReference.collection() cannot be called with an ' + - 'empty path.' + expect(() => collection(db, '')).to.throw( + 'Function collection() cannot be called with an empty path.' ); }); validationIt(persistence, 'must be odd-length', db => { - const baseDocRef = db.doc('foo/bar'); + const baseDocRef = doc(db, 'foo/bar'); const badAbsolutePaths = ['foo/bar', 'foo/bar/baz/quu']; const badRelativePaths = ['/', 'baz/quu']; const badPathLengths = [2, 4]; @@ -250,8 +276,8 @@ apiDescribe('Validation:', (persistence: boolean) => { 'Invalid collection reference. Collection references ' + 'must have an odd number of segments, but ' + `${badAbsolutePaths[i]} has ${badPathLengths[i]}`; - expect(() => db.collection(badAbsolutePaths[i])).to.throw(error); - expect(() => baseDocRef.collection(badRelativePaths[i])).to.throw( + expect(() => collection(db, badAbsolutePaths[i])).to.throw(error); + expect(() => collection(baseDocRef, badRelativePaths[i])).to.throw( error ); } @@ -259,38 +285,32 @@ apiDescribe('Validation:', (persistence: boolean) => { validationIt(persistence, 'must not have empty segments', db => { // NOTE: leading / trailing slashes are okay. - db.collection('/foo/'); - db.collection('/foo'); - db.collection('foo/'); + collection(db, '/foo/'); + collection(db, '/foo'); + collection(db, 'foo/'); const badPaths = ['foo//bar//baz', '//foo', 'foo//']; - const collection = db.collection('test-collection'); - const doc = collection.doc('test-document'); + const collRef = collection(db, 'test-collection'); + const docRef = doc(collRef, 'test-document'); for (const path of badPaths) { const reason = `Invalid segment (${path}). Paths must not contain // in them.`; - expect(() => db.collection(path)).to.throw(reason); - expect(() => db.doc(path)).to.throw(reason); - expect(() => collection.doc(path)).to.throw(reason); - expect(() => doc.collection(path)).to.throw(reason); + expect(() => collection(db, path)).to.throw(reason); + expect(() => doc(db, path)).to.throw(reason); + expect(() => doc(collRef, path)).to.throw(reason); + expect(() => collection(docRef, path)).to.throw(reason); } }); }); describe('Document paths', () => { validationIt(persistence, 'must be strings', db => { - const baseCollectionRef = db.collection('foo'); - - expect(() => db.doc('')).to.throw( - 'Function Firestore.doc() cannot be called with an empty path.' - ); - expect(() => baseCollectionRef.doc('')).to.throw( - 'Function CollectionReference.doc() cannot be called with an empty ' + - 'path.' + expect(() => doc(db, '')).to.throw( + 'Function doc() cannot be called with an empty path.' ); }); validationIt(persistence, 'must be even-length', db => { - const baseCollectionRef = db.collection('foo'); + const baseCollectionRef = collection(db, 'foo'); const badAbsolutePaths = ['foo', 'foo/bar/baz']; const badRelativePaths = ['/', 'bar/baz']; const badPathLengths = [1, 3]; @@ -300,31 +320,18 @@ apiDescribe('Validation:', (persistence: boolean) => { 'Invalid document reference. Document references ' + 'must have an even number of segments, but ' + `${badAbsolutePaths[i]} has ${badPathLengths[i]}`; - expect(() => db.doc(badAbsolutePaths[i])).to.throw(error); - expect(() => baseCollectionRef.doc(badRelativePaths[i])).to.throw( + expect(() => doc(db, badAbsolutePaths[i])).to.throw(error); + expect(() => doc(baseCollectionRef, badRelativePaths[i])).to.throw( error ); } }); }); - validationIt(persistence, 'Merge options are validated', db => { - const docRef = db.collection('test').doc(); - - expect(() => docRef.set({}, { merge: true, mergeFields: [] })).to.throw( - 'Invalid options passed to function DocumentReference.set(): You cannot specify both ' + - '"merge" and "mergeFields".' - ); - expect(() => docRef.set({}, { merge: false, mergeFields: [] })).to.throw( - 'Invalid options passed to function DocumentReference.set(): You cannot specify both ' + - '"merge" and "mergeFields".' - ); - }); - describe('Writes', () => { validationIt(persistence, 'must be objects.', db => { - // PORTING NOTE: The error for firebase.firestore.FieldValue.delete() - // is different for minified builds, so omit testing it specifically. + // PORTING NOTE: The error for deleteField() is different for minified + // builds, so omit testing it specifically. const badData = [ 42, [1], @@ -387,8 +394,8 @@ apiDescribe('Validation:', (persistence: boolean) => { db => { return expectWriteToFail( db, - { 'array': [FieldValue.serverTimestamp()] }, - 'FieldValue.serverTimestamp() is not currently supported inside arrays' + { 'array': [serverTimestamp()] }, + 'serverTimestamp() is not currently supported inside arrays' ); } ); @@ -408,27 +415,20 @@ apiDescribe('Validation:', (persistence: boolean) => { validationIt(persistence, 'may contain indirectly nested arrays.', db => { const data = { 'nested-array': [1, { foo: [2] }] }; - const ref = db.collection('foo').doc(); - const ref2 = db.collection('foo').doc(); - - return ref - .set(data) - .then(() => { - return ref.firestore.batch().set(ref, data).commit(); - }) - .then(() => { - return ref.update(data); - }) - .then(() => { - return ref.firestore.batch().update(ref, data).commit(); - }) - .then(() => { - return ref.firestore.runTransaction(async txn => { + const ref = doc(collection(db, 'foo')); + const ref2 = doc(collection(db, 'foo')); + + return setDoc(ref, data) + .then(() => writeBatch(db).set(ref, data).commit()) + .then(() => updateDoc(ref, data)) + .then(() => writeBatch(db).update(ref, data).commit()) + .then(() => + runTransaction(db, async txn => { // Note ref2 does not exist at this point so set that and update ref. txn.update(ref, data); txn.set(ref2, data); - }); - }); + }) + ); }); validationIt(persistence, 'must not contain undefined.', db => { @@ -445,7 +445,7 @@ apiDescribe('Validation:', (persistence: boolean) => { 'must not contain references to a different database', db => { return withAlternateTestDb(persistence, db2 => { - const ref = db2.doc('baz/quu'); + const ref = doc(db2, 'baz/quu'); const data = { foo: ref }; return expectWriteToFail( db, @@ -505,12 +505,12 @@ apiDescribe('Validation:', (persistence: boolean) => { validationIt( persistence, - 'via set() must not contain FieldValue.delete()', + 'via set() must not contain deleteField()', db => { return expectSetToFail( db, - { foo: FieldValue.delete() }, - 'FieldValue.delete() cannot be used with set() unless you pass ' + + { foo: deleteField() }, + 'deleteField() cannot be used with set() unless you pass ' + '{merge:true} (found in field foo)' ); } @@ -518,12 +518,12 @@ apiDescribe('Validation:', (persistence: boolean) => { validationIt( persistence, - 'via update() must not contain nested FieldValue.delete()', + 'via update() must not contain nested deleteField()', db => { return expectUpdateToFail( db, - { foo: { bar: FieldValue.delete() } }, - 'FieldValue.delete() can only appear at the top level of your ' + + { foo: { bar: deleteField() } }, + 'deleteField() can only appear at the top level of your ' + 'update data (found in field foo.bar)' ); } @@ -535,11 +535,11 @@ apiDescribe('Validation:', (persistence: boolean) => { 'Batch writes require correct Document References', db => { return withAlternateTestDb(persistence, async db2 => { - const badRef = db2.doc('foo/bar'); + const badRef = doc(db2, 'foo/bar'); const reason = 'Provided document reference is from a different Firestore instance.'; const data = { foo: 1 }; - const batch = db.batch(); + const batch = writeBatch(db); expect(() => batch.set(badRef, data)).to.throw(reason); expect(() => batch.update(badRef, data)).to.throw(reason); expect(() => batch.delete(badRef)).to.throw(reason); @@ -552,11 +552,11 @@ apiDescribe('Validation:', (persistence: boolean) => { 'Transaction writes require correct Document References', db => { return withAlternateTestDb(persistence, db2 => { - const badRef = db2.doc('foo/bar'); + const badRef = doc(db2, 'foo/bar'); const reason = 'Provided document reference is from a different Firestore instance.'; const data = { foo: 1 }; - return db.runTransaction(async txn => { + return runTransaction(db, async txn => { expect(() => txn.get(badRef)).to.throw(reason); expect(() => txn.set(badRef, data)).to.throw(reason); expect(() => txn.update(badRef, data)).to.throw(reason); @@ -567,12 +567,9 @@ apiDescribe('Validation:', (persistence: boolean) => { ); validationIt(persistence, 'Field paths must not have empty segments', db => { - const docRef = db.collection('test').doc(); - return docRef - .set({ test: 1 }) - .then(() => { - return docRef.get(); - }) + const docRef = doc(collection(db, 'test')); + return setDoc(docRef, { test: 1 }) + .then(() => getDoc(docRef)) .then(snapshot => { const badFieldPaths = ['', 'foo..baz', '.foo', 'foo.']; const promises: Array> = []; @@ -590,12 +587,9 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'Field paths must not have invalid segments', db => { - const docRef = db.collection('test').doc(); - return docRef - .set({ test: 1 }) - .then(() => { - return docRef.get(); - }) + const docRef = doc(collection(db, 'test')); + return setDoc(docRef, { test: 1 }) + .then(() => getDoc(docRef)) .then(snapshot => { const badFieldPaths = [ 'foo~bar', @@ -619,60 +613,56 @@ apiDescribe('Validation:', (persistence: boolean) => { describe('Array transforms', () => { validationIt(persistence, 'fail in queries', db => { - const collection = db.collection('test'); + const coll = collection(db, 'test'); expect(() => - collection.where('test', '==', { test: FieldValue.arrayUnion(1) }) + query(coll, where('test', '==', { test: arrayUnion(1) })) ).to.throw( - 'Function Query.where() called with invalid data. ' + - 'FieldValue.arrayUnion() can only be used with update() and set() ' + + 'Function where() called with invalid data. ' + + 'arrayUnion() can only be used with update() and set() ' + '(found in field test)' ); expect(() => - collection.where('test', '==', { test: FieldValue.arrayRemove(1) }) + query(coll, where('test', '==', { test: arrayRemove(1) })) ).to.throw( - 'Function Query.where() called with invalid data. ' + - 'FieldValue.arrayRemove() can only be used with update() and set() ' + + 'Function where() called with invalid data. ' + + 'arrayRemove() can only be used with update() and set() ' + '(found in field test)' ); }); validationIt(persistence, 'reject invalid elements', db => { - const doc = db.collection('test').doc(); + const docRef = doc(collection(db, 'test')); expect(() => - doc.set({ x: FieldValue.arrayUnion(1, new TestClass('foo')) }) + setDoc(docRef, { x: arrayUnion(1, new TestClass('foo')) }) ).to.throw( - 'Function FieldValue.arrayUnion() called with invalid data. ' + + 'Function arrayUnion() called with invalid data. ' + 'Unsupported field value: a custom TestClass object' ); expect(() => - doc.set({ x: FieldValue.arrayRemove(1, new TestClass('foo')) }) + setDoc(docRef, { x: arrayRemove(1, new TestClass('foo')) }) ).to.throw( - 'Function FieldValue.arrayRemove() called with invalid data. ' + + 'Function arrayRemove() called with invalid data. ' + 'Unsupported field value: a custom TestClass object' ); - expect(() => doc.set({ x: FieldValue.arrayRemove(undefined) })).to.throw( - 'Function FieldValue.arrayRemove() called with invalid data. ' + + expect(() => setDoc(docRef, { x: arrayRemove(undefined) })).to.throw( + 'Function arrayRemove() called with invalid data. ' + 'Unsupported field value: undefined' ); }); validationIt(persistence, 'reject arrays', db => { - const doc = db.collection('test').doc(); + const docRef = doc(collection(db, 'test')); // This would result in a directly nested array which is not supported. - expect(() => - doc.set({ x: FieldValue.arrayUnion(1, ['nested']) }) - ).to.throw( - 'Function FieldValue.arrayUnion() called with invalid data. ' + + expect(() => setDoc(docRef, { x: arrayUnion(1, ['nested']) })).to.throw( + 'Function arrayUnion() called with invalid data. ' + 'Nested arrays are not supported' ); - expect(() => - doc.set({ x: FieldValue.arrayRemove(1, ['nested']) }) - ).to.throw( - 'Function FieldValue.arrayRemove() called with invalid data. ' + + expect(() => setDoc(docRef, { x: arrayRemove(1, ['nested']) })).to.throw( + 'Function arrayRemove() called with invalid data. ' + 'Nested arrays are not supported' ); }); @@ -680,12 +670,12 @@ apiDescribe('Validation:', (persistence: boolean) => { describe('Numeric transforms', () => { validationIt(persistence, 'fail in queries', db => { - const collection = db.collection('test'); + const coll = collection(db, 'test'); expect(() => - collection.where('test', '==', { test: FieldValue.increment(1) }) + query(coll, where('test', '==', { test: increment(1) })) ).to.throw( - 'Function Query.where() called with invalid data. ' + - 'FieldValue.increment() can only be used with update() and set() ' + + 'Function where() called with invalid data. ' + + 'increment() can only be used with update() and set() ' + '(found in field test)' ); }); @@ -693,12 +683,12 @@ apiDescribe('Validation:', (persistence: boolean) => { describe('Queries', () => { validationIt(persistence, 'with non-positive limit fail', db => { - const collection = db.collection('test'); - expect(() => collection.limit(0)).to.throw( - `Function Query.limit() requires a positive number, but it was: 0.` + const coll = collection(db, 'test'); + expect(() => query(coll, limit(0))).to.throw( + `Function limit() requires a positive number, but it was: 0.` ); - expect(() => collection.limitToLast(-1)).to.throw( - `Function Query.limitToLast() requires a positive number, but it was: -1.` + expect(() => query(coll, limitToLast(-1))).to.throw( + `Function limitToLast() requires a positive number, but it was: -1.` ); }); @@ -707,21 +697,18 @@ apiDescribe('Validation:', (persistence: boolean) => { f: { k: 'f', nosort: 1 } // should not show up }; return withTestCollection(persistence, testDocs, coll => { - const query = coll.orderBy('sort'); - return coll - .doc('f') - .get() - .then(doc => { - expect(doc.data()).to.deep.equal({ k: 'f', nosort: 1 }); - const reason = - `Invalid query. You are trying to start or end a ` + - `query using a document for which the field 'sort' (used as ` + - `the orderBy) does not exist.`; - expect(() => query.startAt(doc)).to.throw(reason); - expect(() => query.startAfter(doc)).to.throw(reason); - expect(() => query.endBefore(doc)).to.throw(reason); - expect(() => query.endAt(doc)).to.throw(reason); - }); + const query1 = query(coll, orderBy('sort')); + return getDoc(doc(coll, 'f')).then(doc => { + expect(doc.data()).to.deep.equal({ k: 'f', nosort: 1 }); + const reason = + `Invalid query. You are trying to start or end a ` + + `query using a document for which the field 'sort' (used as ` + + `the orderBy) does not exist.`; + expect(() => query(query1, startAt(doc))).to.throw(reason); + expect(() => query(query1, startAfter(doc))).to.throw(reason); + expect(() => query(query1, endBefore(doc))).to.throw(reason); + expect(() => query(query1, endAt(doc))).to.throw(reason); + }); }); }); @@ -732,48 +719,47 @@ apiDescribe('Validation:', (persistence: boolean) => { return withTestCollection( persistence, /*docs=*/ {}, - async (collection: firestore.CollectionReference) => { - await db.disableNetwork(); + async collection => { + await disableNetwork(db); const offlineDeferred = new Deferred(); const onlineDeferred = new Deferred(); - const unsubscribe = collection.onSnapshot(snapshot => { + const unsubscribe = onSnapshot(collection, snapshot => { // Skip the initial empty snapshot. if (snapshot.empty) { return; } expect(snapshot.docs).to.have.lengthOf(1); - const docSnap: firestore.DocumentSnapshot = snapshot.docs[0]; + const docSnap: DocumentSnapshot = snapshot.docs[0]; if (snapshot.metadata.hasPendingWrites) { // Offline snapshot. Since the server timestamp is uncommitted, // we shouldn't be able to query by it. expect(() => - collection - .orderBy('timestamp') - .endAt(docSnap) - .onSnapshot(() => {}) + onSnapshot( + query(collection, orderBy('timestamp'), endAt(docSnap)), + () => {} + ) ).to.throw('uncommitted server timestamp'); offlineDeferred.resolve(); } else { // Online snapshot. Since the server timestamp is committed, we // should be able to query by it. - collection - .orderBy('timestamp') - .endAt(docSnap) - .onSnapshot(() => {}); + onSnapshot( + query(collection, orderBy('timestamp'), endAt(docSnap)), + () => {} + ); onlineDeferred.resolve(); } }); - const doc: firestore.DocumentReference = collection.doc(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - doc.set({ timestamp: FieldValue.serverTimestamp() }); + const docRef = doc(collection); + void setDoc(docRef, { timestamp: serverTimestamp() }); await offlineDeferred.promise; - await db.enableNetwork(); + await enableNetwork(db); await onlineDeferred.promise; unsubscribe(); @@ -786,14 +772,16 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'must not have more components than order by.', db => { - const collection = db.collection('collection'); - const query = collection.orderBy('foo'); + const coll = collection(db, 'collection'); + const query1 = query(coll, orderBy('foo')); const reason = - 'Too many arguments provided to Query.startAt(). The number of ' + + 'Too many arguments provided to startAt(). The number of ' + 'arguments must be less than or equal to the number of orderBy() ' + 'clauses'; - expect(() => query.startAt(1, 2)).to.throw(reason); - expect(() => query.orderBy('bar').startAt(1, 2, 3)).to.throw(reason); + expect(() => query(query1, startAt(1, 2))).to.throw(reason); + expect(() => query(query1, orderBy('bar'), startAt(1, 2, 3))).to.throw( + reason + ); } ); @@ -801,24 +789,26 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'order-by-key bounds must be strings without slashes.', db => { - const query = db - .collection('collection') - .orderBy(FieldPath.documentId()); - const cgQuery = db - .collectionGroup('collection') - .orderBy(FieldPath.documentId()); - expect(() => query.startAt(1)).to.throw( + const collQuery = query( + collection(db, 'collection'), + orderBy(documentId()) + ); + const cgQuery = query( + collectionGroup(db, 'collection'), + orderBy(documentId()) + ); + expect(() => query(collQuery, startAt(1))).to.throw( 'Invalid query. Expected a string for document ID in ' + - 'Query.startAt(), but got a number' + 'startAt(), but got a number' ); - expect(() => query.startAt('foo/bar')).to.throw( + expect(() => query(collQuery, startAt('foo/bar'))).to.throw( 'Invalid query. When querying a collection and ordering by ' + - 'FieldPath.documentId(), the value passed to Query.startAt() ' + + 'documentId(), the value passed to startAt() ' + "must be a plain document ID, but 'foo/bar' contains a slash." ); - expect(() => cgQuery.startAt('foo')).to.throw( + expect(() => query(cgQuery, startAt('foo'))).to.throw( 'Invalid query. When querying a collection group and ordering by ' + - 'FieldPath.documentId(), the value passed to Query.startAt() ' + + 'documentId(), the value passed to startAt() ' + "must result in a valid document path, but 'foo' is not because " + 'it contains an odd number of segments.' ); @@ -826,9 +816,9 @@ apiDescribe('Validation:', (persistence: boolean) => { ); validationIt(persistence, 'with different inequality fields fail', db => { - const collection = db.collection('test'); + const coll = collection(db, 'test'); expect(() => - collection.where('x', '>=', 32).where('y', '<', 'cat') + query(coll, where('x', '>=', 32), where('y', '<', 'cat')) ).to.throw( 'Invalid query. All where filters with an ' + 'inequality (<, <=, !=, not-in, >, or >=) must be on the same field.' + @@ -837,9 +827,9 @@ apiDescribe('Validation:', (persistence: boolean) => { }); validationIt(persistence, 'with more than one != query fail', db => { - const collection = db.collection('test'); + const coll = collection(db, 'test'); expect(() => - collection.where('x', '!=', 32).where('x', '!=', 33) + query(coll, where('x', '!=', 32), where('x', '!=', 33)) ).to.throw("Invalid query. You cannot use more than one '!=' filter."); }); @@ -847,9 +837,9 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'with != and inequality queries on different fields fail', db => { - const collection = db.collection('test'); + const coll = collection(db, 'test'); expect(() => - collection.where('y', '>', 32).where('x', '!=', 33) + query(coll, where('y', '>', 32), where('x', '!=', 33)) ).to.throw( 'Invalid query. All where filters with an ' + 'inequality (<, <=, !=, not-in, >, or >=) must be on the same field.' + @@ -862,9 +852,9 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'with != and inequality queries on different fields fail', db => { - const collection = db.collection('test'); + const coll = collection(db, 'test'); expect(() => - collection.where('y', '>', 32).where('x', 'not-in', [33]) + query(coll, where('y', '>', 32), where('x', 'not-in', [33])) ).to.throw( 'Invalid query. All where filters with an ' + 'inequality (<, <=, !=, not-in, >, or >=) must be on the same field.' + @@ -877,25 +867,25 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'with inequality different than first orderBy fail.', db => { - const collection = db.collection('test'); + const coll = collection(db, 'test'); const reason = `Invalid query. You have a where filter with an ` + `inequality (<, <=, !=, not-in, >, or >=) on field 'x' and so you must also ` + - `use 'x' as your first argument to Query.orderBy(), but your first ` + + `use 'x' as your first argument to orderBy(), but your first ` + `orderBy() is on field 'y' instead.`; - expect(() => collection.where('x', '>', 32).orderBy('y')).to.throw( + expect(() => query(coll, where('x', '>', 32), orderBy('y'))).to.throw( reason ); - expect(() => collection.orderBy('y').where('x', '>', 32)).to.throw( + expect(() => query(coll, orderBy('y'), where('x', '>', 32))).to.throw( reason ); expect(() => - collection.where('x', '>', 32).orderBy('y').orderBy('x') + query(coll, where('x', '>', 32), orderBy('y'), orderBy('x')) ).to.throw(reason); expect(() => - collection.orderBy('y').orderBy('x').where('x', '>', 32) + query(coll, orderBy('y'), orderBy('x'), where('x', '>', 32)) ).to.throw(reason); - expect(() => collection.where('x', '!=', 32).orderBy('y')).to.throw( + expect(() => query(coll, where('x', '!=', 32), orderBy('y'))).to.throw( reason ); } @@ -903,39 +893,43 @@ apiDescribe('Validation:', (persistence: boolean) => { validationIt(persistence, 'with multiple array filters fail', db => { expect(() => - db - .collection('test') - .where('foo', 'array-contains', 1) - .where('foo', 'array-contains', 2) + query( + collection(db, 'test'), + where('foo', 'array-contains', 1), + where('foo', 'array-contains', 2) + ) ).to.throw( "Invalid query. You cannot use more than one 'array-contains' filter." ); expect(() => - db - .collection('test') - .where('foo', 'array-contains', 1) - .where('foo', 'array-contains-any', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'array-contains', 1), + where('foo', 'array-contains-any', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use 'array-contains-any' filters with " + "'array-contains' filters." ); expect(() => - db - .collection('test') - .where('foo', 'array-contains-any', [2, 3]) - .where('foo', 'array-contains', 1) + query( + collection(db, 'test'), + where('foo', 'array-contains-any', [2, 3]), + where('foo', 'array-contains', 1) + ) ).to.throw( "Invalid query. You cannot use 'array-contains' filters with " + "'array-contains-any' filters." ); expect(() => - db - .collection('test') - .where('foo', 'not-in', [2, 3]) - .where('foo', 'array-contains', 1) + query( + collection(db, 'test'), + where('foo', 'not-in', [2, 3]), + where('foo', 'array-contains', 1) + ) ).to.throw( "Invalid query. You cannot use 'array-contains' filters with " + "'not-in' filters." @@ -944,19 +938,21 @@ apiDescribe('Validation:', (persistence: boolean) => { validationIt(persistence, 'with != and not-in filters fail', db => { expect(() => - db - .collection('test') - .where('foo', 'not-in', [2, 3]) - .where('foo', '!=', 4) + query( + collection(db, 'test'), + where('foo', 'not-in', [2, 3]), + where('foo', '!=', 4) + ) ).to.throw( "Invalid query. You cannot use '!=' filters with 'not-in' filters." ); expect(() => - db - .collection('test') - .where('foo', '!=', 4) - .where('foo', 'not-in', [2, 3]) + query( + collection(db, 'test'), + where('foo', '!=', 4), + where('foo', 'not-in', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use 'not-in' filters with '!=' filters." ); @@ -964,85 +960,94 @@ apiDescribe('Validation:', (persistence: boolean) => { validationIt(persistence, 'with multiple disjunctive filters fail', db => { expect(() => - db - .collection('test') - .where('foo', 'in', [1, 2]) - .where('foo', 'in', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'in', [1, 2]), + where('foo', 'in', [2, 3]) + ) ).to.throw("Invalid query. You cannot use more than one 'in' filter."); expect(() => - db - .collection('test') - .where('foo', 'not-in', [1, 2]) - .where('foo', 'not-in', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'not-in', [1, 2]), + where('foo', 'not-in', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use more than one 'not-in' filter." ); expect(() => - db - .collection('test') - .where('foo', 'array-contains-any', [1, 2]) - .where('foo', 'array-contains-any', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'array-contains-any', [1, 2]), + where('foo', 'array-contains-any', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use more than one 'array-contains-any'" + ' filter.' ); expect(() => - db - .collection('test') - .where('foo', 'array-contains-any', [2, 3]) - .where('foo', 'in', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'array-contains-any', [2, 3]), + where('foo', 'in', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use 'in' filters with " + "'array-contains-any' filters." ); expect(() => - db - .collection('test') - .where('foo', 'in', [2, 3]) - .where('foo', 'array-contains-any', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'in', [2, 3]), + where('foo', 'array-contains-any', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use 'array-contains-any' filters with " + "'in' filters." ); expect(() => - db - .collection('test') - .where('foo', 'not-in', [2, 3]) - .where('foo', 'array-contains-any', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'not-in', [2, 3]), + where('foo', 'array-contains-any', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use 'array-contains-any' filters with " + "'not-in' filters." ); expect(() => - db - .collection('test') - .where('foo', 'array-contains-any', [2, 3]) - .where('foo', 'not-in', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'array-contains-any', [2, 3]), + where('foo', 'not-in', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use 'not-in' filters with " + "'array-contains-any' filters." ); expect(() => - db - .collection('test') - .where('foo', 'not-in', [2, 3]) - .where('foo', 'in', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'not-in', [2, 3]), + where('foo', 'in', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use 'in' filters with 'not-in' filters." ); expect(() => - db - .collection('test') - .where('foo', 'in', [2, 3]) - .where('foo', 'not-in', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'in', [2, 3]), + where('foo', 'not-in', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use 'not-in' filters with 'in' filters." ); @@ -1050,43 +1055,47 @@ apiDescribe('Validation:', (persistence: boolean) => { // This is redundant with the above tests, but makes sure our validation // doesn't get confused. expect(() => - db - .collection('test') - .where('foo', 'in', [2, 3]) - .where('foo', 'array-contains', 1) - .where('foo', 'array-contains-any', [2]) + query( + collection(db, 'test'), + where('foo', 'in', [2, 3]), + where('foo', 'array-contains', 1), + where('foo', 'array-contains-any', [2]) + ) ).to.throw( "Invalid query. You cannot use 'array-contains-any' filters with 'in' filters." ); expect(() => - db - .collection('test') - .where('foo', 'array-contains', 1) - .where('foo', 'in', [2, 3]) - .where('foo', 'array-contains-any', [2]) + query( + collection(db, 'test'), + where('foo', 'array-contains', 1), + where('foo', 'in', [2, 3]), + where('foo', 'array-contains-any', [2]) + ) ).to.throw( "Invalid query. You cannot use 'array-contains-any' filters with " + "'array-contains' filters." ); expect(() => - db - .collection('test') - .where('foo', 'not-in', [2, 3]) - .where('foo', 'array-contains', 2) - .where('foo', 'array-contains-any', [2]) + query( + collection(db, 'test'), + where('foo', 'not-in', [2, 3]), + where('foo', 'array-contains', 2), + where('foo', 'array-contains-any', [2]) + ) ).to.throw( "Invalid query. You cannot use 'array-contains' filters with " + "'not-in' filters." ); expect(() => - db - .collection('test') - .where('foo', 'array-contains', 2) - .where('foo', 'in', [2]) - .where('foo', 'not-in', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'array-contains', 2), + where('foo', 'in', [2]), + where('foo', 'not-in', [2, 3]) + ) ).to.throw( "Invalid query. You cannot use 'not-in' filters with " + "'array-contains' filters." @@ -1098,35 +1107,39 @@ apiDescribe('Validation:', (persistence: boolean) => { 'can have an IN filter with an array-contains filter.', db => { expect(() => - db - .collection('test') - .where('foo', 'array-contains', 1) - .where('foo', 'in', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'array-contains', 1), + where('foo', 'in', [2, 3]) + ) ).not.to.throw(); expect(() => - db - .collection('test') - .where('foo', 'in', [2, 3]) - .where('foo', 'array-contains', 1) + query( + collection(db, 'test'), + where('foo', 'in', [2, 3]), + where('foo', 'array-contains', 1) + ) ).not.to.throw(); expect(() => - db - .collection('test') - .where('foo', 'in', [2, 3]) - .where('foo', 'array-contains', 1) - .where('foo', 'array-contains', 2) + query( + collection(db, 'test'), + where('foo', 'in', [2, 3]), + where('foo', 'array-contains', 1), + where('foo', 'array-contains', 2) + ) ).to.throw( "Invalid query. You cannot use more than one 'array-contains' filter." ); expect(() => - db - .collection('test') - .where('foo', 'array-contains', 1) - .where('foo', 'in', [2, 3]) - .where('foo', 'in', [2, 3]) + query( + collection(db, 'test'), + where('foo', 'array-contains', 1), + where('foo', 'in', [2, 3]), + where('foo', 'in', [2, 3]) + ) ).to.throw("Invalid query. You cannot use more than one 'in' filter."); } ); @@ -1135,46 +1148,52 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'enforce array requirements for disjunctive filters', db => { - expect(() => db.collection('test').where('foo', 'in', 2)).to.throw( + expect(() => + query(collection(db, 'test'), where('foo', 'in', 2)) + ).to.throw( "Invalid Query. A non-empty array is required for 'in' filters." ); expect(() => - db.collection('test').where('foo', 'array-contains-any', 2) + query(collection(db, 'test'), where('foo', 'array-contains-any', 2)) ).to.throw( 'Invalid Query. A non-empty array is required for ' + "'array-contains-any' filters." ); expect(() => - db - .collection('test') + query( + collection(db, 'test'), // The 10 element max includes duplicates. - .where('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9]) + where('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9]) + ) ).to.throw( "Invalid Query. 'in' filters support a maximum of 10 elements in " + 'the value array.' ); expect(() => - db - .collection('test') - .where( + query( + collection(db, 'test'), + where( 'foo', 'array-contains-any', [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9] ) + ) ).to.throw( "Invalid Query. 'array-contains-any' filters support a maximum of " + '10 elements in the value array.' ); - expect(() => db.collection('test').where('foo', 'in', [])).to.throw( + expect(() => + query(collection(db, 'test'), where('foo', 'in', [])) + ).to.throw( "Invalid Query. A non-empty array is required for 'in' filters." ); expect(() => - db.collection('test').where('foo', 'array-contains-any', []) + query(collection(db, 'test'), where('foo', 'array-contains-any', [])) ).to.throw( 'Invalid Query. A non-empty array is required for ' + "'array-contains-any' filters." @@ -1186,17 +1205,23 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'must not specify starting or ending point after orderBy', db => { - const collection = db.collection('collection'); - const query = collection.orderBy('foo'); + const coll = collection(db, 'collection'); + const query1 = query(coll, orderBy('foo')); let reason = - 'Invalid query. You must not call startAt() or startAfter() before calling Query.orderBy().'; - expect(() => query.startAt(1).orderBy('bar')).to.throw(reason); - expect(() => query.startAfter(1).orderBy('bar')).to.throw(reason); + 'Invalid query. You must not call startAt() or startAfter() before calling orderBy().'; + expect(() => query(query1, startAt(1), orderBy('bar'))).to.throw( + reason + ); + expect(() => query(query1, startAfter(1), orderBy('bar'))).to.throw( + reason + ); reason = - 'Invalid query. You must not call endAt() or endBefore() before calling Query.orderBy().'; - expect(() => query.endAt(1).orderBy('bar')).to.throw(reason); - expect(() => query.endBefore(1).orderBy('bar')).to.throw(reason); + 'Invalid query. You must not call endAt() or endBefore() before calling orderBy().'; + expect(() => query(query1, endAt(1), orderBy('bar'))).to.throw(reason); + expect(() => query(query1, endBefore(1), orderBy('bar'))).to.throw( + reason + ); } ); @@ -1204,46 +1229,42 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'must be non-empty strings or references when filtering by document ID', db => { - const collection = db.collection('test'); - expect(() => - collection.where(FieldPath.documentId(), '>=', '') - ).to.throw( - 'Invalid query. When querying with FieldPath.documentId(), you ' + + const coll = collection(db, 'test'); + expect(() => query(coll, where(documentId(), '>=', ''))).to.throw( + 'Invalid query. When querying with documentId(), you ' + 'must provide a valid document ID, but it was an empty string.' ); expect(() => - collection.where(FieldPath.documentId(), '>=', 'foo/bar/baz') + query(coll, where(documentId(), '>=', 'foo/bar/baz')) ).to.throw( `Invalid query. When querying a collection by ` + - `FieldPath.documentId(), you must provide a plain document ID, but ` + + `documentId(), you must provide a plain document ID, but ` + `'foo/bar/baz' contains a '/' character.` ); - expect(() => - collection.where(FieldPath.documentId(), '>=', 1) - ).to.throw( - 'Invalid query. When querying with FieldPath.documentId(), you must ' + + expect(() => query(coll, where(documentId(), '>=', 1))).to.throw( + 'Invalid query. When querying with documentId(), you must ' + 'provide a valid string or a DocumentReference, but it was: 1.' ); expect(() => - db.collectionGroup('foo').where(FieldPath.documentId(), '>=', 'foo') + query(collectionGroup(db, 'foo'), where(documentId(), '>=', 'foo')) ).to.throw( `Invalid query. When querying a collection group by ` + - `FieldPath.documentId(), the value provided must result in a valid document path, ` + + `documentId(), the value provided must result in a valid document path, ` + `but 'foo' is not because it has an odd number of segments (1).` ); expect(() => - collection.where(FieldPath.documentId(), 'array-contains', 1) + query(coll, where(documentId(), 'array-contains', 1)) ).to.throw( "Invalid Query. You can't perform 'array-contains' queries on " + - 'FieldPath.documentId().' + 'documentId().' ); expect(() => - collection.where(FieldPath.documentId(), 'array-contains-any', 1) + query(coll, where(documentId(), 'array-contains-any', 1)) ).to.throw( "Invalid Query. You can't perform 'array-contains-any' queries on " + - 'FieldPath.documentId().' + 'documentId().' ); } ); @@ -1252,58 +1273,54 @@ apiDescribe('Validation:', (persistence: boolean) => { persistence, 'using IN and document id must have proper document references in array', db => { - const collection = db.collection('test'); + const coll = collection(db, 'test'); expect(() => - collection.where(FieldPath.documentId(), 'in', [collection.path]) + query(coll, where(documentId(), 'in', [coll.path])) ).not.to.throw(); - expect(() => - collection.where(FieldPath.documentId(), 'in', ['']) - ).to.throw( - 'Invalid query. When querying with FieldPath.documentId(), you ' + + expect(() => query(coll, where(documentId(), 'in', ['']))).to.throw( + 'Invalid query. When querying with documentId(), you ' + 'must provide a valid document ID, but it was an empty string.' ); expect(() => - collection.where(FieldPath.documentId(), 'in', ['foo/bar/baz']) + query(coll, where(documentId(), 'in', ['foo/bar/baz'])) ).to.throw( `Invalid query. When querying a collection by ` + - `FieldPath.documentId(), you must provide a plain document ID, but ` + + `documentId(), you must provide a plain document ID, but ` + `'foo/bar/baz' contains a '/' character.` ); - expect(() => - collection.where(FieldPath.documentId(), 'in', [1, 2]) - ).to.throw( - 'Invalid query. When querying with FieldPath.documentId(), you must ' + + expect(() => query(coll, where(documentId(), 'in', [1, 2]))).to.throw( + 'Invalid query. When querying with documentId(), you must ' + 'provide a valid string or a DocumentReference, but it was: 1.' ); expect(() => - db.collectionGroup('foo').where(FieldPath.documentId(), 'in', ['foo']) + query(collectionGroup(db, 'foo'), where(documentId(), 'in', ['foo'])) ).to.throw( `Invalid query. When querying a collection group by ` + - `FieldPath.documentId(), the value provided must result in a valid document path, ` + + `documentId(), the value provided must result in a valid document path, ` + `but 'foo' is not because it has an odd number of segments (1).` ); } ); validationIt(persistence, 'cannot pass undefined as a field value', db => { - const collection = db.collection('test'); - expect(() => collection.where('foo', '==', undefined)).to.throw( - 'Function Query.where() called with invalid data. Unsupported field value: undefined' + const coll = collection(db, 'test'); + expect(() => query(coll, where('foo', '==', undefined))).to.throw( + 'Function where() called with invalid data. Unsupported field value: undefined' ); - expect(() => collection.orderBy('foo').startAt(undefined)).to.throw( - 'Function Query.startAt() called with invalid data. Unsupported field value: undefined' + expect(() => query(coll, orderBy('foo'), startAt(undefined))).to.throw( + 'Function startAt() called with invalid data. Unsupported field value: undefined' ); }); }); }); function expectSetToFail( - db: firestore.FirebaseFirestore, + db: Firestore, data: any, reason: string ): Promise { @@ -1317,7 +1334,7 @@ function expectSetToFail( } function expectUpdateToFail( - db: firestore.FirebaseFirestore, + db: Firestore, data: any, reason: string ): Promise { @@ -1335,7 +1352,7 @@ function expectUpdateToFail( * with the expected reason. */ function expectWriteToFail( - db: firestore.FirebaseFirestore, + db: Firestore, data: any, reason: string, includeSets?: boolean, @@ -1355,27 +1372,25 @@ function expectWriteToFail( reason = `${reason} (found in document ${docPath})`; } - const docRef = db.doc(docPath); + const docRef = doc(db, docPath); const error = (fnName: string): string => `Function ${fnName}() called with invalid data. ${reason}`; if (includeSets) { - expect(() => docRef.set(data)).to.throw(error('DocumentReference.set')); - expect(() => docRef.firestore.batch().set(docRef, data)).to.throw( + expect(() => setDoc(docRef, data)).to.throw(error('setDoc')); + expect(() => writeBatch(db).set(docRef, data)).to.throw( error('WriteBatch.set') ); } if (includeUpdates) { - expect(() => docRef.update(data)).to.throw( - error('DocumentReference.update') - ); - expect(() => docRef.firestore.batch().update(docRef, data)).to.throw( + expect(() => updateDoc(docRef, data)).to.throw(error('updateDoc')); + expect(() => writeBatch(db).update(docRef, data)).to.throw( error('WriteBatch.update') ); } - return docRef.firestore.runTransaction(async txn => { + return runTransaction(db, async txn => { if (includeSets) { expect(() => txn.set(docRef, data)).to.throw(error('Transaction.set')); } @@ -1393,7 +1408,7 @@ function expectWriteToFail( * they fail with the specified reason. */ function expectFieldPathToFail( - snapshot: firestore.DocumentSnapshot, + snapshot: DocumentSnapshot, path: string, reason: string ): Promise { @@ -1407,19 +1422,19 @@ function expectFieldPathToFail( const db = snapshot.ref.firestore; // Query filter / order fields. - const coll = db.collection('test-collection'); + const coll = collection(db, 'test-collection'); // <=, etc omitted for brevity since the code path is trivially // shared. - expect(() => coll.where(path, '==', 1)).to.throw( - `Function Query.where() called with invalid data. ` + reason + expect(() => query(coll, where(path, '==', 1))).to.throw( + `Function where() called with invalid data. ` + reason ); - expect(() => coll.orderBy(path)).to.throw( - `Function Query.orderBy() called with invalid data. ` + reason + expect(() => query(coll, orderBy(path))).to.throw( + `Function orderBy() called with invalid data. ` + reason ); // Update paths. const data = {} as { [field: string]: number }; data[path] = 1; - return expectUpdateToFail(db, data, reason); + return expectUpdateToFail(db as Firestore, data, reason); }); } diff --git a/packages/firestore/test/integration/api_internal/database.test.ts b/packages/firestore/test/integration/api_internal/database.test.ts index 457764845c6..2344530438a 100644 --- a/packages/firestore/test/integration/api_internal/database.test.ts +++ b/packages/firestore/test/integration/api_internal/database.test.ts @@ -15,11 +15,19 @@ * limitations under the License. */ +import { deleteApp } from '@firebase/app'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { User } from '../../../src/auth/user'; import { SimpleDb } from '../../../src/local/simple_db'; +import { + clearIndexedDbPersistence, + disableNetwork, + doc, + setDoc, + waitForPendingWrites +} from '../util/firebase_export'; import { apiDescribe, withTestDoc } from '../util/helpers'; import { withMockCredentialProviderTestDb } from '../util/internal_helpers'; @@ -30,16 +38,15 @@ apiDescribe('Database (with internal API)', (persistence: boolean) => { (persistence ? it : it.skip)( 'will reject the promise if clear persistence fails', async () => { - await withTestDoc(persistence, async docRef => { + await withTestDoc(persistence, async (docRef, firestore) => { const oldDelete = SimpleDb.delete; try { SimpleDb.delete = (name: string): Promise => { return Promise.reject('Failed to delete the database.'); }; - const firestore = docRef.firestore; - await firestore.app.delete(); + await deleteApp(firestore.app); await expect( - firestore.clearPersistence() + clearIndexedDbPersistence(firestore) ).to.eventually.be.rejectedWith('Failed to delete the database.'); } finally { SimpleDb.delete = oldDelete; @@ -53,10 +60,9 @@ apiDescribe('Database (with internal API)', (persistence: boolean) => { persistence, async (db, mockCredentialsProvider) => { // Prevent pending writes receiving acknowledgement. - await db.disableNetwork(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - db.doc('abc/123').set({ foo: 'bar' }); - const awaitPendingWrite = db.waitForPendingWrites(); + await disableNetwork(db); + void setDoc(doc(db, 'abc/123'), { foo: 'bar' }); + const awaitPendingWrite = waitForPendingWrites(db); mockCredentialsProvider.triggerUserChange(new User('user_1')); @@ -69,12 +75,12 @@ apiDescribe('Database (with internal API)', (persistence: boolean) => { it('app delete leads to instance termination', async () => { await withTestDoc(persistence, async docRef => { - await docRef.set({ foo: 'bar' }); + await setDoc(docRef, { foo: 'bar' }); const app = docRef.firestore.app; - await app.delete(); + await deleteApp(app); // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect((docRef.firestore as any)._delegate._terminated).to.be.true; + expect((docRef.firestore as any)._terminated).to.be.true; }); }); }); diff --git a/packages/firestore/test/integration/api_internal/idle_timeout.test.ts b/packages/firestore/test/integration/api_internal/idle_timeout.test.ts index b74bfafd6cd..ba360622f0f 100644 --- a/packages/firestore/test/integration/api_internal/idle_timeout.test.ts +++ b/packages/firestore/test/integration/api_internal/idle_timeout.test.ts @@ -17,30 +17,31 @@ import { TimerId } from '../../../src/util/async_queue'; import { Deferred } from '../../util/promise'; +import { collection, doc, onSnapshot, setDoc } from '../util/firebase_export'; import { apiDescribe, withTestDb } from '../util/helpers'; import { asyncQueue } from '../util/internal_helpers'; apiDescribe('Idle Timeout', (persistence: boolean) => { - it('can write document after idle timeout', () => { + it('can write documdent after idle timeout', () => { return withTestDb(persistence, db => { - const docRef = db.collection('test-collection').doc(); - return docRef - .set({ foo: 'bar' }) + const docRef = doc(collection(db, 'test-collection')); + return setDoc(docRef, { foo: 'bar' }) .then(() => { return asyncQueue(db).runAllDelayedOperationsUntil( TimerId.WriteStreamIdle ); }) - .then(() => docRef.set({ foo: 'bar' })); + .then(() => setDoc(docRef, { foo: 'bar' })); }); }); it('can watch documents after idle timeout', () => { return withTestDb(persistence, db => { const awaitOnlineSnapshot = (): Promise => { - const docRef = db.collection('test-collection').doc(); + const docRef = doc(collection(db, 'test-collection')); const deferred = new Deferred(); - const unregister = docRef.onSnapshot( + const unregister = onSnapshot( + docRef, { includeMetadataChanges: true }, snapshot => { if (!snapshot.metadata.fromCache) { diff --git a/packages/firestore/test/integration/api_internal/transaction.test.ts b/packages/firestore/test/integration/api_internal/transaction.test.ts index 1b636adbd98..f8665094b6b 100644 --- a/packages/firestore/test/integration/api_internal/transaction.test.ts +++ b/packages/firestore/test/integration/api_internal/transaction.test.ts @@ -15,17 +15,22 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { DEFAULT_MAX_ATTEMPTS_COUNT } from '../../../src/core/transaction_runner'; import { TimerId } from '../../../src/util/async_queue'; import { Deferred } from '../../util/promise'; -import * as integrationHelpers from '../util/helpers'; +import { + collection, + doc, + FirestoreError, + getDoc, + runTransaction, + setDoc +} from '../util/firebase_export'; +import { apiDescribe, withTestDb } from '../util/helpers'; import { asyncQueue } from '../util/internal_helpers'; -const apiDescribe = integrationHelpers.apiDescribe; - apiDescribe( 'Database transactions (with internal API)', (persistence: boolean) => { @@ -37,26 +42,25 @@ apiDescribe( const barrier = new Deferred(); let started = 0; - return integrationHelpers.withTestDb(persistence, db => { + return withTestDb(persistence, db => { asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); - const doc = db.collection('counters').doc(); - return doc - .set({ - count: 5 - }) + const docRef = doc(collection(db, 'counters')); + return setDoc(docRef, { + count: 5 + }) .then(() => { // Make 3 transactions that will all increment. for (let i = 0; i < 3; i++) { const resolveRead = new Deferred(); readPromises.push(resolveRead.promise); transactionPromises.push( - db.runTransaction(transaction => { - return transaction.get(doc).then(snapshot => { + runTransaction(db, transaction => { + return transaction.get(docRef).then(snapshot => { expect(snapshot).to.exist; started = started + 1; resolveRead.resolve(); return barrier.promise.then(() => { - transaction.set(doc, { + transaction.set(docRef, { count: snapshot.data()!['count'] + 1 }); }); @@ -77,7 +81,7 @@ apiDescribe( }) .then(() => { // Now all transaction should be completed, so check the result. - return doc.get(); + return getDoc(docRef); }) .then(snapshot => { expect(snapshot).to.exist; @@ -94,27 +98,26 @@ apiDescribe( const barrier = new Deferred(); let counter = 0; - return integrationHelpers.withTestDb(persistence, db => { + return withTestDb(persistence, db => { asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); - const doc = db.collection('counters').doc(); - return doc - .set({ - count: 5, - other: 'yes' - }) + const docRef = doc(collection(db, 'counters')); + return setDoc(docRef, { + count: 5, + other: 'yes' + }) .then(() => { // Make 3 transactions that will all increment. for (let i = 0; i < 3; i++) { const resolveRead = new Deferred(); readPromises.push(resolveRead.promise); transactionPromises.push( - db.runTransaction(transaction => { - return transaction.get(doc).then(snapshot => { + runTransaction(db, transaction => { + return transaction.get(docRef).then(snapshot => { expect(snapshot).to.exist; counter = counter + 1; resolveRead.resolve(); return barrier.promise.then(() => { - transaction.update(doc, { + transaction.update(docRef, { count: snapshot.data()!['count'] + 1 }); }); @@ -138,7 +141,7 @@ apiDescribe( // There should be a maximum of 3 retries: once for the 2nd update, // and twice for the 3rd update. expect(counter).to.be.lessThan(7); - return doc.get(); + return getDoc(docRef); }) .then(snapshot => { expect(snapshot).to.exist; @@ -149,39 +152,38 @@ apiDescribe( }); it('handle reading a doc twice with different versions', () => { - return integrationHelpers.withTestDb(persistence, db => { + return withTestDb(persistence, db => { asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); - const doc = db.collection('counters').doc(); + const docRef = doc(collection(db, 'counters')); let counter = 0; - return doc - .set({ - count: 15 - }) + return setDoc(docRef, { + count: 15 + }) .then(() => { - return db.runTransaction(transaction => { + return runTransaction(db, transaction => { counter++; - // Get the doc once. + // Get the docRef once. return ( transaction - .get(doc) + .get(docRef) // Do a write outside of the transaction. Because the transaction // will retry, set the document to a different value each time. - .then(() => doc.set({ count: 1234 + counter })) - // Get the doc again in the transaction with the new + .then(() => setDoc(docRef, { count: 1234 + counter })) + // Get the docRef again in the transaction with the new // version. - .then(() => transaction.get(doc)) - // Now try to update the doc from within the transaction. + .then(() => transaction.get(docRef)) + // Now try to update the docRef from within the transaction. // This should fail, because we read 15 earlier. - .then(() => transaction.set(doc, { count: 16 })) + .then(() => transaction.set(docRef, { count: 16 })) ); }); }) .then(() => expect.fail('transaction should fail')) - .catch(err => { + .catch((err: FirestoreError) => { expect(err).to.exist; - expect((err as firestore.FirestoreError).code).to.equal('aborted'); + expect(err.code).to.equal('aborted'); }) - .then(() => doc.get()) + .then(() => getDoc(docRef)) .then(snapshot => { expect(snapshot.data()!['count']).to.equal(1234 + counter); expect(counter).to.equal(DEFAULT_MAX_ATTEMPTS_COUNT); diff --git a/packages/firestore/test/integration/bootstrap.ts b/packages/firestore/test/integration/bootstrap.ts index 3b333395fc1..52ee47869fe 100644 --- a/packages/firestore/test/integration/bootstrap.ts +++ b/packages/firestore/test/integration/bootstrap.ts @@ -16,7 +16,6 @@ */ import '../../src/index'; -import '../../compat/index'; /** * This will include all of the test files and compile them as needed diff --git a/packages/firestore/test/integration/browser/indexeddb.test.ts b/packages/firestore/test/integration/browser/indexeddb.test.ts index cb53cf601c6..46f4943a8d8 100644 --- a/packages/firestore/test/integration/browser/indexeddb.test.ts +++ b/packages/firestore/test/integration/browser/indexeddb.test.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { isPersistenceAvailable, withTestDb } from '../util/helpers'; @@ -51,8 +50,8 @@ describe('where persistence is unsupported, enablePersistence', () => { }); // Do the set immediately without waiting on the promise. - const doc = db.collection('test-collection').doc(); - return doc.set({ foo: 'bar' }).then(() => persistenceFailedPromise); + const doc = collection(db, 'test-collection').doc(); + return setDoc(doc, { foo: 'bar' }).then(() => persistenceFailedPromise); }); }); }); diff --git a/packages/firestore/test/integration/prime_backend.test.ts b/packages/firestore/test/integration/prime_backend.test.ts index df26ab25541..0be6b91d38e 100644 --- a/packages/firestore/test/integration/prime_backend.test.ts +++ b/packages/firestore/test/integration/prime_backend.test.ts @@ -15,10 +15,14 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; import { EventsAccumulator } from './util/events_accumulator'; +import { + DocumentSnapshot, + onSnapshot, + runTransaction +} from './util/firebase_export'; import { withTestDoc } from './util/helpers'; // Firestore databases can be subject to a ~30s "cold start" delay if they have not been used @@ -31,15 +35,15 @@ before( function (): Promise { this.timeout(PRIMING_TIMEOUT_MS); - return withTestDoc(/*persistence=*/ false, async doc => { - const accumulator = new EventsAccumulator(); - const unsubscribe = doc.onSnapshot(accumulator.storeEvent); + return withTestDoc(/*persistence=*/ false, async (doc, db) => { + const accumulator = new EventsAccumulator(); + const unsubscribe = onSnapshot(doc, accumulator.storeEvent); // Wait for watch to initialize and deliver first event. await accumulator.awaitRemoteEvent(); // Use a transaction to perform a write without triggering any local events. - await doc.firestore.runTransaction(async txn => { + await runTransaction(db, async txn => { txn.set(doc, { value: 'done' }); }); diff --git a/packages/firestore/test/integration/util/events_accumulator.ts b/packages/firestore/test/integration/util/events_accumulator.ts index 63a2198ad43..64697a88d1a 100644 --- a/packages/firestore/test/integration/util/events_accumulator.ts +++ b/packages/firestore/test/integration/util/events_accumulator.ts @@ -15,18 +15,16 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; +import { DocumentSnapshot, QuerySnapshot } from '../../../src'; import { Deferred } from '../../util/promise'; /** * A helper object that can accumulate an arbitrary amount of events and resolve * a promise when expected number has been emitted. */ -export class EventsAccumulator< - T extends firestore.DocumentSnapshot | firestore.QuerySnapshot -> { +export class EventsAccumulator { private events: T[] = []; private waitingFor: number = 0; private deferred: Deferred | null = null; diff --git a/packages/firestore/test/integration/util/firebase_export.ts b/packages/firestore/test/integration/util/firebase_export.ts index 26f77b8b78a..5a54f53c133 100644 --- a/packages/firestore/test/integration/util/firebase_export.ts +++ b/packages/firestore/test/integration/util/firebase_export.ts @@ -20,41 +20,27 @@ // reference to the minified sources. If you change any exports in this file, // you need to also adjust "integration/firestore/firebase_export.ts". -import firebase from '@firebase/app-compat'; -import { FirebaseApp } from '@firebase/app-types'; -import * as firestore from '@firebase/firestore-types'; +import { FirebaseApp, initializeApp } from '@firebase/app'; -import { Blob } from '../../../compat/api/blob'; -import { - Firestore, - DocumentReference, - QueryDocumentSnapshot -} from '../../../compat/api/database'; -import { FieldPath } from '../../../compat/api/field_path'; -import { FieldValue } from '../../../compat/api/field_value'; -import { GeoPoint } from '../../../compat/api/geo_point'; -import { Timestamp } from '../../../compat/api/timestamp'; +import { Firestore, initializeFirestore } from '../../../src'; +import { PrivateSettings } from '../../../src/lite-api/settings'; // TODO(dimond): Right now we create a new app and Firestore instance for // every test and never clean them up. We may need to revisit. let appCount = 0; -/** - * Creates a new test instance of Firestore using either firebase.firestore() - * or `initializeFirestore` from the modular API. - */ export function newTestFirestore( projectId: string, nameOrApp?: string | FirebaseApp, - settings?: firestore.Settings -): firestore.FirebaseFirestore { + settings?: PrivateSettings +): Firestore { if (nameOrApp === undefined) { nameOrApp = 'test-app-' + appCount++; } const app = typeof nameOrApp === 'string' - ? firebase.initializeApp( + ? initializeApp( { apiKey: 'fake-api-key', projectId @@ -63,21 +49,7 @@ export function newTestFirestore( ) : nameOrApp; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const firestore = (firebase as any).firestore(app); - if (settings) { - firestore.settings(settings); - } - return firestore; + return initializeFirestore(app, settings || {}); } -export { - Firestore, - FieldValue, - FieldPath, - Timestamp, - Blob, - GeoPoint, - DocumentReference, - QueryDocumentSnapshot -}; +export * from '../../../src'; diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 72455a2a82e..87f2ea31ad9 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -15,10 +15,24 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { isIndexedDBAvailable } from '@firebase/util'; import * as firebaseExport from './firebase_export'; +import { + collection, + doc, + DocumentReference, + Firestore, + terminate, + clearIndexedDbPersistence, + enableIndexedDbPersistence, + Settings, + CollectionReference, + DocumentData, + QuerySnapshot, + setDoc, + SnapshotListenOptions +} from './firebase_export'; import { ALT_PROJECT_ID, DEFAULT_PROJECT_ID, @@ -91,24 +105,22 @@ apiDescribe.skip = apiDescribeInternal.bind(null, describe.skip); apiDescribe.only = apiDescribeInternal.bind(null, describe.only); /** Converts the documents in a QuerySnapshot to an array with the data of each document. */ -export function toDataArray( - docSet: firestore.QuerySnapshot -): firestore.DocumentData[] { +export function toDataArray(docSet: QuerySnapshot): DocumentData[] { return docSet.docs.map(d => d.data()); } /** Converts the changes in a QuerySnapshot to an array with the data of each document. */ export function toChangesArray( - docSet: firestore.QuerySnapshot, - options?: firestore.SnapshotListenOptions -): firestore.DocumentData[] { + docSet: QuerySnapshot, + options?: SnapshotListenOptions +): DocumentData[] { return docSet.docChanges(options).map(d => d.doc.data()); } -export function toDataMap(docSet: firestore.QuerySnapshot): { - [field: string]: firestore.DocumentData; +export function toDataMap(docSet: QuerySnapshot): { + [field: string]: DocumentData; } { - const docsData: { [field: string]: firestore.DocumentData } = {}; + const docsData: { [field: string]: DocumentData } = {}; docSet.forEach(doc => { docsData[doc.id] = doc.data(); }); @@ -116,13 +128,13 @@ export function toDataMap(docSet: firestore.QuerySnapshot): { } /** Converts a DocumentSet to an array with the id of each document */ -export function toIds(docSet: firestore.QuerySnapshot): string[] { +export function toIds(docSet: QuerySnapshot): string[] { return docSet.docs.map(d => d.id); } export function withTestDb( persistence: boolean, - fn: (db: firestore.FirebaseFirestore) => Promise + fn: (db: Firestore) => Promise ): Promise { return withTestDbs(persistence, 1, ([db]) => { return fn(db); @@ -132,7 +144,7 @@ export function withTestDb( /** Runs provided fn with a db for an alternate project id. */ export function withAlternateTestDb( persistence: boolean, - fn: (db: firestore.FirebaseFirestore) => Promise + fn: (db: Firestore) => Promise ): Promise { return withTestDbsSettings( persistence, @@ -148,7 +160,7 @@ export function withAlternateTestDb( export function withTestDbs( persistence: boolean, numDbs: number, - fn: (db: firestore.FirebaseFirestore[]) => Promise + fn: (db: Firestore[]) => Promise ): Promise { return withTestDbsSettings( persistence, @@ -161,20 +173,20 @@ export function withTestDbs( export async function withTestDbsSettings( persistence: boolean, projectId: string, - settings: firestore.Settings, + settings: Settings, numDbs: number, - fn: (db: firestore.FirebaseFirestore[]) => Promise + fn: (db: Firestore[]) => Promise ): Promise { if (numDbs === 0) { throw new Error("Can't test with no databases"); } - const dbs: firestore.FirebaseFirestore[] = []; + const dbs: Firestore[] = []; for (let i = 0; i < numDbs; i++) { const db = newTestFirestore(projectId, /* name =*/ undefined, settings); if (persistence) { - await db.enablePersistence(); + await enableIndexedDbPersistence(db); } dbs.push(db); } @@ -183,9 +195,9 @@ export async function withTestDbsSettings( await fn(dbs); } finally { for (const db of dbs) { - await db.terminate(); + await terminate(db); if (persistence) { - await db.clearPersistence(); + await clearIndexedDbPersistence(db); } } } @@ -193,17 +205,17 @@ export async function withTestDbsSettings( export function withTestDoc( persistence: boolean, - fn: (doc: firestore.DocumentReference) => Promise + fn: (doc: DocumentReference, db: Firestore) => Promise ): Promise { return withTestDb(persistence, db => { - return fn(db.collection('test-collection').doc()); + return fn(doc(collection(db, 'test-collection')), db); }); } export function withTestDocAndSettings( persistence: boolean, - settings: firestore.Settings, - fn: (doc: firestore.DocumentReference) => Promise + settings: Settings, + fn: (doc: DocumentReference) => Promise ): Promise { return withTestDbsSettings( persistence, @@ -211,7 +223,7 @@ export function withTestDocAndSettings( settings, 1, ([db]) => { - return fn(db.collection('test-collection').doc()); + return fn(doc(collection(db, 'test-collection'))); } ); } @@ -219,29 +231,27 @@ export function withTestDocAndSettings( // TODO(rsgowman): Modify withTestDoc to take in (an optional) initialData and // fix existing usages of it. Then delete this function. This makes withTestDoc // more analogous to withTestCollection and eliminates the pattern of -// `withTestDoc(..., docRef => { docRef.set(initialData) ...});` that otherwise is +// `withTestDoc(..., docRef => { setDoc(docRef, initialData) ...});` that otherwise is // quite common. export function withTestDocAndInitialData( persistence: boolean, - initialData: firestore.DocumentData | null, - fn: (doc: firestore.DocumentReference) => Promise + initialData: DocumentData | null, + fn: (doc: DocumentReference, db: Firestore) => Promise ): Promise { return withTestDb(persistence, db => { - const docRef: firestore.DocumentReference = db - .collection('test-collection') - .doc(); + const docRef: DocumentReference = doc(collection(db, 'test-collection')); if (initialData) { - return docRef.set(initialData).then(() => fn(docRef)); + return setDoc(docRef, initialData).then(() => fn(docRef, db)); } else { - return fn(docRef); + return fn(docRef, db); } }); } export function withTestCollection( persistence: boolean, - docs: { [key: string]: firestore.DocumentData }, - fn: (collection: firestore.CollectionReference) => Promise + docs: { [key: string]: DocumentData }, + fn: (collection: CollectionReference, db: Firestore) => Promise ): Promise { return withTestCollectionSettings(persistence, DEFAULT_SETTINGS, docs, fn); } @@ -250,9 +260,9 @@ export function withTestCollection( // return the same collection every time. export function withTestCollectionSettings( persistence: boolean, - settings: firestore.Settings, - docs: { [key: string]: firestore.DocumentData }, - fn: (collection: firestore.CollectionReference) => Promise + settings: Settings, + docs: { [key: string]: DocumentData }, + fn: (collection: CollectionReference, db: Firestore) => Promise ): Promise { return withTestDbsSettings( persistence, @@ -261,15 +271,15 @@ export function withTestCollectionSettings( 2, ([testDb, setupDb]) => { // Abuse .doc() to get a random ID. - const collectionId = 'test-collection-' + testDb.collection('x').doc().id; - const testCollection = testDb.collection(collectionId); - const setupCollection = setupDb.collection(collectionId); + const collectionId = 'test-collection-' + doc(collection(testDb, 'x')).id; + const testCollection = collection(testDb, collectionId); + const setupCollection = collection(setupDb, collectionId); const sets: Array> = []; Object.keys(docs).forEach(key => { - sets.push(setupCollection.doc(key).set(docs[key])); + sets.push(setDoc(doc(setupCollection, key), docs[key])); }); return Promise.all(sets).then(() => { - return fn(testCollection); + return fn(testCollection, testDb); }); } ); diff --git a/packages/firestore/test/integration/util/internal_helpers.ts b/packages/firestore/test/integration/util/internal_helpers.ts index 8548241b7bf..4069acf3975 100644 --- a/packages/firestore/test/integration/util/internal_helpers.ts +++ b/packages/firestore/test/integration/util/internal_helpers.ts @@ -15,9 +15,6 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; - -import { Firestore } from '../../../compat/api/database'; import { CredentialChangeListener, CredentialsProvider, @@ -35,11 +32,12 @@ import { TestBundleBuilder } from '../../unit/util/bundle_data'; import { collectionReference } from '../../util/api_helpers'; import { key } from '../../util/helpers'; +import { Firestore, DocumentData } from './firebase_export'; import { withTestDbsSettings } from './helpers'; import { DEFAULT_PROJECT_ID, DEFAULT_SETTINGS } from './settings'; -export function asyncQueue(db: firestore.FirebaseFirestore): AsyncQueueImpl { - return (db as Firestore)._delegate._queue as AsyncQueueImpl; +export function asyncQueue(db: Firestore): AsyncQueueImpl { + return db._queue as AsyncQueueImpl; } export function getDefaultDatabaseInfo(): DatabaseInfo { @@ -93,7 +91,7 @@ export class MockAuthCredentialsProvider extends EmptyAuthCredentialsProvider { export function withMockCredentialProviderTestDb( persistence: boolean, fn: ( - db: firestore.FirebaseFirestore, + db: Firestore, mockCredential: MockAuthCredentialsProvider ) => Promise ): Promise { @@ -124,7 +122,7 @@ export function withMockCredentialProviderTestDb( function bundleWithTestDocsAndQueries( projectId: string = 'test-project' ): string { - const testDocs: { [key: string]: firestore.DocumentData } = { + const testDocs: { [key: string]: DocumentData } = { a: { k: { stringValue: 'a' }, bar: { integerValue: 1 } }, b: { k: { stringValue: 'b' }, bar: { integerValue: 2 } } }; diff --git a/packages/firestore/test/integration/util/settings.ts b/packages/firestore/test/integration/util/settings.ts index 754af6bf5c3..5457417f273 100644 --- a/packages/firestore/test/integration/util/settings.ts +++ b/packages/firestore/test/integration/util/settings.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; +import { PrivateSettings } from '../../../src/lite-api/settings'; /** * NOTE: These helpers are used by api/ tests and therefore may not have any @@ -47,7 +47,7 @@ export const DEFAULT_SETTINGS = getDefaultSettings(); // eslint-disable-next-line no-console console.log(`Default Settings: ${JSON.stringify(DEFAULT_SETTINGS)}`); -function getDefaultSettings(): firestore.Settings { +function getDefaultSettings(): PrivateSettings { const karma = typeof __karma__ !== 'undefined' ? __karma__ : undefined; if (karma && karma.config.firestoreSettings) { return karma.config.firestoreSettings; diff --git a/packages/firestore/test/register.ts b/packages/firestore/test/register.ts index be93ecdccc2..6932c467c6d 100644 --- a/packages/firestore/test/register.ts +++ b/packages/firestore/test/register.ts @@ -15,11 +15,5 @@ * limitations under the License. */ -import firebase from '@firebase/app-compat'; -import { FirebaseNamespace } from '@firebase/app-types'; - -import { registerFirestore as registerFirestoreCompat } from '../compat'; import { registerFirestore } from '../src/register'; - registerFirestore(); -registerFirestoreCompat(firebase as unknown as FirebaseNamespace); diff --git a/packages/firestore/test/unit/api/blob.test.ts b/packages/firestore/test/unit/api/bytes.test.ts similarity index 87% rename from packages/firestore/test/unit/api/blob.test.ts rename to packages/firestore/test/unit/api/bytes.test.ts index ae85ebde898..22e98d495cc 100644 --- a/packages/firestore/test/unit/api/blob.test.ts +++ b/packages/firestore/test/unit/api/bytes.test.ts @@ -17,10 +17,10 @@ import { expect } from 'chai'; -import { Blob } from '../../../compat/api/blob'; +import { Bytes } from '../../../src'; import { blob, expectEqual, expectNotEqual } from '../../util/helpers'; -describe('Blob', () => { +describe('Bytes', () => { const base64Mappings: { [base64: string]: number[] } = { '': [], 'AA==': [0], @@ -30,7 +30,7 @@ describe('Blob', () => { it('constructs values from Base64', () => { Object.keys(base64Mappings).forEach(base64Str => { - const blob = Blob.fromBase64String(base64Str); + const blob = Bytes.fromBase64String(base64Str); const expectedBytes = base64Mappings[base64Str]; const actualBytes = blob.toUint8Array(); expect(actualBytes.length).to.equal(expectedBytes.length); @@ -48,13 +48,13 @@ describe('Blob', () => { }); it('Blob throws on invalid Base64 strings', () => { - expect(() => Blob.fromBase64String('not-base64!')).to.throw( + expect(() => Bytes.fromBase64String('not-base64!')).to.throw( /Failed to construct data from Base64 string:/ ); }); it('works with instanceof checks', () => { - expect(Blob.fromBase64String('') instanceof Blob).to.equal(true); + expect(Bytes.fromBase64String('') instanceof Blob).to.equal(true); }); it('support equality checking with isEqual()', () => { diff --git a/packages/firestore/test/unit/api/field_value.test.ts b/packages/firestore/test/unit/api/field_value.test.ts index 9ba1b0585a1..105750a22e8 100644 --- a/packages/firestore/test/unit/api/field_value.test.ts +++ b/packages/firestore/test/unit/api/field_value.test.ts @@ -17,29 +17,36 @@ import { expect } from 'chai'; -import { FieldValue } from '../../../compat/api/field_value'; +import { + arrayRemove, + arrayUnion, + deleteField, + FieldValue, + increment, + serverTimestamp +} from '../../../src'; import { expectEqual, expectNotEqual } from '../../util/helpers'; describe('FieldValue', () => { it('support equality checking with isEqual()', () => { - expectEqual(FieldValue.delete(), FieldValue.delete()); - expectEqual(FieldValue.serverTimestamp(), FieldValue.serverTimestamp()); - expectNotEqual(FieldValue.delete(), FieldValue.serverTimestamp()); + expectEqual(deleteField(), deleteField()); + expectEqual(serverTimestamp(), serverTimestamp()); + expectNotEqual(deleteField(), serverTimestamp()); }); it('support instanceof checks', () => { - expect(FieldValue.delete()).to.be.an.instanceOf(FieldValue); - expect(FieldValue.serverTimestamp()).to.be.an.instanceOf(FieldValue); - expect(FieldValue.increment(1)).to.be.an.instanceOf(FieldValue); - expect(FieldValue.arrayUnion('a')).to.be.an.instanceOf(FieldValue); - expect(FieldValue.arrayRemove('a')).to.be.an.instanceOf(FieldValue); + expect(deleteField()).to.be.an.instanceOf(FieldValue); + expect(serverTimestamp()).to.be.an.instanceOf(FieldValue); + expect(arrayRemove(1)).to.be.an.instanceOf(FieldValue); + expect(arrayUnion('a')).to.be.an.instanceOf(FieldValue); + expect(arrayRemove('a')).to.be.an.instanceOf(FieldValue); }); it('JSON.stringify() does not throw', () => { - JSON.stringify(FieldValue.delete()); - JSON.stringify(FieldValue.serverTimestamp()); - JSON.stringify(FieldValue.increment(1)); - JSON.stringify(FieldValue.arrayUnion(2)); - JSON.stringify(FieldValue.arrayRemove(3)); + JSON.stringify(deleteField()); + JSON.stringify(serverTimestamp()); + JSON.stringify(increment(1)); + JSON.stringify(arrayUnion(2)); + JSON.stringify(arrayRemove(3)); }); }); diff --git a/packages/firestore/test/unit/core/query.test.ts b/packages/firestore/test/unit/core/query.test.ts index 248db631b3d..6a86a5e30ba 100644 --- a/packages/firestore/test/unit/core/query.test.ts +++ b/packages/firestore/test/unit/core/query.test.ts @@ -17,9 +17,7 @@ import { expect } from 'chai'; -import { Blob } from '../../../compat/api/blob'; -import { GeoPoint } from '../../../src/api/geo_point'; -import { Timestamp } from '../../../src/api/timestamp'; +import { Bytes, GeoPoint, Timestamp } from '../../../src'; import { canonifyQuery, LimitType, @@ -647,7 +645,7 @@ describe('Query', () => { assertCanonicalId( query( 'collection', - filter('a', '>=', Blob.fromUint8Array(new Uint8Array([1, 2, 3]))) + filter('a', '>=', Bytes.fromUint8Array(new Uint8Array([1, 2, 3]))) ), 'collection|f:a>=AQID|ob:aasc,__name__asc' ); diff --git a/packages/firestore/test/unit/local/local_store.test.ts b/packages/firestore/test/unit/local/local_store.test.ts index 71e0ab862cd..8bf63c28435 100644 --- a/packages/firestore/test/unit/local/local_store.test.ts +++ b/packages/firestore/test/unit/local/local_store.test.ts @@ -17,8 +17,7 @@ import { expect } from 'chai'; -import { FieldValue } from '../../../compat/api/field_value'; -import { Timestamp } from '../../../src/api/timestamp'; +import { arrayUnion, increment, Timestamp } from '../../../src'; import { User } from '../../../src/auth/user'; import { BundledDocuments, NamedQuery } from '../../../src/core/bundle'; import { BundleConverterImpl } from '../../../src/core/bundle_impl'; @@ -1274,7 +1273,7 @@ function genericLocalStoreTests( } ); - // TODO(mrschmidt): The FieldValue.increment() field transform tests below + // TODO(mrschmidt): The increment() field transform tests below // would probably be better implemented as spec tests but currently they don't // support transforms. @@ -1283,10 +1282,10 @@ function genericLocalStoreTests( .after(setMutation('foo/bar', { sum: 0 })) .toReturnChanged(doc('foo/bar', 0, { sum: 0 }).setHasLocalMutations()) .toContain(doc('foo/bar', 0, { sum: 0 }).setHasLocalMutations()) - .after(patchMutation('foo/bar', { sum: FieldValue.increment(1) })) + .after(patchMutation('foo/bar', { sum: increment(1) })) .toReturnChanged(doc('foo/bar', 0, { sum: 1 }).setHasLocalMutations()) .toContain(doc('foo/bar', 0, { sum: 1 }).setHasLocalMutations()) - .after(patchMutation('foo/bar', { sum: FieldValue.increment(2) })) + .after(patchMutation('foo/bar', { sum: increment(2) })) .toReturnChanged(doc('foo/bar', 0, { sum: 3 }).setHasLocalMutations()) .toContain(doc('foo/bar', 0, { sum: 3 }).setHasLocalMutations()) .finish(); @@ -1305,7 +1304,7 @@ function genericLocalStoreTests( doc('foo/bar', 1, { sum: 0 }).setHasCommittedMutations() ) .toContain(doc('foo/bar', 1, { sum: 0 }).setHasCommittedMutations()) - .after(patchMutation('foo/bar', { sum: FieldValue.increment(1) })) + .after(patchMutation('foo/bar', { sum: increment(1) })) .toReturnChanged(doc('foo/bar', 1, { sum: 1 }).setHasLocalMutations()) .toContain(doc('foo/bar', 1, { sum: 1 }).setHasLocalMutations()) .afterAcknowledgingMutation({ @@ -1316,7 +1315,7 @@ function genericLocalStoreTests( doc('foo/bar', 2, { sum: 1 }).setHasCommittedMutations() ) .toContain(doc('foo/bar', 2, { sum: 1 }).setHasCommittedMutations()) - .after(patchMutation('foo/bar', { sum: FieldValue.increment(2) })) + .after(patchMutation('foo/bar', { sum: increment(2) })) .toReturnChanged(doc('foo/bar', 2, { sum: 3 }).setHasLocalMutations()) .toContain(doc('foo/bar', 2, { sum: 3 }).setHasLocalMutations()) .finish(); @@ -1338,7 +1337,7 @@ function genericLocalStoreTests( .afterAcknowledgingMutation({ documentVersion: 1 }) .toReturnChanged(doc('foo/bar', 1, { sum: 0 })) .toContain(doc('foo/bar', 1, { sum: 0 })) - .after(patchMutation('foo/bar', { sum: FieldValue.increment(1) })) + .after(patchMutation('foo/bar', { sum: increment(1) })) .toReturnChanged(doc('foo/bar', 1, { sum: 1 }).setHasLocalMutations()) .toContain(doc('foo/bar', 1, { sum: 1 }).setHasLocalMutations()) // The value in this remote event gets ignored since we still have a @@ -1350,7 +1349,7 @@ function genericLocalStoreTests( .toContain(doc('foo/bar', 2, { sum: 1 }).setHasLocalMutations()) // Add another increment. Note that we still compute the increment based // on the local value. - .after(patchMutation('foo/bar', { sum: FieldValue.increment(2) })) + .after(patchMutation('foo/bar', { sum: increment(2) })) .toReturnChanged(doc('foo/bar', 2, { sum: 3 }).setHasLocalMutations()) .toContain(doc('foo/bar', 2, { sum: 3 }).setHasLocalMutations()) .afterAcknowledgingMutation({ @@ -1399,9 +1398,9 @@ function genericLocalStoreTests( ) .toReturnChanged(doc('foo/bar', 1, { sum: 0, arrayUnion: [] })) .afterMutations([ - patchMutation('foo/bar', { sum: FieldValue.increment(1) }), + patchMutation('foo/bar', { sum: increment(1) }), patchMutation('foo/bar', { - arrayUnion: FieldValue.arrayUnion('foo') + arrayUnion: arrayUnion('foo') }) ]) .toReturnChanged( @@ -1435,11 +1434,7 @@ function genericLocalStoreTests( .afterAllocatingQuery(query1) .toReturnTargetId(2) .after( - patchMutation( - 'foo/bar', - { sum: FieldValue.increment(1) }, - Precondition.none() - ) + patchMutation('foo/bar', { sum: increment(1) }, Precondition.none()) ) .toReturnChanged(doc('foo/bar', 0, { sum: 1 }).setHasLocalMutations()) .toContain(doc('foo/bar', 0, { sum: 1 }).setHasLocalMutations()) @@ -1459,7 +1454,7 @@ function genericLocalStoreTests( return expectLocalStore() .afterAllocatingQuery(query1) .toReturnTargetId(2) - .after(patchMutation('foo/bar', { sum: FieldValue.increment(1) })) + .after(patchMutation('foo/bar', { sum: increment(1) })) .toReturnChanged(deletedDoc('foo/bar', 0)) .toNotContain('foo/bar') .afterRemoteEvent( @@ -1567,11 +1562,7 @@ function genericLocalStoreTests( .afterAllocatingQuery(query1) .toReturnTargetId(2) .after( - patchMutation( - 'foo/bar', - { sum: FieldValue.increment(1) }, - Precondition.none() - ) + patchMutation('foo/bar', { sum: increment(1) }, Precondition.none()) ) .toReturnChanged(doc('foo/bar', 0, { sum: 1 }).setHasLocalMutations()) .toContain(doc('foo/bar', 0, { sum: 1 }).setHasLocalMutations()) @@ -1594,7 +1585,7 @@ function genericLocalStoreTests( return expectLocalStore() .afterAllocatingQuery(query1) .toReturnTargetId(2) - .after(patchMutation('foo/bar', { sum: FieldValue.increment(1) })) + .after(patchMutation('foo/bar', { sum: increment(1) })) .toReturnChanged(deletedDoc('foo/bar', 0)) .toNotContain('foo/bar') .after(bundledDocuments([doc('foo/bar', 1, { sum: 1337 })])) diff --git a/packages/firestore/test/unit/model/mutation.test.ts b/packages/firestore/test/unit/model/mutation.test.ts index 5f5657405f0..e5765805e6f 100644 --- a/packages/firestore/test/unit/model/mutation.test.ts +++ b/packages/firestore/test/unit/model/mutation.test.ts @@ -17,8 +17,14 @@ import { expect } from 'chai'; -import { FieldValue } from '../../../compat/api/field_value'; -import { Timestamp } from '../../../src/api/timestamp'; +import { + arrayRemove, + arrayUnion, + increment, + Timestamp, + serverTimestamp, + deleteField +} from '../../../src'; import { MutableDocument } from '../../../src/model/document'; import { mutationApplyToLocalView, @@ -28,7 +34,7 @@ import { MutationResult, Precondition } from '../../../src/model/mutation'; -import { serverTimestamp } from '../../../src/model/server_timestamps'; +import { serverTimestamp as serverTimestampInternal } from '../../../src/model/server_timestamps'; import { ArrayRemoveTransformOperation, ArrayUnionTransformOperation @@ -125,7 +131,7 @@ describe('Mutation', () => { foo: { bar: 'bar-value', baz: 'baz-value' } }); const patch = patchMutation('collection/key', { - 'foo.bar': FieldValue.delete() + 'foo.bar': deleteField() }); mutationApplyToLocalView(patch, document, timestamp); @@ -166,7 +172,7 @@ describe('Mutation', () => { const document = doc('collection/key', 0, docData); const transform = patchMutation('collection/key', { - 'foo.bar': FieldValue.serverTimestamp() + 'foo.bar': serverTimestamp() }); mutationApplyToLocalView(transform, document, timestamp); @@ -176,7 +182,7 @@ describe('Mutation', () => { foo: { bar: '' }, baz: 'baz-value' }); - data.set(field('foo.bar'), serverTimestamp(timestamp, null)); + data.set(field('foo.bar'), serverTimestampInternal(timestamp, null)); const expectedDoc = doc('collection/key', 0, data).setHasLocalMutations(); expect(document).to.deep.equal(expectedDoc); @@ -187,8 +193,8 @@ describe('Mutation', () => { // test once we have integration tests. it('can create arrayUnion() transform.', () => { const transform = patchMutation('collection/key', { - foo: FieldValue.arrayUnion('tag'), - 'bar.baz': FieldValue.arrayUnion(true, { nested: { a: [1, 2] } }) + foo: arrayUnion('tag'), + 'bar.baz': arrayUnion(true, { nested: { a: [1, 2] } }) }); expect(transform.fieldTransforms).to.have.lengthOf(2); @@ -213,7 +219,7 @@ describe('Mutation', () => { // test once we have integration tests. it('can create arrayRemove() transform.', () => { const transform = patchMutation('collection/key', { - foo: FieldValue.arrayRemove('tag') + foo: arrayRemove('tag') }); expect(transform.fieldTransforms).to.have.lengthOf(1); @@ -226,28 +232,28 @@ describe('Mutation', () => { it('can apply local arrayUnion transform to missing field', () => { const baseDoc = {}; - const transform = { missing: FieldValue.arrayUnion(1, 2) }; + const transform = { missing: arrayUnion(1, 2) }; const expected = { missing: [1, 2] }; verifyTransform(baseDoc, transform, expected); }); it('can apply local arrayUnion transform to non-array field', () => { const baseDoc = { 'non-array': 42 }; - const transform = { 'non-array': FieldValue.arrayUnion(1, 2) }; + const transform = { 'non-array': arrayUnion(1, 2) }; const expected = { 'non-array': [1, 2] }; verifyTransform(baseDoc, transform, expected); }); it('can apply local arrayUnion transform with non-existing elements', () => { const baseDoc = { array: [1, 3] }; - const transform = { array: FieldValue.arrayUnion(2, 4) }; + const transform = { array: arrayUnion(2, 4) }; const expected = { array: [1, 3, 2, 4] }; verifyTransform(baseDoc, transform, expected); }); it('can apply local arrayUnion transform with existing elements', () => { const baseDoc = { array: [1, 3] }; - const transform = { array: FieldValue.arrayUnion(1, 3) }; + const transform = { array: arrayUnion(1, 3) }; const expected = { array: [1, 3] }; verifyTransform(baseDoc, transform, expected); }); @@ -255,7 +261,7 @@ describe('Mutation', () => { it('can apply local arrayUnion transform with duplicate existing elements', () => { // Duplicate entries in your existing array should be preserved. const baseDoc = { array: [1, 2, 2, 3] }; - const transform = { array: FieldValue.arrayUnion(2) }; + const transform = { array: arrayUnion(2) }; const expected = { array: [1, 2, 2, 3] }; verifyTransform(baseDoc, transform, expected); }); @@ -263,7 +269,7 @@ describe('Mutation', () => { it('can apply local arrayUnion transform with duplicate union elements', () => { // Duplicate entries in your union array should only be added once. const baseDoc = { array: [1, 3] }; - const transform = { array: FieldValue.arrayUnion(2, 2) }; + const transform = { array: arrayUnion(2, 2) }; const expected = { array: [1, 3, 2] }; verifyTransform(baseDoc, transform, expected); }); @@ -271,7 +277,7 @@ describe('Mutation', () => { it('can apply local arrayUnion transform with non-primitive elements', () => { // Union nested object values (one existing, one not). const baseDoc = { array: [1, { a: 'b' }] }; - const transform = { array: FieldValue.arrayUnion({ a: 'b' }, { c: 'd' }) }; + const transform = { array: arrayUnion({ a: 'b' }, { c: 'd' }) }; const expected = { array: [1, { a: 'b' }, { c: 'd' }] }; verifyTransform(baseDoc, transform, expected); }); @@ -279,35 +285,35 @@ describe('Mutation', () => { it('can apply local arrayUnion transform with partially-overlapping elements', () => { // Union objects that partially overlap an existing object. const baseDoc = { array: [1, { a: 'b', c: 'd' }] }; - const transform = { array: FieldValue.arrayUnion({ a: 'b' }, { c: 'd' }) }; + const transform = { array: arrayUnion({ a: 'b' }, { c: 'd' }) }; const expected = { array: [1, { a: 'b', c: 'd' }, { a: 'b' }, { c: 'd' }] }; verifyTransform(baseDoc, transform, expected); }); it('can apply local arrayRemove transform to missing field', () => { const baseDoc = {}; - const transform = { missing: FieldValue.arrayRemove(1, 2) }; + const transform = { missing: arrayRemove(1, 2) }; const expected = { missing: [] }; verifyTransform(baseDoc, transform, expected); }); it('can apply local arrayRemove transform to non-array field', () => { const baseDoc = { 'non-array': 42 }; - const transform = { 'non-array': FieldValue.arrayRemove(1, 2) }; + const transform = { 'non-array': arrayRemove(1, 2) }; const expected = { 'non-array': [] }; verifyTransform(baseDoc, transform, expected); }); it('can apply local arrayRemove transform with non-existing elements', () => { const baseDoc = { array: [1, 3] }; - const transform = { array: FieldValue.arrayRemove(2, 4) }; + const transform = { array: arrayRemove(2, 4) }; const expected = { array: [1, 3] }; verifyTransform(baseDoc, transform, expected); }); it('can apply local arrayRemove transform with existing elements', () => { const baseDoc = { array: [1, 2, 3, 4] }; - const transform = { array: FieldValue.arrayRemove(1, 3) }; + const transform = { array: arrayRemove(1, 3) }; const expected = { array: [2, 4] }; verifyTransform(baseDoc, transform, expected); }); @@ -316,7 +322,7 @@ describe('Mutation', () => { // Remove nested object values (one existing, one not). const baseDoc = { array: [1, { a: 'b' }] }; const transform = { - array: FieldValue.arrayRemove({ a: 'b' }, { c: 'd' }) + array: arrayRemove({ a: 'b' }, { c: 'd' }) }; const expected = { array: [1] }; verifyTransform(baseDoc, transform, expected); @@ -350,7 +356,7 @@ describe('Mutation', () => { const document = doc('collection/key', 0, docData); const transform = patchMutation('collection/key', { - 'foo.bar': FieldValue.serverTimestamp() + 'foo.bar': serverTimestamp() }); const mutationResult = new MutationResult(version(1), [ @@ -375,8 +381,8 @@ describe('Mutation', () => { const docData = { array1: [1, 2], array2: ['a', 'b'] }; const document = doc('collection/key', 0, docData); const transform = setMutation('collection/key', { - array1: FieldValue.arrayUnion(2, 3), - array2: FieldValue.arrayRemove('a', 'c') + array1: arrayUnion(2, 3), + array2: arrayRemove('a', 'c') }); // Server just sends null transform results for array operations. @@ -403,14 +409,14 @@ describe('Mutation', () => { doublePlusInfinity: 8.8 }; const transform = { - longPlusLong: FieldValue.increment(1), - longPlusDouble: FieldValue.increment(2.2), - doublePlusLong: FieldValue.increment(3), - doublePlusDouble: FieldValue.increment(4.4), - longPlusNan: FieldValue.increment(Number.NaN), - doublePlusNan: FieldValue.increment(Number.NaN), - longPlusInfinity: FieldValue.increment(Number.POSITIVE_INFINITY), - doublePlusInfinity: FieldValue.increment(Number.POSITIVE_INFINITY) + longPlusLong: increment(1), + longPlusDouble: increment(2.2), + doublePlusLong: increment(3), + doublePlusDouble: increment(4.4), + longPlusNan: increment(Number.NaN), + doublePlusNan: increment(Number.NaN), + longPlusInfinity: increment(Number.POSITIVE_INFINITY), + doublePlusInfinity: increment(Number.POSITIVE_INFINITY) }; const expected = { longPlusLong: 2, @@ -427,23 +433,23 @@ describe('Mutation', () => { it('can apply numeric add transform to unexpected type', () => { const baseDoc = { stringVal: 'zero' }; - const transform = { stringVal: FieldValue.increment(1) }; + const transform = { stringVal: increment(1) }; const expected = { stringVal: 1 }; verifyTransform(baseDoc, transform, expected); }); it('can apply numeric add transform to missing field', () => { const baseDoc = {}; - const transform = { missing: FieldValue.increment(1) }; + const transform = { missing: increment(1) }; const expected = { missing: 1 }; verifyTransform(baseDoc, transform, expected); }); it('can apply numeric add transforms consecutively', () => { const baseDoc = { numberVal: 1 }; - const transform1 = { numberVal: FieldValue.increment(2) }; - const transform2 = { numberVal: FieldValue.increment(3) }; - const transform3 = { numberVal: FieldValue.increment(4) }; + const transform1 = { numberVal: increment(2) }; + const transform2 = { numberVal: increment(3) }; + const transform3 = { numberVal: increment(4) }; const expected = { numberVal: 10 }; verifyTransform(baseDoc, [transform1, transform2, transform3], expected); }); @@ -456,7 +462,7 @@ describe('Mutation', () => { const docData = { sum: 1 }; const document = doc('collection/key', 0, docData); const transform = setMutation('collection/key', { - sum: FieldValue.increment(2) + sum: increment(2) }); const mutationResult = new MutationResult(version(1), [ @@ -566,8 +572,8 @@ describe('Mutation', () => { const baseDoc = doc('collection/key', 0, allValues); const allTransforms = { - time: FieldValue.serverTimestamp(), - nested: { time: FieldValue.serverTimestamp() } + time: serverTimestamp(), + nested: { time: serverTimestamp() } }; // Server timestamps are idempotent and don't have base values. @@ -587,17 +593,17 @@ describe('Mutation', () => { const baseDoc = doc('collection/key', 0, allValues); const allTransforms = { - double: FieldValue.increment(1), - long: FieldValue.increment(1), - text: FieldValue.increment(1), - map: FieldValue.increment(1), - missing: FieldValue.increment(1), + double: increment(1), + long: increment(1), + text: increment(1), + map: increment(1), + missing: increment(1), nested: { - double: FieldValue.increment(1), - long: FieldValue.increment(1), - text: FieldValue.increment(1), - map: FieldValue.increment(1), - missing: FieldValue.increment(1) + double: increment(1), + long: increment(1), + text: increment(1), + map: increment(1), + missing: increment(1) } }; const transform = patchMutation('collection/key', allTransforms); @@ -618,8 +624,8 @@ describe('Mutation', () => { it('increment twice', () => { const document = doc('collection/key', 0, { sum: 0 }); - const increment = { sum: FieldValue.increment(1) }; - const transform = setMutation('collection/key', increment); + const inc = { sum: increment(1) }; + const transform = setMutation('collection/key', inc); mutationApplyToLocalView(transform, document, Timestamp.now()); mutationApplyToLocalView(transform, document, Timestamp.now()); diff --git a/packages/firestore/test/unit/remote/serializer.helper.ts b/packages/firestore/test/unit/remote/serializer.helper.ts index bee01fbdaa6..fbb4338b53d 100644 --- a/packages/firestore/test/unit/remote/serializer.helper.ts +++ b/packages/firestore/test/unit/remote/serializer.helper.ts @@ -17,14 +17,18 @@ import { expect } from 'chai'; -import { Blob } from '../../../compat/api/blob'; import { + arrayRemove, + arrayUnion, + Bytes, DocumentReference, - UserDataWriter -} from '../../../compat/api/database'; -import { FieldValue } from '../../../compat/api/field_value'; -import { GeoPoint } from '../../../src/api/geo_point'; -import { Timestamp } from '../../../src/api/timestamp'; + GeoPoint, + increment, + refEqual, + serverTimestamp, + Timestamp +} from '../../../src'; +import { ExpUserDataWriter } from '../../../src/api/reference_impl'; import { DatabaseId } from '../../../src/core/database_info'; import { LimitType, @@ -121,7 +125,7 @@ import { wrapObject } from '../../util/helpers'; -const userDataWriter = new UserDataWriter(firestore()); +const userDataWriter = new ExpUserDataWriter(firestore()); const protobufJsonReader = testUserDataReader(/* useProto3Json= */ true); const protoJsReader = testUserDataReader(/* useProto3Json= */ false); @@ -189,7 +193,7 @@ export function serializerTest( actualReturnFieldValue instanceof DocumentReference && value instanceof DocumentReference ) { - expect(actualReturnFieldValue.isEqual(value)).to.be.true; + expect(refEqual(actualReturnFieldValue, value)).to.be.true; } else { expect(actualReturnFieldValue).to.deep.equal(value); } @@ -415,7 +419,7 @@ export function serializerTest( const bytes = new Uint8Array([0, 1, 2, 3, 4, 5]); verifyFieldValueRoundTrip({ - value: Blob.fromUint8Array(bytes), + value: Bytes.fromUint8Array(bytes), valueType: 'bytesValue', jsonValue: 'AAECAwQF', protoJsValue: bytes @@ -666,8 +670,8 @@ export function serializerTest( it('ServerTimestamp transform', () => { const mutation = setMutation('baz/quux', { - a: FieldValue.serverTimestamp(), - 'bar': FieldValue.serverTimestamp() + a: serverTimestamp(), + 'bar': serverTimestamp() }); const proto = { update: toMutationDocument(s, mutation.key, mutation.value), @@ -679,7 +683,7 @@ export function serializerTest( verifyMutation(mutation, proto); const mutation2 = setMutation('baz/quux', { - a: FieldValue.serverTimestamp() + a: serverTimestamp() }); const proto2 = { update: toMutationDocument(s, mutation2.key, mutation2.value), @@ -692,8 +696,8 @@ export function serializerTest( it('Numeric Add transform', () => { const mutation = setMutation('baz/quux', { - integer: FieldValue.increment(42), - double: FieldValue.increment(13.37) + integer: increment(42), + double: increment(13.37) }); const proto = { update: toMutationDocument(s, mutation.key, mutation.value), @@ -705,8 +709,8 @@ export function serializerTest( verifyMutation(mutation, proto); const mutation2 = setMutation('baz/quux', { - integer: FieldValue.increment(42), - double: FieldValue.increment(13.37) + integer: increment(42), + double: increment(13.37) }); const proto2 = { update: toMutationDocument(s, mutation2.key, mutation2.value), @@ -720,8 +724,8 @@ export function serializerTest( it('Array transforms', () => { const mutation = patchMutation('docs/1', { - a: FieldValue.arrayUnion('a', 2), - 'bar.baz': FieldValue.arrayRemove({ x: 1 }) + a: arrayUnion('a', 2), + 'bar.baz': arrayRemove({ x: 1 }) }); const proto = { update: toMutationDocument(s, mutation.key, mutation.data), @@ -743,8 +747,8 @@ export function serializerTest( verifyMutation(mutation, proto); const mutation2 = setMutation('docs/1', { - a: FieldValue.arrayUnion('a', 2), - bar: FieldValue.arrayRemove({ x: 1 }) + a: arrayUnion('a', 2), + bar: arrayRemove({ x: 1 }) }); const proto2 = { update: toMutationDocument(s, mutation2.key, mutation2.value), diff --git a/packages/firestore/test/unit/specs/spec_builder.ts b/packages/firestore/test/unit/specs/spec_builder.ts index 1f36c1a9cf7..838b3263171 100644 --- a/packages/firestore/test/unit/specs/spec_builder.ts +++ b/packages/firestore/test/unit/specs/spec_builder.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { UserDataWriter } from '../../../compat/api/database'; +import { ExpUserDataWriter } from '../../../src/api/reference_impl'; import { hasLimitToFirst, hasLimitToLast, @@ -67,7 +67,7 @@ import { SpecWriteFailure } from './spec_test_runner'; -const userDataWriter = new UserDataWriter(firestore()); +const userDataWriter = new ExpUserDataWriter(firestore()); // These types are used in a protected API by SpecBuilder and need to be // exported. diff --git a/packages/firestore/test/util/api_helpers.ts b/packages/firestore/test/util/api_helpers.ts index 374c58abeb2..81d01132087 100644 --- a/packages/firestore/test/util/api_helpers.ts +++ b/packages/firestore/test/util/api_helpers.ts @@ -19,33 +19,20 @@ // these in any integration test, where we expect working Firestore object. import { - CollectionReference, DocumentReference, - DocumentSnapshot, + ensureFirestoreConfigured, Firestore, - IndexedDbPersistenceProvider, Query, + CollectionReference, QuerySnapshot, - UserDataWriter -} from '../../compat/api/database'; + DocumentSnapshot, + SnapshotMetadata +} from '../../src'; import { EmptyAppCheckTokenProvider, EmptyAuthCredentialsProvider } from '../../src/api/credentials'; -import { - ensureFirestoreConfigured, - Firestore as ExpFirestore -} from '../../src/api/database'; -import { - Query as ExpQuery, - CollectionReference as ExpCollectionReference -} from '../../src/api/reference'; import { ExpUserDataWriter } from '../../src/api/reference_impl'; -import { - QuerySnapshot as ExpQuerySnapshot, - DocumentSnapshot as ExpDocumentSnapshot, - SnapshotMetadata -} from '../../src/api/snapshot'; import { DatabaseId } from '../../src/core/database_info'; import { newQueryForPath, Query as InternalQuery } from '../../src/core/query'; import { @@ -72,32 +59,21 @@ export function firestore(): Firestore { export function newTestFirestore(projectId = 'new-project'): Firestore { return new Firestore( new DatabaseId(projectId), - new ExpFirestore( - new DatabaseId(projectId), - new EmptyAuthCredentialsProvider(), - new EmptyAppCheckTokenProvider() - ), - new IndexedDbPersistenceProvider() + new EmptyAuthCredentialsProvider(), + new EmptyAppCheckTokenProvider() ); } export function collectionReference(path: string): CollectionReference { const db = firestore(); - ensureFirestoreConfigured(db._delegate); - return new CollectionReference( - db, - new ExpCollectionReference( - db._delegate, - /* converter= */ null, - pathFrom(path) - ) - ); + ensureFirestoreConfigured(db); + return new CollectionReference(db, /* converter= */ null, pathFrom(path)); } export function documentReference(path: string): DocumentReference { const db = firestore(); - ensureFirestoreConfigured(db._delegate); - return DocumentReference.forKey(key(path), db, /* converter= */ null); + ensureFirestoreConfigured(db); + return new DocumentReference(db, /* converter= */ null, key(path)); } export function documentSnapshot( @@ -106,44 +82,31 @@ export function documentSnapshot( fromCache: boolean ): DocumentSnapshot { const db = firestore(); - const userDataWriter = new UserDataWriter(db); + const userDataWriter = new ExpUserDataWriter(db); if (data) { return new DocumentSnapshot( - firestore(), - new ExpDocumentSnapshot( - db._delegate, - userDataWriter, - key(path), - doc(path, 1, data), - new SnapshotMetadata(/* hasPendingWrites= */ false, fromCache), - /* converter= */ null - ) + db, + userDataWriter, + key(path), + doc(path, 1, data), + new SnapshotMetadata(/* hasPendingWrites= */ false, fromCache), + /* converter= */ null ); } else { return new DocumentSnapshot( - firestore(), - new ExpDocumentSnapshot( - db._delegate, - userDataWriter, - key(path), - null, - new SnapshotMetadata(/* hasPendingWrites= */ false, fromCache), - /* converter= */ null - ) + db, + userDataWriter, + key(path), + null, + new SnapshotMetadata(/* hasPendingWrites= */ false, fromCache), + /* converter= */ null ); } } export function query(path: string): Query { const db = firestore(); - return new Query( - db, - new ExpQuery( - db._delegate, - /* converter= */ null, - newQueryForPath(pathFrom(path)) - ) - ); + return new Query(db, /* converter= */ null, newQueryForPath(pathFrom(path))); } /** @@ -194,11 +157,8 @@ export function querySnapshot( const db = firestore(); return new QuerySnapshot( db, - new ExpQuerySnapshot( - db._delegate, - new ExpUserDataWriter(db._delegate), - new ExpQuery(db._delegate, /* converter= */ null, query), - viewSnapshot - ) + new ExpUserDataWriter(db), + new Query(db, /* converter= */ null, query), + viewSnapshot ); } diff --git a/packages/firestore/test/util/helpers.ts b/packages/firestore/test/util/helpers.ts index a1ab260288e..8cbd13426e2 100644 --- a/packages/firestore/test/util/helpers.ts +++ b/packages/firestore/test/util/helpers.ts @@ -15,12 +15,14 @@ * limitations under the License. */ -import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; -import { Blob } from '../../compat/api/blob'; -import { DocumentReference } from '../../compat/api/database'; -import { Timestamp } from '../../src/api/timestamp'; +import { + Bytes, + DocumentReference, + OrderByDirection, + Timestamp +} from '../../src'; import { BundledDocuments } from '../../src/core/bundle'; import { DatabaseId } from '../../src/core/database_info'; import { @@ -137,10 +139,10 @@ export function version(v: TestSnapshotVersion): SnapshotVersion { } export function ref(key: string, offset?: number): DocumentReference { - return DocumentReference.forPath( - path(key, offset), + return new DocumentReference( FIRESTORE, - /* converter= */ null + /* converter= */ null, + new DocumentKey(path(key, offset)) ); } @@ -219,9 +221,9 @@ export function mask(...paths: string[]): FieldMask { return new FieldMask(paths.map(v => field(v))); } -export function blob(...bytes: number[]): Blob { +export function blob(...bytes: number[]): Bytes { // bytes can be undefined for the empty blob - return Blob.fromUint8Array(new Uint8Array(bytes || [])); + return Bytes.fromUint8Array(new Uint8Array(bytes || [])); } export function filter(path: string, op: string, value: unknown): FieldFilter { @@ -291,7 +293,7 @@ export function mutationResult( } export function bound( - values: Array<[string, {}, firestore.OrderByDirection]>, + values: Array<[string, {}, OrderByDirection]>, before: boolean ): Bound { const components: api.Value[] = [];