diff --git a/packages/firestore/test/integration/api/array_transforms.test.ts b/packages/firestore/test/integration/api/array_transforms.test.ts index ac5d0cc448b..e1a628435e8 100644 --- a/packages/firestore/test/integration/api/array_transforms.test.ts +++ b/packages/firestore/test/integration/api/array_transforms.test.ts @@ -160,80 +160,84 @@ apiDescribe('Array Transforms:', persistence => { * Unlike the withTestSetup() tests above, these tests intentionally avoid * having any ongoing listeners so that we can test what gets stored in the * offline cache based purely on the write acknowledgement (without receiving - * an updated document via watch). As such they also rely on persistence - * being enabled so documents remain in the cache after the write. + * an updated document via watch). As such they also rely on persistence with + * LRU garbage collection (rather than eager garbage collection) so documents + * remain in the cache after the write. */ // eslint-disable-next-line no-restricted-properties - (persistence ? describe : describe.skip)('Server Application: ', () => { - it('set() with no cached base doc', async () => { - await withTestDoc(persistence, async docRef => { - await setDoc(docRef, { array: arrayUnion(1, 2) }); - const snapshot = await getDocFromCache(docRef); - expect(snapshot.data()).to.deep.equal({ array: [1, 2] }); - }); - }); - - it('update() with no cached base doc', async () => { - let path: string | null = null; - // Write an initial document in an isolated Firestore instance so it's not - // stored in our cache - await withTestDoc(persistence, async docRef => { - path = docRef.path; - await setDoc(docRef, { array: [42] }); + (persistence.gc === 'lru' ? describe : describe.skip)( + 'Server Application: ', + () => { + it('set() with no cached base doc', async () => { + await withTestDoc(persistence, async docRef => { + await setDoc(docRef, { array: arrayUnion(1, 2) }); + const snapshot = await getDocFromCache(docRef); + expect(snapshot.data()).to.deep.equal({ array: [1, 2] }); + }); }); - await withTestDb(persistence, async db => { - 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 getDocFromCache(docRef); - } catch (err) { - expect((err as FirestoreError).code).to.equal('unavailable'); - errCaught = true; - } - expect(errCaught).to.be.true; + it('update() with no cached base doc', async () => { + let path: string | null = null; + // Write an initial document in an isolated Firestore instance so it's not + // stored in our cache + await withTestDoc(persistence, async docRef => { + path = docRef.path; + await setDoc(docRef, { array: [42] }); + }); + + await withTestDb(persistence, async db => { + 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 getDocFromCache(docRef); + } catch (err) { + expect((err as FirestoreError).code).to.equal('unavailable'); + errCaught = true; + } + expect(errCaught).to.be.true; + }); }); - }); - it('set(..., {merge}) with no cached based doc', async () => { - let path: string | null = null; - // Write an initial document in an isolated Firestore instance so it's not - // stored in our cache - await withTestDoc(persistence, async docRef => { - path = docRef.path; - await setDoc(docRef, { array: [42] }); + it('set(..., {merge}) with no cached based doc', async () => { + let path: string | null = null; + // Write an initial document in an isolated Firestore instance so it's not + // stored in our cache + await withTestDoc(persistence, async docRef => { + path = docRef.path; + await setDoc(docRef, { array: [42] }); + }); + + await withTestDb(persistence, async db => { + 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 getDocFromCache(docRef); + expect(snapshot.data()).to.deep.equal({ array: [1, 2] }); + }); }); - await withTestDb(persistence, async db => { - 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 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 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 arrayUnion()', async () => { - await withTestDoc(persistence, async docRef => { - 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 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] }); + }); }); - }); - - it('update() with cached base doc using arrayRemove()', async () => { - await withTestDoc(persistence, async docRef => { - 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/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 498b9355113..76194e3ea6b 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -67,15 +67,13 @@ import { } from '../util/firebase_export'; import { apiDescribe, - withEnsuredLruGcTestDb, withTestCollection, withTestDbsSettings, withTestDb, withTestDbs, withTestDoc, withTestDocAndInitialData, - withNamedTestDbsOrSkipUnlessUsingEmulator, - withEnsuredEagerGcTestDb + withNamedTestDbsOrSkipUnlessUsingEmulator } from '../util/helpers'; import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; @@ -153,29 +151,32 @@ apiDescribe('Database', persistence => { }); // 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 = 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 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' }) - ); - }); - }); + (persistence.gc === 'lru' ? it : it.skip)( + 'can update an unknown document', + () => { + return withTestDbs(persistence, 2, async ([reader, writer]) => { + 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 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' }) + ); + }); + } + ); it('can merge data with an existing document using set', () => { return withTestDoc(persistence, doc => { @@ -1095,32 +1096,35 @@ apiDescribe('Database', persistence => { }); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)('offline writes are sent after restart', () => { - return withTestDoc(persistence, async (docRef, firestore) => { - const app = firestore.app; - const name = app.name; - const options = app.options; + (persistence.storage === 'indexeddb' ? it : it.skip)( + 'offline writes are sent after restart', + () => { + return withTestDoc(persistence, async (docRef, firestore) => { + const app = firestore.app; + const name = app.name; + const options = app.options; - await disableNetwork(firestore); + await disableNetwork(firestore); - // We are merely adding to the cache. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - setDoc(docRef, { foo: 'bar' }); + // We are merely adding to the cache. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + setDoc(docRef, { foo: 'bar' }); - await deleteApp(app); + await deleteApp(app); - const firestore2 = newTestFirestore( - newTestApp(options.projectId!, name), - DEFAULT_SETTINGS - ); - await enableIndexedDbPersistence(firestore2); - await waitForPendingWrites(firestore2); - const doc2 = await getDoc(doc(firestore2, docRef.path)); + const firestore2 = newTestFirestore( + newTestApp(options.projectId!, name), + DEFAULT_SETTINGS + ); + await enableIndexedDbPersistence(firestore2); + await waitForPendingWrites(firestore2); + const doc2 = await getDoc(doc(firestore2, docRef.path)); - expect(doc2.exists()).to.be.true; - expect(doc2.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 => { @@ -1141,7 +1145,7 @@ apiDescribe('Database', persistence => { }); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'maintains persistence after restarting app', async () => { await withTestDoc(persistence, async docRef => { @@ -1164,7 +1168,7 @@ apiDescribe('Database', persistence => { ); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'can clear persistence if the client has been terminated', async () => { await withTestDoc(persistence, async (docRef, firestore) => { @@ -1188,7 +1192,7 @@ apiDescribe('Database', persistence => { ); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'can clear persistence if the client has not been initialized', async () => { await withTestDoc(persistence, async docRef => { @@ -1212,7 +1216,7 @@ apiDescribe('Database', persistence => { ); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'cannot clear persistence if the client has been initialized', async () => { await withTestDoc(persistence, async (docRef, firestore) => { @@ -1757,7 +1761,7 @@ apiDescribe('Database', persistence => { it('Cannot get document from cache with eager GC enabled.', () => { const initialData = { key: 'value' }; - return withEnsuredEagerGcTestDb(async db => { + return withTestDb(persistence.toEagerGc(), async db => { const docRef = doc(collection(db, 'test-collection')); await setDoc(docRef, initialData); await expect(getDocFromCache(docRef)).to.be.rejectedWith('Failed to get'); @@ -1766,7 +1770,7 @@ apiDescribe('Database', persistence => { it('Can get document from cache with Lru GC enabled.', () => { const initialData = { key: 'value' }; - return withEnsuredLruGcTestDb(persistence, async db => { + return withTestDb(persistence.toLruGc(), async db => { const docRef = doc(collection(db, 'test-collection')); await setDoc(docRef, initialData); return getDocFromCache(docRef).then(doc => { diff --git a/packages/firestore/test/integration/api/get_options.test.ts b/packages/firestore/test/integration/api/get_options.test.ts index aec3cb0257b..9eec596861f 100644 --- a/packages/firestore/test/integration/api/get_options.test.ts +++ b/packages/firestore/test/integration/api/get_options.test.ts @@ -36,7 +36,7 @@ import { apiDescribe, withTestCollection, withTestDocAndInitialData, - withEnsuredLruGcTestDb + withTestDb } from '../util/helpers'; apiDescribe('GetOptions', persistence => { @@ -70,8 +70,8 @@ apiDescribe('GetOptions', persistence => { it('get document while offline with default get options', () => { const initialData = { key: 'value' }; - // Use an instance with Gc turned on. - return withEnsuredLruGcTestDb(persistence, async db => { + // Use an instance with LRU GC. + return withTestDb(persistence.toLruGc(), async db => { const docRef = doc(collection(db, 'test-collection')); await setDoc(docRef, initialData); return getDoc(docRef) @@ -496,9 +496,10 @@ apiDescribe('GetOptions', persistence => { }); }); - // We need the deleted doc to stay in cache, so only run this with persistence. + // We need the deleted doc to stay in cache, so only run this test when the + // local cache is configured with LRU GC (as opposed to eager GC). // eslint-disable-next-line no-restricted-properties, - (persistence ? it : it.skip)( + (persistence.gc === 'lru' ? it : it.skip)( 'get deleted doc while offline with source=cache', () => { return withTestDocAndInitialData(persistence, null, (docRef, db) => { diff --git a/packages/firestore/test/integration/api/index_configuration.test.ts b/packages/firestore/test/integration/api/index_configuration.test.ts index 764283ec7ec..1cab94d202e 100644 --- a/packages/firestore/test/integration/api/index_configuration.test.ts +++ b/packages/firestore/test/integration/api/index_configuration.test.ts @@ -80,7 +80,7 @@ apiDescribe('Index Configuration:', persistence => { it('bad JSON does not crash client', () => { return withTestDb(persistence, async db => { const action = (): Promise => setIndexConfiguration(db, '{,}'); - if (persistence) { + if (persistence.storage === 'indexeddb') { expect(action).to.throw(/Failed to parse JSON/); } else { // Silently do nothing. Parsing is not done and therefore no error is thrown. diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index feadaae467c..7514fa836f9 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1333,10 +1333,11 @@ apiDescribe('Queries', persistence => { }); }); - // OR Query tests only run when the SDK is configured for persistence - // because they validate that the result from server and cache match. + // OR Query tests only run when the SDK's local cache is configured to use + // LRU garbage collection (rather than eager garbage collection) because + // they validate that the result from server and cache match. // eslint-disable-next-line no-restricted-properties - (persistence ? describe : describe.skip)('OR Queries', () => { + (persistence.gc === 'lru' ? describe : describe.skip)('OR Queries', () => { it('can use query overloads', () => { const testDocs = { doc1: { a: 1, b: 0 }, @@ -1646,390 +1647,398 @@ apiDescribe('Queries', persistence => { }); }); - // OR Query tests only run when the SDK is configured for persistence - // because they validate that the result from server and cache match - // Additionally these tests must be skipped if running against production - // because it results in a 'missing index' error. The Firestore Emulator, - // however, does serve these queries. + // OR Query tests only run when the SDK's local cache is configured to use + // LRU garbage collection (rather than eager garbage collection) because + // they validate that the result from server and cache match. Additionally, + // these tests must be skipped if running against production because it + // results in a 'missing index' error. The Firestore Emulator, however, does + // serve these queries. // eslint-disable-next-line no-restricted-properties - (persistence && USE_EMULATOR ? describe : describe.skip)('OR Queries', () => { - it('can use query overloads', () => { - const testDocs = { - doc1: { a: 1, b: 0 }, - doc2: { a: 2, b: 1 }, - doc3: { a: 3, b: 2 }, - doc4: { a: 1, b: 3 }, - doc5: { a: 1, b: 1 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - // a == 1, limit 2, b - desc - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', '==', 1), limit(2), orderBy('b', 'desc')), - 'doc4', - 'doc5' - ); + (persistence.gc === 'lru' && USE_EMULATOR ? describe : describe.skip)( + 'OR Queries', + () => { + it('can use query overloads', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { a: 2, b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1, b: 1 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // a == 1, limit 2, b - desc + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', '==', 1), limit(2), orderBy('b', 'desc')), + 'doc4', + 'doc5' + ); + }); }); - }); - - it('can use or queries', () => { - const testDocs = { - doc1: { a: 1, b: 0 }, - doc2: { a: 2, b: 1 }, - doc3: { a: 3, b: 2 }, - doc4: { a: 1, b: 3 }, - doc5: { a: 1, b: 1 } - }; - return withTestCollection(persistence, testDocs, async coll => { - // with one inequality: a>2 || b==1. - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', '>', 2), where('b', '==', 1))), - 'doc5', - 'doc2', - 'doc3' - ); + it('can use or queries', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { a: 2, b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1, b: 1 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // with one inequality: a>2 || b==1. + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', '>', 2), where('b', '==', 1))), + 'doc5', + 'doc2', + 'doc3' + ); - // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', '==', 1), where('b', '>', 0)), limit(2)), - 'doc1', - 'doc2' - ); + // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', '==', 1), where('b', '>', 0)), limit(2)), + 'doc1', + 'doc2' + ); - // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 - // Note: The public query API does not allow implicit ordering when limitToLast is used. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', '==', 1), where('b', '>', 0)), - limitToLast(2), - orderBy('b') - ), - 'doc3', - 'doc4' - ); + // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 + // Note: The public query API does not allow implicit ordering when limitToLast is used. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', '==', 1), where('b', '>', 0)), + limitToLast(2), + orderBy('b') + ), + 'doc3', + 'doc4' + ); - // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', '==', 2), where('b', '==', 1)), - limit(1), - orderBy('a') - ), - 'doc5' - ); + // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', '==', 2), where('b', '==', 1)), + limit(1), + orderBy('a') + ), + 'doc5' + ); - // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1 - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', '==', 2), where('b', '==', 1)), - limitToLast(1), - orderBy('a') - ), - 'doc2' - ); + // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1 + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', '==', 2), where('b', '==', 1)), + limitToLast(1), + orderBy('a') + ), + 'doc2' + ); + }); }); - }); - it('can use or queries with not-in', () => { - const testDocs = { - doc1: { a: 1, b: 0 }, - doc2: { b: 1 }, - doc3: { a: 3, b: 2 }, - doc4: { a: 1, b: 3 }, - doc5: { a: 1 }, - doc6: { a: 2 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - // a==2 || b not-in [2,3] - // Has implicit orderBy b. - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', '==', 2), where('b', 'not-in', [2, 3]))), - 'doc1', - 'doc2' - ); + it('can use or queries with not-in', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // a==2 || b not-in [2,3] + // Has implicit orderBy b. + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', '==', 2), where('b', 'not-in', [2, 3]))), + 'doc1', + 'doc2' + ); + }); }); - }); - - // eslint-disable-next-line no-restricted-properties - it('supports order by equality', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7], c: 10 }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2, c: 20 } - }; - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', '==', 1), orderBy('a')), - 'doc1', - 'doc4', - 'doc5' - ); + // eslint-disable-next-line no-restricted-properties + it('supports order by equality', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', '==', 1), orderBy('a')), + 'doc1', + 'doc4', + 'doc5' + ); - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', 'in', [2, 3]), orderBy('a')), - 'doc6', - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', 'in', [2, 3]), orderBy('a')), + 'doc6', + 'doc3' + ); + }); }); - }); - - // eslint-disable-next-line no-restricted-properties - it('supports multiple in ops', () => { - const testDocs = { - doc1: { a: 1, b: 0 }, - doc2: { b: 1 }, - doc3: { a: 3, b: 2 }, - doc4: { a: 1, b: 3 }, - doc5: { a: 1 }, - doc6: { a: 2 } - }; - return withTestCollection(persistence, testDocs, async coll => { - // Two IN operations on different fields with disjunction. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), - orderBy('a') - ), - 'doc1', - 'doc6', - 'doc3' - ); + // eslint-disable-next-line no-restricted-properties + it('supports multiple in ops', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // Two IN operations on different fields with disjunction. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), + orderBy('a') + ), + 'doc1', + 'doc6', + 'doc3' + ); - // Two IN operations on different fields with conjunction. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), - orderBy('a') - ), - 'doc3' - ); + // Two IN operations on different fields with conjunction. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), + orderBy('a') + ), + 'doc3' + ); - // Two IN operations on the same field. - // a IN [1,2,3] && a IN [0,1,4] should result in "a==1". - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [1, 2, 3]), where('a', 'in', [0, 1, 4])) - ), - 'doc1', - 'doc4', - 'doc5' - ); + // Two IN operations on the same field. + // a IN [1,2,3] && a IN [0,1,4] should result in "a==1". + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [1, 2, 3]), where('a', 'in', [0, 1, 4])) + ), + 'doc1', + 'doc4', + 'doc5' + ); - // a IN [2,3] && a IN [0,1,4] is never true and so the result should be an - // empty set. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('a', 'in', [0, 1, 4])) - ) - ); + // a IN [2,3] && a IN [0,1,4] is never true and so the result should be an + // empty set. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('a', 'in', [0, 1, 4])) + ) + ); - // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]). - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', 'in', [0, 3]), where('a', 'in', [0, 2]))), - 'doc3', - 'doc6' - ); + // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]). + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', 'in', [0, 3]), where('a', 'in', [0, 2]))), + 'doc3', + 'doc6' + ); - // Nested composite filter on the same field. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [1, 3]), - or( - where('a', 'in', [0, 2]), - and(where('b', '>=', 1), where('a', 'in', [1, 3])) + // Nested composite filter on the same field. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [1, 3]), + or( + where('a', 'in', [0, 2]), + and(where('b', '>=', 1), where('a', 'in', [1, 3])) + ) ) - ) - ), - 'doc3', - 'doc4' - ); + ), + 'doc3', + 'doc4' + ); - // Nested composite filter on the different fields. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('b', 'in', [0, 3]), - or( - where('b', 'in', [1]), - and(where('b', 'in', [2, 3]), where('a', 'in', [1, 3])) + // Nested composite filter on the different fields. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('b', 'in', [0, 3]), + or( + where('b', 'in', [1]), + and(where('b', 'in', [2, 3]), where('a', 'in', [1, 3])) + ) ) - ) - ), - 'doc4' - ); + ), + 'doc4' + ); + }); }); - }); - // eslint-disable-next-line no-restricted-properties - it('supports using in with array contains any', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7], c: 10 }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2, c: 20 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or( - where('a', 'in', [2, 3]), - where('b', 'array-contains-any', [0, 7]) - ) - ), - 'doc1', - 'doc3', - 'doc4', - 'doc6' - ); + // eslint-disable-next-line no-restricted-properties + it('supports using in with array contains any', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + where('a', 'in', [2, 3]), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc1', + 'doc3', + 'doc4', + 'doc6' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - where('b', 'array-contains-any', [0, 7]) - ) - ), - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc3' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or( - and(where('a', 'in', [2, 3]), where('c', '==', 10)), - where('b', 'array-contains-any', [0, 7]) - ) - ), - 'doc1', - 'doc3', - 'doc4' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + and(where('a', 'in', [2, 3]), where('c', '==', 10)), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc1', + 'doc3', + 'doc4' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - or(where('b', 'array-contains-any', [0, 7]), where('c', '==', 20)) - ) - ), - 'doc3', - 'doc6' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + or( + where('b', 'array-contains-any', [0, 7]), + where('c', '==', 20) + ) + ) + ), + 'doc3', + 'doc6' + ); + }); }); - }); - // eslint-disable-next-line no-restricted-properties - it('supports using in with array contains', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7] }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', 'in', [2, 3]), where('b', 'array-contains', 3)) - ), - 'doc3', - 'doc4', - 'doc6' - ); + // eslint-disable-next-line no-restricted-properties + it('supports using in with array contains', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7] }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', 'in', [2, 3]), where('b', 'array-contains', 3)) + ), + 'doc3', + 'doc4', + 'doc6' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('b', 'array-contains', 7)) - ), - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('b', 'array-contains', 7)) + ), + 'doc3' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or( - where('a', 'in', [2, 3]), - and(where('b', 'array-contains', 3), where('a', '==', 1)) - ) - ), - 'doc3', - 'doc4', - 'doc6' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + where('a', 'in', [2, 3]), + and(where('b', 'array-contains', 3), where('a', '==', 1)) + ) + ), + 'doc3', + 'doc4', + 'doc6' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - or(where('b', 'array-contains', 7), where('a', '==', 1)) - ) - ), - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + or(where('b', 'array-contains', 7), where('a', '==', 1)) + ) + ), + 'doc3' + ); + }); }); - }); - // eslint-disable-next-line no-restricted-properties - it('supports order by equality', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7], c: 10 }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2, c: 20 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', '==', 1), orderBy('a')), - 'doc1', - 'doc4', - 'doc5' - ); + // eslint-disable-next-line no-restricted-properties + it('supports order by equality', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', '==', 1), orderBy('a')), + 'doc1', + 'doc4', + 'doc5' + ); - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', 'in', [2, 3]), orderBy('a')), - 'doc6', - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', 'in', [2, 3]), orderBy('a')), + 'doc6', + 'doc3' + ); + }); }); - }); - }); + } + ); // Reproduces https://github.com/firebase/firebase-js-sdk/issues/5873 - // eslint-disable-next-line no-restricted-properties - (persistence ? describe : describe.skip)('Caching empty results', () => { + describe('Caching empty results', () => { it('can raise initial snapshot from cache, even if it is empty', () => { - return withTestCollection(persistence, {}, async coll => { + // Use persistence with LRU garbage collection so the resume token and + // document data do not get prematurely deleted from the local cache. + return withTestCollection(persistence.toLruGc(), {}, async coll => { const snapshot1 = await getDocs(coll); // Populate the cache. expect(snapshot1.metadata.fromCache).to.be.false; expect(toDataArray(snapshot1)).to.deep.equal([]); // Precondition check. @@ -2047,7 +2056,9 @@ apiDescribe('Queries', persistence => { const testDocs = { a: { key: 'a' } }; - return withTestCollection(persistence, testDocs, async coll => { + // Use persistence with LRU garbage collection so the resume token and + // document data do not get prematurely deleted from the local cache. + return withTestCollection(persistence.toLruGc(), testDocs, async coll => { // Populate the cache. const snapshot1 = await getDocs(coll); expect(snapshot1.metadata.fromCache).to.be.false; @@ -2128,13 +2139,13 @@ apiDescribe('Queries', persistence => { ); } - // Skip the verification of the existence filter mismatch when persistence - // is disabled because without persistence there is no resume token - // specified in the subsequent call to getDocs(), and, therefore, Watch - // will _not_ send an existence filter. + // Skip the verification of the existence filter mismatch when the local + // cache is configured to use eager garbage collection because with eager + // GC there is no resume token specified in the subsequent call to + // getDocs(), and, therefore, Watch will _not_ send an existence filter. // TODO(b/272754156) Re-write this test using a snapshot listener instead // of calls to getDocs() and remove this check for disabled persistence. - if (!persistence) { + if (persistence.gc === 'eager') { return 'passed'; } diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index 43944ee66ba..1aaf70e9452 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -56,6 +56,7 @@ import { } from '../util/firebase_export'; import { apiDescribe, + PersistenceMode, withAlternateTestDb, withTestCollection, withTestDb @@ -67,17 +68,17 @@ import { ALT_PROJECT_ID, DEFAULT_PROJECT_ID } from '../util/settings'; interface ValidationIt { ( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ): void; skip: ( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ) => void; only: ( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ) => void; @@ -87,7 +88,7 @@ interface ValidationIt { // we have a helper wrapper around it() and withTestDb() to optimize for that. const validationIt: ValidationIt = Object.assign( ( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ) => { @@ -102,7 +103,7 @@ const validationIt: ValidationIt = Object.assign( }, { skip( - persistence: boolean, + persistence: PersistenceMode, message: string, _: (db: Firestore) => void | Promise ): void { @@ -110,7 +111,7 @@ const validationIt: ValidationIt = Object.assign( it.skip(message, () => {}); }, only( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ): void { @@ -134,14 +135,6 @@ class TestClass { apiDescribe('Validation:', persistence => { describe('FirestoreSettings', () => { - // Enabling persistence counts as a use of the firestore instance, meaning - // that it will be impossible to verify that a set of settings don't throw, - // and additionally that some exceptions happen for specific reasons, rather - // than persistence having already been enabled. - if (persistence) { - return; - } - validationIt( persistence, 'disallows changing settings after use', @@ -170,15 +163,19 @@ apiDescribe('Validation:', persistence => { }); }); - validationIt(persistence, 'useEmulator can set host and port', () => { - const db = newTestFirestore(newTestApp('test-project')); - // Verify that this doesn't throw. - connectFirestoreEmulator(db, 'localhost', 9000); - }); + validationIt( + persistence, + 'connectFirestoreEmulator() can set host and port', + () => { + const db = newTestFirestore(newTestApp('test-project')); + // Verify that this doesn't throw. + connectFirestoreEmulator(db, 'localhost', 9000); + } + ); validationIt( persistence, - 'disallows calling useEmulator after use', + 'disallows calling connectFirestoreEmulator() after use', async db => { const errorMsg = 'Firestore has already been started and its settings can no longer be changed.'; @@ -192,7 +189,7 @@ apiDescribe('Validation:', persistence => { validationIt( persistence, - 'useEmulator can set mockUserToken object', + 'connectFirestoreEmulator() can set mockUserToken object', () => { const db = newTestFirestore(newTestApp('test-project')); // Verify that this doesn't throw. @@ -204,7 +201,7 @@ apiDescribe('Validation:', persistence => { validationIt( persistence, - 'useEmulator can set mockUserToken string', + 'connectFirestoreEmulator() can set mockUserToken string', () => { const db = newTestFirestore(newTestApp('test-project')); // Verify that this doesn't throw. @@ -230,15 +227,11 @@ apiDescribe('Validation:', persistence => { }); describe('Firestore', () => { - (persistence ? validationIt : validationIt.skip)( + validationIt( persistence, - 'disallows calling enablePersistence after use', + 'disallows calling enableIndexedDbPersistence() after use', db => { - // Calling `enablePersistence()` itself counts as use, so we should only - // need this method when persistence is not enabled. - if (!persistence) { - doc(db, 'foo/bar'); - } + //doc(db, 'foo/bar'); expect(() => enableIndexedDbPersistence(db)).to.throw( 'SDK cache is already specified.' ); diff --git a/packages/firestore/test/integration/api_internal/database.test.ts b/packages/firestore/test/integration/api_internal/database.test.ts index 95a756cf996..93ccbbd2390 100644 --- a/packages/firestore/test/integration/api_internal/database.test.ts +++ b/packages/firestore/test/integration/api_internal/database.test.ts @@ -35,7 +35,7 @@ use(chaiAsPromised); apiDescribe('Database (with internal API)', persistence => { // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'will reject the promise if clear persistence fails', async () => { await withTestDoc(persistence, async (docRef, firestore) => { diff --git a/packages/firestore/test/integration/browser/indexeddb.test.ts b/packages/firestore/test/integration/browser/indexeddb.test.ts index b00fd7fbb83..ec2667866cf 100644 --- a/packages/firestore/test/integration/browser/indexeddb.test.ts +++ b/packages/firestore/test/integration/browser/indexeddb.test.ts @@ -24,7 +24,12 @@ import { FirestoreError, setDoc } from '../util/firebase_export'; -import { isPersistenceAvailable, withTestDb } from '../util/helpers'; +import { + IndexedDbPersistenceMode, + MemoryEagerPersistenceMode, + isPersistenceAvailable, + withTestDb +} from '../util/helpers'; describe('where indexeddb is not available: ', () => { // Only test on platforms where persistence is *not* available (e.g. Edge, @@ -36,7 +41,7 @@ describe('where indexeddb is not available: ', () => { it('fails with code unimplemented', () => { // withTestDb will fail the test if persistence is requested but it fails // so we'll enable persistence here instead. - return withTestDb(/* persistence= */ false, db => { + return withTestDb(new MemoryEagerPersistenceMode(), db => { return enableIndexedDbPersistence(db).then( () => expect.fail('enablePersistence should not have succeeded!'), (error: FirestoreError) => { @@ -47,7 +52,7 @@ describe('where indexeddb is not available: ', () => { }); it('falls back without requiring a wait for the promise', () => { - return withTestDb(/* persistence= */ false, db => { + return withTestDb(new MemoryEagerPersistenceMode(), db => { const persistenceFailedPromise = enableIndexedDbPersistence(db).catch( (err: FirestoreError) => { expect(err.code).to.equal('unimplemented'); @@ -65,7 +70,7 @@ describe('where indexeddb is not available: ', () => { it('fails back to memory cache with initializeFirestore too', () => { // withTestDb will fail the test if persistence is requested but it fails // so we'll enable persistence here instead. - return withTestDb(/* persistence= */ true, db => { + return withTestDb(new IndexedDbPersistenceMode(), db => { // Do the set immediately without waiting on the promise. const testDoc = doc(collection(db, 'test-collection')); return setDoc(testDoc, { foo: 'bar' }); diff --git a/packages/firestore/test/integration/prime_backend.test.ts b/packages/firestore/test/integration/prime_backend.test.ts index 0be6b91d38e..c1c121e9a0f 100644 --- a/packages/firestore/test/integration/prime_backend.test.ts +++ b/packages/firestore/test/integration/prime_backend.test.ts @@ -23,7 +23,7 @@ import { onSnapshot, runTransaction } from './util/firebase_export'; -import { withTestDoc } from './util/helpers'; +import { MemoryEagerPersistenceMode, withTestDoc } from './util/helpers'; // Firestore databases can be subject to a ~30s "cold start" delay if they have not been used // recently, so before any tests run we "prime" the backend. @@ -35,7 +35,7 @@ before( function (): Promise { this.timeout(PRIMING_TIMEOUT_MS); - return withTestDoc(/*persistence=*/ false, async (doc, db) => { + return withTestDoc(new MemoryEagerPersistenceMode(), async (doc, db) => { const accumulator = new EventsAccumulator(); const unsubscribe = onSnapshot(doc, accumulator.storeEvent); diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 0dc74698729..ac52cdb01c9 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -25,10 +25,13 @@ import { DocumentData, DocumentReference, Firestore, + MemoryLocalCache, + memoryEagerGarbageCollector, memoryLocalCache, memoryLruGarbageCollector, newTestApp, newTestFirestore, + PersistentLocalCache, persistentLocalCache, PrivateSettings, QuerySnapshot, @@ -47,6 +50,92 @@ import { /* eslint-disable no-restricted-globals */ +export interface PersistenceMode { + readonly name: string; + readonly storage: 'memory' | 'indexeddb'; + readonly gc: 'eager' | 'lru'; + + /** + * Creates and returns a new `PersistenceMode` object that is the nearest + * equivalent to this persistence mode but uses eager garbage collection. + */ + toEagerGc(): PersistenceMode; + + /** + * Creates and returns a new `PersistenceMode` object that is the nearest + * equivalent to this persistence mode but uses LRU garbage collection. + */ + toLruGc(): PersistenceMode; + + /** + * Creates and returns a new "local cache" object corresponding to this + * persistence type. + */ + asLocalCacheFirestoreSettings(): MemoryLocalCache | PersistentLocalCache; +} + +export class MemoryEagerPersistenceMode implements PersistenceMode { + readonly name = 'memory'; + readonly storage = 'memory'; + readonly gc = 'eager'; + + toEagerGc(): MemoryEagerPersistenceMode { + return new MemoryEagerPersistenceMode(); + } + + toLruGc(): MemoryLruPersistenceMode { + return new MemoryLruPersistenceMode(); + } + + asLocalCacheFirestoreSettings(): MemoryLocalCache { + return memoryLocalCache({ + garbageCollector: memoryEagerGarbageCollector() + }); + } +} + +export class MemoryLruPersistenceMode implements PersistenceMode { + readonly name = 'memory_lru_gc'; + readonly storage = 'memory'; + readonly gc = 'lru'; + + toEagerGc(): MemoryEagerPersistenceMode { + return new MemoryEagerPersistenceMode(); + } + + toLruGc(): MemoryLruPersistenceMode { + return new MemoryLruPersistenceMode(); + } + + asLocalCacheFirestoreSettings(): MemoryLocalCache { + return memoryLocalCache({ garbageCollector: memoryLruGarbageCollector() }); + } +} + +export class IndexedDbPersistenceMode implements PersistenceMode { + readonly name = 'indexeddb'; + readonly storage = 'indexeddb'; + readonly gc = 'lru'; + + toEagerGc(): MemoryEagerPersistenceMode { + return new MemoryEagerPersistenceMode(); + } + + toLruGc(): IndexedDbPersistenceMode { + return new IndexedDbPersistenceMode(); + } + + asLocalCacheFirestoreSettings(): PersistentLocalCache { + if (this.gc !== 'lru') { + throw new Error( + `PersistentLocalCache does not support the given ` + + `garbage collector: ${this.gc}` + ); + } + return persistentLocalCache(); + } +} + function isIeOrEdge(): boolean { if (!window.navigator) { return false; @@ -77,24 +166,31 @@ export function isPersistenceAvailable(): boolean { function apiDescribeInternal( describeFn: Mocha.PendingSuiteFunction, message: string, - testSuite: (persistence: boolean) => void + testSuite: (persistence: PersistenceMode) => void ): void { - const persistenceModes = [false]; + const persistenceModes: PersistenceMode[] = [ + new MemoryEagerPersistenceMode() + ]; if (isPersistenceAvailable()) { - persistenceModes.push(true); + persistenceModes.push(new IndexedDbPersistenceMode()); } - for (const enabled of persistenceModes) { - describeFn(`(Persistence=${enabled}) ${message}`, () => testSuite(enabled)); + for (const persistenceMode of persistenceModes) { + describeFn(`(Persistence=${persistenceMode.name}) ${message}`, () => + // Freeze the properties of the `PersistenceMode` object specified to the + // test suite so that it cannot (accidentally or intentionally) change + // its properties, and affect all subsequent test suites. + testSuite(Object.freeze(persistenceMode)) + ); } } type ApiSuiteFunction = ( message: string, - testSuite: (persistence: boolean) => void + testSuite: (persistence: PersistenceMode) => void ) => void; interface ApiDescribe { - (message: string, testSuite: (persistence: boolean) => void): void; + (message: string, testSuite: (persistence: PersistenceMode) => void): void; skip: ApiSuiteFunction; only: ApiSuiteFunction; } @@ -137,7 +233,7 @@ export function toIds(docSet: QuerySnapshot): string[] { } export function withTestDb( - persistence: boolean, + persistence: PersistenceMode, fn: (db: Firestore) => Promise ): Promise { return withTestDbs(persistence, 1, ([db]) => { @@ -145,50 +241,9 @@ export function withTestDb( }); } -export function withEnsuredEagerGcTestDb( - fn: (db: Firestore) => Promise -): Promise { - return withTestDbsSettings( - false, - DEFAULT_PROJECT_ID, - { ...DEFAULT_SETTINGS, cacheSizeBytes: 1 * 1024 * 1024 }, - 1, - async ([db]) => { - return fn(db); - } - ); -} - -export function withEnsuredLruGcTestDb( - persistence: boolean, - fn: (db: Firestore) => Promise -): Promise { - const newSettings = { ...DEFAULT_SETTINGS }; - if (persistence) { - newSettings.localCache = persistentLocalCache({ - cacheSizeBytes: 1 * 1024 * 1024 - }); - } else { - newSettings.localCache = memoryLocalCache({ - garbageCollector: memoryLruGarbageCollector({ - cacheSizeBytes: 1 * 1024 * 1024 - }) - }); - } - return withTestDbsSettings( - persistence, - DEFAULT_PROJECT_ID, - newSettings, - 1, - async ([db]) => { - return fn(db); - } - ); -} - /** Runs provided fn with a db for an alternate project id. */ export function withAlternateTestDb( - persistence: boolean, + persistence: PersistenceMode, fn: (db: Firestore) => Promise ): Promise { return withTestDbsSettings( @@ -203,7 +258,7 @@ export function withAlternateTestDb( } export function withTestDbs( - persistence: boolean, + persistence: PersistenceMode, numDbs: number, fn: (db: Firestore[]) => Promise ): Promise { @@ -216,7 +271,7 @@ export function withTestDbs( ); } export async function withTestDbsSettings( - persistence: boolean, + persistence: PersistenceMode, projectId: string, settings: PrivateSettings, numDbs: number, @@ -229,10 +284,10 @@ export async function withTestDbsSettings( const dbs: Firestore[] = []; for (let i = 0; i < numDbs; i++) { - const newSettings = { ...settings }; - if (persistence) { - newSettings.localCache = persistentLocalCache(); - } + const newSettings = { + ...settings, + localCache: persistence.asLocalCacheFirestoreSettings() + }; const db = newTestFirestore(newTestApp(projectId), newSettings); dbs.push(db); } @@ -242,7 +297,7 @@ export async function withTestDbsSettings( } finally { for (const db of dbs) { await terminate(db); - if (persistence) { + if (persistence.storage === 'indexeddb') { await clearIndexedDbPersistence(db); } } @@ -250,7 +305,7 @@ export async function withTestDbsSettings( } export async function withNamedTestDbsOrSkipUnlessUsingEmulator( - persistence: boolean, + persistence: PersistenceMode, dbNames: string[], fn: (db: Firestore[]) => Promise ): Promise { @@ -264,10 +319,10 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( const app = newTestApp(DEFAULT_PROJECT_ID); const dbs: Firestore[] = []; for (const dbName of dbNames) { - const newSettings = { ...DEFAULT_SETTINGS }; - if (persistence) { - newSettings.localCache = persistentLocalCache(); - } + const newSettings = { + ...DEFAULT_SETTINGS, + localCache: persistence.asLocalCacheFirestoreSettings() + }; const db = newTestFirestore(app, newSettings, dbName); dbs.push(db); } @@ -277,7 +332,7 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( } finally { for (const db of dbs) { await terminate(db); - if (persistence) { + if (persistence.storage === 'indexeddb') { await clearIndexedDbPersistence(db); } } @@ -285,7 +340,7 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( } export function withTestDoc( - persistence: boolean, + persistence: PersistenceMode, fn: (doc: DocumentReference, db: Firestore) => Promise ): Promise { return withTestDb(persistence, db => { @@ -294,7 +349,7 @@ export function withTestDoc( } export function withTestDocAndSettings( - persistence: boolean, + persistence: PersistenceMode, settings: PrivateSettings, fn: (doc: DocumentReference) => Promise ): Promise { @@ -315,7 +370,7 @@ export function withTestDocAndSettings( // `withTestDoc(..., docRef => { setDoc(docRef, initialData) ...});` that // otherwise is quite common. export function withTestDocAndInitialData( - persistence: boolean, + persistence: PersistenceMode, initialData: DocumentData | null, fn: (doc: DocumentReference, db: Firestore) => Promise ): Promise { @@ -330,7 +385,7 @@ export function withTestDocAndInitialData( } export function withTestCollection( - persistence: boolean, + persistence: PersistenceMode, docs: { [key: string]: DocumentData }, fn: (collection: CollectionReference, db: Firestore) => Promise ): Promise { @@ -338,7 +393,7 @@ export function withTestCollection( } export function withEmptyTestCollection( - persistence: boolean, + persistence: PersistenceMode, fn: (collection: CollectionReference, db: Firestore) => Promise ): Promise { return withTestCollection(persistence, {}, fn); @@ -347,7 +402,7 @@ export function withEmptyTestCollection( // TODO(mikelehen): Once we wipe the database between tests, we can probably // return the same collection every time. export function withTestCollectionSettings( - persistence: boolean, + persistence: PersistenceMode, settings: PrivateSettings, docs: { [key: string]: DocumentData }, fn: (collection: CollectionReference, db: Firestore) => Promise diff --git a/packages/firestore/test/integration/util/internal_helpers.ts b/packages/firestore/test/integration/util/internal_helpers.ts index 4abfcf78096..86ded6af3c1 100644 --- a/packages/firestore/test/integration/util/internal_helpers.ts +++ b/packages/firestore/test/integration/util/internal_helpers.ts @@ -42,7 +42,7 @@ import { limit, limitToLast } from './firebase_export'; -import { withTestDbsSettings } from './helpers'; +import { withTestDbsSettings, PersistenceMode } from './helpers'; import { DEFAULT_PROJECT_ID, DEFAULT_SETTINGS } from './settings'; export function asyncQueue(db: Firestore): AsyncQueueImpl { @@ -101,7 +101,7 @@ export class MockAuthCredentialsProvider extends EmptyAuthCredentialsProvider { } export function withMockCredentialProviderTestDb( - persistence: boolean, + persistence: PersistenceMode, fn: ( db: Firestore, mockCredential: MockAuthCredentialsProvider