From eac7d9755868b277d46a8bfbd673036f82b91efd Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Thu, 2 Feb 2023 09:20:51 -0700 Subject: [PATCH 1/9] Tests for sum and average. --- packages/firestore/lite/index.ts | 3 +- packages/firestore/src/api.ts | 3 +- packages/firestore/src/api/aggregate.ts | 3 +- packages/firestore/src/lite-api/aggregate.ts | 1 + .../firestore/src/lite-api/aggregate_types.ts | 11 +- .../test/integration/api/aggregation.test.ts | 1229 ++++++++++++++++- .../firestore/test/lite/integration.test.ts | 156 ++- .../firestore/test/unit/api/aggregate.test.ts | 38 + 8 files changed, 1427 insertions(+), 17 deletions(-) create mode 100644 packages/firestore/test/unit/api/aggregate.test.ts diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index f35e2b61fc7..bec95b76124 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -33,7 +33,8 @@ export { getAggregate, count, sum, - average + average, + aggregateFieldEqual } from '../src/lite-api/aggregate'; export { diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index 523cdb4581e..93da0b1ee08 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -21,7 +21,8 @@ export { getAggregateFromServer, count, sum, - average + average, + aggregateFieldEqual } from './api/aggregate'; export { diff --git a/packages/firestore/src/api/aggregate.ts b/packages/firestore/src/api/aggregate.ts index 29edc86db25..810737815ab 100644 --- a/packages/firestore/src/api/aggregate.ts +++ b/packages/firestore/src/api/aggregate.ts @@ -31,7 +31,8 @@ export { aggregateQuerySnapshotEqual, count, sum, - average + average, + aggregateFieldEqual } from '../lite-api/aggregate'; /** diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index ea43c64f8b1..e09e7c8c07b 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -164,6 +164,7 @@ export function count(): AggregateField { * * @param left Compare this AggregateField to the `right`. * @param right Compare this AggregateField to the `left`. + * @internal TODO (sum/avg) remove when public */ export function aggregateFieldEqual( left: AggregateField, diff --git a/packages/firestore/src/lite-api/aggregate_types.ts b/packages/firestore/src/lite-api/aggregate_types.ts index ab9619b58b4..9c5c40e0052 100644 --- a/packages/firestore/src/lite-api/aggregate_types.ts +++ b/packages/firestore/src/lite-api/aggregate_types.ts @@ -65,9 +65,18 @@ export interface AggregateSpec { * from the input `AggregateSpec`. */ export type AggregateSpecData = { - [P in keyof T]: T[P] extends AggregateField ? U : never; + [P in keyof T as TrimBackticks

]: T[P] extends AggregateField + ? U + : never; }; +/** + * Removes enclosing backticks. + * type Foo = '`Foo`' + * type TrimmedFoo = TrimBackticks; // 'Foo' + */ +type TrimBackticks = T extends `\`${infer Body}\`` ? Body : T; + /** * The results of executing an aggregation query. */ diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index df09f2cca37..0e46887c39e 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -30,7 +30,9 @@ import { terminate, where, writeBatch, - count + count, + sum, + average } from '../util/firebase_export'; import { apiDescribe, @@ -40,7 +42,7 @@ import { } from '../util/helpers'; import { USE_EMULATOR } from '../util/settings'; -apiDescribe('Count quries', (persistence: boolean) => { +apiDescribe('Count queries', (persistence: boolean) => { it('can run count query getCountFromServer', () => { const testDocs = { a: { author: 'authorA', title: 'titleA' }, @@ -143,8 +145,7 @@ apiDescribe('Count quries', (persistence: boolean) => { ); }); -// TODO(sum/avg) update this with sum and average when it is supported by the emulator -apiDescribe('Aggregation quries', (persistence: boolean) => { +apiDescribe('Aggregation queries', (persistence: boolean) => { it('can run count query getAggregationFromServer', () => { const testDocs = { a: { author: 'authorA', title: 'titleA' }, @@ -165,9 +166,53 @@ apiDescribe('Aggregation quries', (persistence: boolean) => { }; return withTestCollection(persistence, testDocs, async coll => { const snapshot = await getAggregateFromServer(coll, { - foo: count() + foo: count(), + 'with.dots': count() }); expect(snapshot.data().foo).to.equal(2); + expect(snapshot.data()['with.dots']).to.equal(2); + }); + }); + + it('rejects bad aliases when using getAggregationFromServer unquoted', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA' }, + b: { author: 'authorB', title: 'titleB' } + }; + return withTestCollection(persistence, testDocs, async coll => { + const promise = getAggregateFromServer(coll, { + 'with-un/supp[or]ted': count() + }); + await expect(promise).to.eventually.rejectedWith(/INVALID_ARGUMENT/); + }); + }); + + it('allows special chars in quoted aliases when using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA' }, + b: { author: 'authorB', title: 'titleB' } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + '`with-un/su+pp[or]ted`': count() + }); + + // Note that the backticks (quotes) are removed from the alias + // in the returned object + expect(snapshot.data()['with-un/su+pp[or]ted']).to.equal(2); + }); + }); + + it('rejects bad quoted aliases when using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA' }, + b: { author: 'authorB', title: 'titleB' } + }; + return withTestCollection(persistence, testDocs, async coll => { + const promise = getAggregateFromServer(coll, { + '`with`unsupported`': count() + }); + await expect(promise).to.eventually.rejectedWith(/INVALID_ARGUMENT/); }); }); @@ -279,3 +324,1177 @@ apiDescribe('Aggregation quries', (persistence: boolean) => { } ); }); + +// TODO (sum/avg) enable these tests when sum/avg is supported by the backend +apiDescribe.skip( + 'Aggregation queries - sum / average', + (persistence: boolean) => { + it('can run sum query getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages') + }); + expect(snapshot.data().totalPages).to.equal(150); + }); + }); + + it('can run average query getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averagePages: average('pages') + }); + expect(snapshot.data().averagePages).to.equal(75); + }); + }); + + it('can get multiple aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count() + }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().count).to.equal(2); + }); + }); + + it('can get duplicate aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + totalPagesX: sum('pages'), + averagePagesY: average('pages') + }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().totalPagesX).to.equal(150); + expect(snapshot.data().averagePagesY).to.equal(75); + }); + }); + + it('can perform max (5) aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages') + }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().count).to.equal(2); + expect(snapshot.data().totalPagesX).to.equal(150); + expect(snapshot.data().averagePagesY).to.equal(75); + }); + }); + + it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const promise = getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages'), + countZ: count() + }); + + await expect(promise).to.eventually.be.rejectedWith( + /INVALID_ARGUMENT.*maximum number of aggregations/ + ); + }); + }); + + it('aggregate query supports collection groups', () => { + return withTestDb(persistence, async db => { + const collectionGroupId = doc(collection(db, 'aggregateQueryTest')).id; + const docPaths = [ + `${collectionGroupId}/cg-doc1`, + `abc/123/${collectionGroupId}/cg-doc2`, + `zzz${collectionGroupId}/cg-doc3`, + `abc/123/zzz${collectionGroupId}/cg-doc4`, + `abc/123/zzz/${collectionGroupId}` + ]; + const batch = writeBatch(db); + for (const docPath of docPaths) { + batch.set(doc(db, docPath), { x: 2 }); + } + await batch.commit(); + const snapshot = await getAggregateFromServer( + collectionGroup(db, collectionGroupId), + { + count: count(), + sum: sum('x'), + avg: average('x') + } + ); + expect(snapshot.data().count).to.equal(2); + expect(snapshot.data().sum).to.equal(4); + expect(snapshot.data().avg).to.equal(2); + }); + }); + + it('performs aggregations on documents with all aggregated fields using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, + b: { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, + c: { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, + d: { author: 'authorD', title: 'titleD', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + averageYear: average('year'), + count: count() + }); + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().averageYear).to.equal(2007); + expect(snapshot.data().count).to.equal(3); + }); + }); + + it('performs aggregates on multiple fields where one aggregate could cause short-circuit due to NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating'), + totalPages: sum('pages'), + averageYear: average('year') + }); + expect(snapshot.data().totalRating).to.be.NaN; + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averageYear).to.equal(2000); + }); + }); + + it('returns undefined when getting the result of an unrequested aggregation', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + totalRating: sum('rating'), + averageRating: average('rating') + } + ); + + // @ts-expect-error + const totalPages = snapshot.data().totalPages; + expect(totalPages).to.equal(undefined); + }); + }); + + it('performs aggregates when using `in` operator getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('rating', 'in', [5, 3])), + { + totalRating: sum('rating'), + averageRating: average('rating'), + totalPages: sum('pages'), + averagePages: average('pages'), + countOfDocs: count() + } + ); + expect(snapshot.data().totalRating).to.equal(8); + expect(snapshot.data().averageRating).to.equal(4); + expect(snapshot.data().totalPages).to.equal(200); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().countOfDocs).to.equal(2); + }); + }); + + it('performs aggregates when using `array-contains-any` operator getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: [5, 1000] + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: [4] + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: [2222, 3] + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: [0] + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('rating', 'array-contains-any', [5, 3])), + { + totalRating: sum('rating'), + averageRating: average('rating'), + totalPages: sum('pages'), + averagePages: average('pages'), + countOfDocs: count() + } + ); + expect(snapshot.data().totalRating).to.equal(0); + expect(snapshot.data().averageRating).to.be.null; + expect(snapshot.data().totalPages).to.equal(200); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().countOfDocs).to.equal(2); + }); + }); + + it('performs aggregations on nested map values using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + metadata: { pages: 100, rating: { critic: 2, user: 5 } } + }, + b: { + author: 'authorB', + title: 'titleB', + metadata: { pages: 50, rating: { critic: 4, user: 4 } } + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('metadata.pages'), + averagePages: average('metadata.pages'), + averageCriticRating: average('metadata.rating.critic'), + totalUserRating: sum('metadata.rating.user'), + count: count() + }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().averageCriticRating).to.equal(3); + expect(snapshot.data().totalUserRating).to.equal(9); + expect(snapshot.data().count).to.equal(2); + }); + }); + + it('performs sum that results in float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(12.5); + }); + }); + + it('performs sum of ints and floats that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(13); + }); + }); + + it('performs sum that overflows max int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_SAFE_INTEGER + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal( + Number.MAX_SAFE_INTEGER + Number.MAX_SAFE_INTEGER + ); + }); + }); + + it('performs sum that can overflow integer values during accumulation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 1 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 50, + year: 2020, + rating: -101 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal( + Number.MAX_SAFE_INTEGER - 100 + ); + }); + }); + + it('performs sum that is negative using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MIN_SAFE_INTEGER + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 50, + year: 2020, + rating: -101 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: -10000 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(-10101); + }); + }); + + it('performs sum that is positive infinity using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); + }); + }); + + it('performs sum that is negative infinity using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(Number.NEGATIVE_INFINITY); + }); + }); + + it('performs sum that is valid but could overflow during aggregation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + }, + e: { + author: 'authorE', + title: 'titleE', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + f: { + author: 'authorF', + title: 'titleF', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + }, + g: { + author: 'authorG', + title: 'titleG', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + h: { + author: 'authorH', + title: 'titleDH', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(0); + }); + }); + + it('performs sum that includes NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.be.NaN; + }); + }); + + it('performs sum over a result set of zero documents using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + totalRating: sum('rating') + } + ); + expect(snapshot.data().totalRating).to.equal(0); + }); + }); + + it('performs sum only on numeric fields using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: '3' + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 1 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating'), + countOfDocs: count() + }); + expect(snapshot.data().totalRating).to.equal(10); + expect(snapshot.data().countOfDocs).to.equal(4); + }); + }); + + it('performs sum of min IEEE754 using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(Number.MIN_VALUE); + }); + }); + + it('performs average of ints that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(5); + }); + }); + + it('performs average of floats that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(10); + }); + }); + + it('performs average of floats and ints that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9.5 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(10); + }); + }); + + it('performs average of float that results in float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(4.5); + }); + }); + + it('performs average of floats and ints that results in a float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(10); + }); + }); + + it('performs average of ints that results in a float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(9.5); + }); + }); + + it('performs average causing underflow using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(0); + }); + }); + + it('performs average of min IEEE754 using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(Number.MIN_VALUE); + }); + }); + + it('performs average that could overflow IEEE754 during accumulation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(Number.MAX_VALUE); + }); + }); + + it('performs average that includes NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.be.NaN; + }); + }); + + it('performs average over a result set of zero documents using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + averageRating: average('rating') + } + ); + expect(snapshot.data().averageRating).to.be.null; + }); + }); + + it('performs average only on numeric fields using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: '3' + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 6 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating'), + countOfDocs: count() + }); + expect(snapshot.data().averageRating).to.equal(5); + expect(snapshot.data().countOfDocs).to.equal(4); + }); + }); + } +); diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index e6680ee8fcc..658b3c40a37 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -24,7 +24,9 @@ import { aggregateQuerySnapshotEqual, getCount, getAggregate, - count + count, + sum, + average } from '../../src/lite-api/aggregate'; import { Bytes } from '../../src/lite-api/bytes'; import { @@ -83,6 +85,7 @@ import { import { Timestamp } from '../../src/lite-api/timestamp'; import { runTransaction } from '../../src/lite-api/transaction'; import { writeBatch } from '../../src/lite-api/write_batch'; +import { apiDescribe } from '../integration/util/helpers'; import { DEFAULT_PROJECT_ID, DEFAULT_SETTINGS, @@ -2122,7 +2125,7 @@ describe('withConverter() support', () => { }); // eslint-disable-next-line no-restricted-properties -describe('Count quries', () => { +describe('Count queries', () => { it('AggregateQuerySnapshot inherits the original query', () => { return withTestCollection(async coll => { const query_ = query(coll); @@ -2405,8 +2408,7 @@ describe('Count quries', () => { ); }); -// TODO(sum/avg) update this with sum and average when it is supported by the emulator -describe('Aggregate quries', () => { +describe('Aggregate queries', () => { it('AggregateQuerySnapshot inherits the original query', () => { return withTestCollection(async coll => { const query_ = query(coll); @@ -2651,10 +2653,10 @@ describe('Aggregate quries', () => { it('aggregateQuerySnapshotEqual on different queries be falsy', () => { const testDocs = [ - { author: 'authorA', title: 'titleA' }, - { author: 'authorA', title: 'titleB' }, - { author: 'authorB', title: 'titleC' }, - { author: 'authorB', title: 'titleD' } + { author: 'authorA', title: 'titleA', rating: 1 }, + { author: 'authorA', title: 'titleB', rating: 5 }, + { author: 'authorB', title: 'titleC', rating: 4 }, + { author: 'authorB', title: 'titleD', rating: 3 } ]; return withTestCollectionAndInitialData(testDocs, async coll => { const query1 = query(coll, where('author', '==', 'authorA')); @@ -2665,6 +2667,56 @@ describe('Aggregate quries', () => { }); }); + it('aggregateQuerySnapshotEqual on different aggregations to be falsy', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', rating: 1 }, + { author: 'authorA', title: 'titleB', rating: 5 }, + { author: 'authorB', title: 'titleC', rating: 4 }, + { author: 'authorB', title: 'titleD', rating: 3 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const snapshot1 = await getAggregate(query1, { sum: sum('rating') }); + const snapshot2 = await getAggregate(query1, { avg: average('rating') }); + + // @ts-expect-error + expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.false; + }); + }); + + it('aggregateQuerySnapshotEqual on same aggregations with different aliases to be falsy', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', rating: 1 }, + { author: 'authorA', title: 'titleB', rating: 5 }, + { author: 'authorB', title: 'titleC', rating: 4 }, + { author: 'authorB', title: 'titleD', rating: 3 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const snapshot1 = await getAggregate(query1, { foo: average('rating') }); + const snapshot2 = await getAggregate(query1, { bar: average('rating') }); + + // @ts-expect-error + expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.false; + }); + }); + + it('aggregateQuerySnapshotEqual on same aggregations with same aliases to be truthy', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', rating: 1 }, + { author: 'authorA', title: 'titleB', rating: 5 }, + { author: 'authorB', title: 'titleC', rating: 4 }, + { author: 'authorB', title: 'titleD', rating: 3 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const snapshot1 = await getAggregate(query1, { foo: average('rating') }); + const snapshot2 = await getAggregate(query1, { foo: average('rating') }); + + expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.true; + }); + }); + it('aggregate query fails on a terminated Firestore', () => { return withTestCollection(async coll => { await terminate(coll.firestore); @@ -2712,3 +2764,91 @@ describe('Aggregate quries', () => { } ); }); + +// TODO (sum/avg) enable these tests when sum/avg is supported by the backend +apiDescribe.skip('Aggregation queries - sum / average', () => { + it('can run sum query getAggregationFromServer', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', pages: 100 }, + { author: 'authorB', title: 'titleB', pages: 50 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const snapshot = await getAggregate(coll, { + totalPages: sum('pages') + }); + expect(snapshot.data().totalPages).to.equal(150); + }); + }); + + it('can run average query getAggregationFromServer', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', pages: 100 }, + { author: 'authorB', title: 'titleB', pages: 50 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const snapshot = await getAggregate(coll, { + averagePages: average('pages') + }); + expect(snapshot.data().averagePages).to.equal(75); + }); + }); + + it('can get multiple aggregations using getAggregationFromServer', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', pages: 100 }, + { author: 'authorB', title: 'titleB', pages: 50 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const snapshot = await getAggregate(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count() + }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().count).to.equal(2); + }); + }); + + it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', pages: 100 }, + { author: 'authorB', title: 'titleB', pages: 50 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const promise = getAggregate(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages'), + countZ: count() + }); + + await expect(promise).to.eventually.be.rejectedWith( + /maximum number of aggregations/ + ); + }); + }); + + it('performs aggregations on documents with all aggregated fields using getAggregationFromServer', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, + { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, + { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, + { author: 'authorD', title: 'titleD', pages: 50 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const snapshot = await getAggregate(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + averageYear: average('year'), + count: count() + }); + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().averageYear).to.equal(2007); + expect(snapshot.data().count).to.equal(3); + }); + }); +}); diff --git a/packages/firestore/test/unit/api/aggregate.test.ts b/packages/firestore/test/unit/api/aggregate.test.ts new file mode 100644 index 00000000000..79240b02962 --- /dev/null +++ b/packages/firestore/test/unit/api/aggregate.test.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright 2022 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. + */ + +import { expect } from 'chai'; + +import { aggregateFieldEqual, count, sum, average } from '../../../src'; + +describe('aggregateFieldEqual', () => { + it('equates two equal aggregate fields', () => { + expect(aggregateFieldEqual(count(), count())).to.be.true; + expect(aggregateFieldEqual(sum('foo'), sum('foo'))).to.be.true; + expect(aggregateFieldEqual(average('bar'), average('bar'))).to.be.true; + expect(aggregateFieldEqual(sum('foo.bar'), sum('foo.bar'))).to.be.true; + expect(aggregateFieldEqual(average('bar.baz'), average('bar.baz'))).to.be + .true; + }); + + it('differentiates two different aggregate fields', () => { + expect(aggregateFieldEqual(sum('foo'), sum('bar'))).to.be.false; + expect(aggregateFieldEqual(average('foo'), average('bar'))).to.be.false; + expect(aggregateFieldEqual(average('foo'), sum('foo'))).to.be.false; + expect(aggregateFieldEqual(sum('foo'), average('foo'))).to.be.false; + }); +}); From 441bd618e1527cb78b7c43d02fba03e13b4cedf4 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Thu, 2 Feb 2023 09:36:40 -0700 Subject: [PATCH 2/9] Exporting TrimBackticks type. --- common/api-review/firestore-lite.api.md | 5 ++++- common/api-review/firestore.api.md | 5 ++++- packages/firestore/lite/index.ts | 3 ++- packages/firestore/src/api.ts | 1 + packages/firestore/src/api/aggregate.ts | 3 --- packages/firestore/src/lite-api/aggregate.ts | 3 --- packages/firestore/src/lite-api/aggregate_types.ts | 2 +- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 153aadf3040..5b013c6c2db 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -43,7 +43,7 @@ export interface AggregateSpec { // @public export type AggregateSpecData = { - [P in keyof T]: T[P] extends AggregateField ? U : never; + [P in keyof T as TrimBackticks

]: T[P] extends AggregateField ? U : never; }; // @public @@ -401,6 +401,9 @@ export interface TransactionOptions { readonly maxAttempts?: number; } +// @public +export type TrimBackticks = T extends `\`${infer Body}\`` ? Body : T; + // @public export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 35f887736f7..f8a0bc408fb 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -43,7 +43,7 @@ export interface AggregateSpec { // @public export type AggregateSpecData = { - [P in keyof T]: T[P] extends AggregateField ? U : never; + [P in keyof T as TrimBackticks

]: T[P] extends AggregateField ? U : never; }; // @public @@ -575,6 +575,9 @@ export interface TransactionOptions { readonly maxAttempts?: number; } +// @public +export type TrimBackticks = T extends `\`${infer Body}\`` ? Body : T; + // @public export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index bec95b76124..7b8f9eab6f5 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -43,7 +43,8 @@ export { AggregateSpec, AggregateSpecData, AggregateQuerySnapshot, - AggregateType + AggregateType, + TrimBackticks } from '../src/lite-api/aggregate_types'; export { FirestoreSettings as Settings } from '../src/lite-api/settings'; diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index 93da0b1ee08..db606debc53 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -30,6 +30,7 @@ export { AggregateFieldType, AggregateSpec, AggregateSpecData, + TrimBackticks, AggregateQuerySnapshot, AggregateType } from './lite-api/aggregate_types'; diff --git a/packages/firestore/src/api/aggregate.ts b/packages/firestore/src/api/aggregate.ts index 810737815ab..8ba305f69d9 100644 --- a/packages/firestore/src/api/aggregate.ts +++ b/packages/firestore/src/api/aggregate.ts @@ -108,9 +108,6 @@ export function getAggregateFromServer( const client = ensureFirestoreConfigured(firestore); const internalAggregates = mapToArray(aggregateSpec, (aggregate, alias) => { - // TODO (sum/avg) should alias validation be performed or should that be - // delegated to the backend? - return new AggregateImpl( alias, aggregate._aggregateType, diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index e09e7c8c07b..49e809c094e 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -94,9 +94,6 @@ export function getAggregate( const datastore = getDatastore(firestore); const internalAggregates = mapToArray(aggregateSpec, (aggregate, alias) => { - // TODO (sum/avg) should alias validation be performed or should that be - // delegated to the backend? - return new AggregateImpl( alias, aggregate._aggregateType, diff --git a/packages/firestore/src/lite-api/aggregate_types.ts b/packages/firestore/src/lite-api/aggregate_types.ts index 9c5c40e0052..c087ea3ca2e 100644 --- a/packages/firestore/src/lite-api/aggregate_types.ts +++ b/packages/firestore/src/lite-api/aggregate_types.ts @@ -75,7 +75,7 @@ export type AggregateSpecData = { * type Foo = '`Foo`' * type TrimmedFoo = TrimBackticks; // 'Foo' */ -type TrimBackticks = T extends `\`${infer Body}\`` ? Body : T; +export type TrimBackticks = T extends `\`${infer Body}\`` ? Body : T; /** * The results of executing an aggregation query. From 162c91030759a27fa537fb579cd3a0a43cb70b78 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Thu, 2 Feb 2023 11:03:00 -0700 Subject: [PATCH 3/9] Skipping a few missed tests that use sum and average, which will fail against the backend during CI. --- .../firestore/test/lite/integration.test.ts | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 658b3c40a37..bdda174cd1f 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -2667,56 +2667,6 @@ describe('Aggregate queries', () => { }); }); - it('aggregateQuerySnapshotEqual on different aggregations to be falsy', () => { - const testDocs = [ - { author: 'authorA', title: 'titleA', rating: 1 }, - { author: 'authorA', title: 'titleB', rating: 5 }, - { author: 'authorB', title: 'titleC', rating: 4 }, - { author: 'authorB', title: 'titleD', rating: 3 } - ]; - return withTestCollectionAndInitialData(testDocs, async coll => { - const query1 = query(coll, where('author', '==', 'authorA')); - const snapshot1 = await getAggregate(query1, { sum: sum('rating') }); - const snapshot2 = await getAggregate(query1, { avg: average('rating') }); - - // @ts-expect-error - expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.false; - }); - }); - - it('aggregateQuerySnapshotEqual on same aggregations with different aliases to be falsy', () => { - const testDocs = [ - { author: 'authorA', title: 'titleA', rating: 1 }, - { author: 'authorA', title: 'titleB', rating: 5 }, - { author: 'authorB', title: 'titleC', rating: 4 }, - { author: 'authorB', title: 'titleD', rating: 3 } - ]; - return withTestCollectionAndInitialData(testDocs, async coll => { - const query1 = query(coll, where('author', '==', 'authorA')); - const snapshot1 = await getAggregate(query1, { foo: average('rating') }); - const snapshot2 = await getAggregate(query1, { bar: average('rating') }); - - // @ts-expect-error - expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.false; - }); - }); - - it('aggregateQuerySnapshotEqual on same aggregations with same aliases to be truthy', () => { - const testDocs = [ - { author: 'authorA', title: 'titleA', rating: 1 }, - { author: 'authorA', title: 'titleB', rating: 5 }, - { author: 'authorB', title: 'titleC', rating: 4 }, - { author: 'authorB', title: 'titleD', rating: 3 } - ]; - return withTestCollectionAndInitialData(testDocs, async coll => { - const query1 = query(coll, where('author', '==', 'authorA')); - const snapshot1 = await getAggregate(query1, { foo: average('rating') }); - const snapshot2 = await getAggregate(query1, { foo: average('rating') }); - - expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.true; - }); - }); - it('aggregate query fails on a terminated Firestore', () => { return withTestCollection(async coll => { await terminate(coll.firestore); @@ -2767,6 +2717,56 @@ describe('Aggregate queries', () => { // TODO (sum/avg) enable these tests when sum/avg is supported by the backend apiDescribe.skip('Aggregation queries - sum / average', () => { + it('aggregateQuerySnapshotEqual on different aggregations to be falsy', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', rating: 1 }, + { author: 'authorA', title: 'titleB', rating: 5 }, + { author: 'authorB', title: 'titleC', rating: 4 }, + { author: 'authorB', title: 'titleD', rating: 3 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const snapshot1 = await getAggregate(query1, { sum: sum('rating') }); + const snapshot2 = await getAggregate(query1, { avg: average('rating') }); + + // @ts-expect-error + expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.false; + }); + }); + + it('aggregateQuerySnapshotEqual on same aggregations with different aliases to be falsy', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', rating: 1 }, + { author: 'authorA', title: 'titleB', rating: 5 }, + { author: 'authorB', title: 'titleC', rating: 4 }, + { author: 'authorB', title: 'titleD', rating: 3 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const snapshot1 = await getAggregate(query1, { foo: average('rating') }); + const snapshot2 = await getAggregate(query1, { bar: average('rating') }); + + // @ts-expect-error + expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.false; + }); + }); + + it('aggregateQuerySnapshotEqual on same aggregations with same aliases to be truthy', () => { + const testDocs = [ + { author: 'authorA', title: 'titleA', rating: 1 }, + { author: 'authorA', title: 'titleB', rating: 5 }, + { author: 'authorB', title: 'titleC', rating: 4 }, + { author: 'authorB', title: 'titleD', rating: 3 } + ]; + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const snapshot1 = await getAggregate(query1, { foo: average('rating') }); + const snapshot2 = await getAggregate(query1, { foo: average('rating') }); + + expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.true; + }); + }); + it('can run sum query getAggregationFromServer', () => { const testDocs = [ { author: 'authorA', title: 'titleA', pages: 100 }, From 0f54b4bdfb79c726a474df7d4f33bc09c552e9cd Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:11:59 -0700 Subject: [PATCH 4/9] Making tests robust to older versions of the emulator. --- packages/firestore/test/integration/api/aggregation.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index 0e46887c39e..aa443d245dd 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -183,7 +183,7 @@ apiDescribe('Aggregation queries', (persistence: boolean) => { const promise = getAggregateFromServer(coll, { 'with-un/supp[or]ted': count() }); - await expect(promise).to.eventually.rejectedWith(/INVALID_ARGUMENT/); + await expect(promise).to.eventually.rejectedWith(/invalid/i); }); }); @@ -212,7 +212,7 @@ apiDescribe('Aggregation queries', (persistence: boolean) => { const promise = getAggregateFromServer(coll, { '`with`unsupported`': count() }); - await expect(promise).to.eventually.rejectedWith(/INVALID_ARGUMENT/); + await expect(promise).to.eventually.rejectedWith(/invalid/i); }); }); From 48a8b86b9ff64cb1603d80151b5097b0c7538b1f Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 3 Feb 2023 09:35:40 -0700 Subject: [PATCH 5/9] Increase timeout of transaction test that consistently times out in CI. --- packages/firestore/test/integration/api/transactions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/test/integration/api/transactions.test.ts b/packages/firestore/test/integration/api/transactions.test.ts index 6126e9f9cb8..df7e520fccd 100644 --- a/packages/firestore/test/integration/api/transactions.test.ts +++ b/packages/firestore/test/integration/api/transactions.test.ts @@ -275,7 +275,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { .run(get, set1, set2) .expectDoc({ foo: 'bar2' }); }); - }); + }).timeout(10000); it('runs transactions after getting non-existent document', async () => { return withTestDb(persistence, async db => { From efdbcce061822c45e38a2c815cf755271e3629ed Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:16:09 -0700 Subject: [PATCH 6/9] Addressing PR feedback. --- common/api-review/firestore-lite.api.md | 5 +- common/api-review/firestore.api.md | 5 +- packages/firestore/lite/index.ts | 3 +- packages/firestore/src/api.ts | 1 - packages/firestore/src/api/aggregate.ts | 3 +- packages/firestore/src/core/aggregate.ts | 5 +- packages/firestore/src/lite-api/aggregate.ts | 3 +- .../firestore/src/lite-api/aggregate_types.ts | 11 +- .../firestore/src/model/aggregate_alias.ts | 48 + packages/firestore/src/remote/serializer.ts | 6 +- .../test/integration/api/aggregation.test.ts | 2143 ++++++++--------- .../firestore/test/lite/integration.test.ts | 24 +- 12 files changed, 1150 insertions(+), 1107 deletions(-) create mode 100644 packages/firestore/src/model/aggregate_alias.ts diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 5b013c6c2db..153aadf3040 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -43,7 +43,7 @@ export interface AggregateSpec { // @public export type AggregateSpecData = { - [P in keyof T as TrimBackticks

]: T[P] extends AggregateField ? U : never; + [P in keyof T]: T[P] extends AggregateField ? U : never; }; // @public @@ -401,9 +401,6 @@ export interface TransactionOptions { readonly maxAttempts?: number; } -// @public -export type TrimBackticks = T extends `\`${infer Body}\`` ? Body : T; - // @public export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index f8a0bc408fb..35f887736f7 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -43,7 +43,7 @@ export interface AggregateSpec { // @public export type AggregateSpecData = { - [P in keyof T as TrimBackticks

]: T[P] extends AggregateField ? U : never; + [P in keyof T]: T[P] extends AggregateField ? U : never; }; // @public @@ -575,9 +575,6 @@ export interface TransactionOptions { readonly maxAttempts?: number; } -// @public -export type TrimBackticks = T extends `\`${infer Body}\`` ? Body : T; - // @public export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index 7b8f9eab6f5..bec95b76124 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -43,8 +43,7 @@ export { AggregateSpec, AggregateSpecData, AggregateQuerySnapshot, - AggregateType, - TrimBackticks + AggregateType } from '../src/lite-api/aggregate_types'; export { FirestoreSettings as Settings } from '../src/lite-api/settings'; diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index db606debc53..93da0b1ee08 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -30,7 +30,6 @@ export { AggregateFieldType, AggregateSpec, AggregateSpecData, - TrimBackticks, AggregateQuerySnapshot, AggregateType } from './lite-api/aggregate_types'; diff --git a/packages/firestore/src/api/aggregate.ts b/packages/firestore/src/api/aggregate.ts index 8ba305f69d9..3ced0c8b292 100644 --- a/packages/firestore/src/api/aggregate.ts +++ b/packages/firestore/src/api/aggregate.ts @@ -20,6 +20,7 @@ import { AggregateImpl } from '../core/aggregate'; import { firestoreClientRunAggregateQuery } from '../core/firestore_client'; import { count } from '../lite-api/aggregate'; import { AggregateQuerySnapshot } from '../lite-api/aggregate_types'; +import { AggregateAlias } from '../model/aggregate_alias'; import { ObjectValue } from '../model/object_value'; import { cast } from '../util/input_validation'; import { mapToArray } from '../util/obj'; @@ -109,7 +110,7 @@ export function getAggregateFromServer( const internalAggregates = mapToArray(aggregateSpec, (aggregate, alias) => { return new AggregateImpl( - alias, + new AggregateAlias(alias), aggregate._aggregateType, aggregate._internalFieldPath ); diff --git a/packages/firestore/src/core/aggregate.ts b/packages/firestore/src/core/aggregate.ts index 42cdd0524ff..54e8c826547 100644 --- a/packages/firestore/src/core/aggregate.ts +++ b/packages/firestore/src/core/aggregate.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { AggregateAlias } from '../model/aggregate_alias'; import { FieldPath } from '../model/path'; /** @@ -28,7 +29,7 @@ export type AggregateType = 'count' | 'avg' | 'sum'; */ export interface Aggregate { readonly fieldPath?: FieldPath; - readonly alias: string; + readonly alias: AggregateAlias; readonly aggregateType: AggregateType; } @@ -37,7 +38,7 @@ export interface Aggregate { */ export class AggregateImpl implements Aggregate { constructor( - readonly alias: string, + readonly alias: AggregateAlias, readonly aggregateType: AggregateType, readonly fieldPath?: FieldPath ) {} diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 49e809c094e..0895d8f89c5 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -18,6 +18,7 @@ import { deepEqual } from '@firebase/util'; import { AggregateImpl } from '../core/aggregate'; +import { AggregateAlias } from '../model/aggregate_alias'; import { ObjectValue } from '../model/object_value'; import { invokeRunAggregationQueryRpc } from '../remote/datastore'; import { cast } from '../util/input_validation'; @@ -95,7 +96,7 @@ export function getAggregate( const internalAggregates = mapToArray(aggregateSpec, (aggregate, alias) => { return new AggregateImpl( - alias, + new AggregateAlias(alias), aggregate._aggregateType, aggregate._internalFieldPath ); diff --git a/packages/firestore/src/lite-api/aggregate_types.ts b/packages/firestore/src/lite-api/aggregate_types.ts index c087ea3ca2e..ab9619b58b4 100644 --- a/packages/firestore/src/lite-api/aggregate_types.ts +++ b/packages/firestore/src/lite-api/aggregate_types.ts @@ -65,18 +65,9 @@ export interface AggregateSpec { * from the input `AggregateSpec`. */ export type AggregateSpecData = { - [P in keyof T as TrimBackticks

]: T[P] extends AggregateField - ? U - : never; + [P in keyof T]: T[P] extends AggregateField ? U : never; }; -/** - * Removes enclosing backticks. - * type Foo = '`Foo`' - * type TrimmedFoo = TrimBackticks; // 'Foo' - */ -export type TrimBackticks = T extends `\`${infer Body}\`` ? Body : T; - /** * The results of executing an aggregation query. */ diff --git a/packages/firestore/src/model/aggregate_alias.ts b/packages/firestore/src/model/aggregate_alias.ts new file mode 100644 index 00000000000..2ac63fa9eb3 --- /dev/null +++ b/packages/firestore/src/model/aggregate_alias.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2022 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. + */ + +const aliasRegExp = /^[_a-zA-Z][_a-zA-Z0-9]*(?:\.[_a-zA-Z][_a-zA-Z0-9]*)*$/; + +/** + * An alias for aggregation results. + * @internal + */ +export class AggregateAlias { + /** + * @internal + * @param alias Un-escaped alias representation + */ + constructor(private alias: string) {} + + /** + * Returns true if the string could be used as an alias. + */ + private static isValidAlias(value: string): boolean { + return aliasRegExp.test(value); + } + + /** + * Return an escaped and quoted string representation of the alias. + */ + canonicalString(): string { + let alias = this.alias.replace(/\\/g, '\\\\').replace(/`/g, '\\`'); + if (!AggregateAlias.isValidAlias(alias)) { + alias = '`' + alias + '`'; + } + return alias; + } +} diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 8218d4b8217..5659db7658a 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -915,19 +915,19 @@ export function toRunAggregationQueryRequest( aggregates.forEach(aggregate => { if (aggregate.aggregateType === 'count') { aggregations.push({ - alias: aggregate.alias, + alias: aggregate.alias.canonicalString(), count: {} }); } else if (aggregate.aggregateType === 'avg') { aggregations.push({ - alias: aggregate.alias, + alias: aggregate.alias.canonicalString(), avg: { field: toFieldPathReference(aggregate.fieldPath!) } }); } else if (aggregate.aggregateType === 'sum') { aggregations.push({ - alias: aggregate.alias, + alias: aggregate.alias.canonicalString(), sum: { field: toFieldPathReference(aggregate.fieldPath!) } diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index aa443d245dd..c68ffc8e4af 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -174,20 +174,21 @@ apiDescribe('Aggregation queries', (persistence: boolean) => { }); }); - it('rejects bad aliases when using getAggregationFromServer unquoted', () => { + it('allows special chars in aliases when using getAggregationFromServer', () => { const testDocs = { a: { author: 'authorA', title: 'titleA' }, b: { author: 'authorB', title: 'titleB' } }; return withTestCollection(persistence, testDocs, async coll => { - const promise = getAggregateFromServer(coll, { - 'with-un/supp[or]ted': count() + const snapshot = await getAggregateFromServer(coll, { + 'with-un/su+pp[or]ted': count() }); - await expect(promise).to.eventually.rejectedWith(/invalid/i); + + expect(snapshot.data()['with-un/su+pp[or]ted']).to.equal(2); }); }); - it('allows special chars in quoted aliases when using getAggregationFromServer', () => { + it('allows backticks in aliases when using getAggregationFromServer', () => { const testDocs = { a: { author: 'authorA', title: 'titleA' }, b: { author: 'authorB', title: 'titleB' } @@ -197,22 +198,21 @@ apiDescribe('Aggregation queries', (persistence: boolean) => { '`with-un/su+pp[or]ted`': count() }); - // Note that the backticks (quotes) are removed from the alias - // in the returned object - expect(snapshot.data()['with-un/su+pp[or]ted']).to.equal(2); + expect(snapshot.data()['`with-un/su+pp[or]ted`']).to.equal(2); }); }); - it('rejects bad quoted aliases when using getAggregationFromServer', () => { + it('allows backslash in aliases when using getAggregationFromServer', () => { const testDocs = { a: { author: 'authorA', title: 'titleA' }, b: { author: 'authorB', title: 'titleB' } }; return withTestCollection(persistence, testDocs, async coll => { - const promise = getAggregateFromServer(coll, { - '`with`unsupported`': count() + const snapshot = await getAggregateFromServer(coll, { + 'with\\backshash\\es': count() }); - await expect(promise).to.eventually.rejectedWith(/invalid/i); + + expect(snapshot.data()['with\\backshash\\es']).to.equal(2); }); }); @@ -326,1175 +326,1172 @@ apiDescribe('Aggregation queries', (persistence: boolean) => { }); // TODO (sum/avg) enable these tests when sum/avg is supported by the backend -apiDescribe.skip( - 'Aggregation queries - sum / average', - (persistence: boolean) => { - it('can run sum query getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages') - }); - expect(snapshot.data().totalPages).to.equal(150); +apiDescribe('Aggregation queries - sum / average', (persistence: boolean) => { + it('can run sum query getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages') }); + expect(snapshot.data().totalPages).to.equal(150); }); + }); - it('can run average query getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averagePages: average('pages') - }); - expect(snapshot.data().averagePages).to.equal(75); + it('can run average query getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averagePages: average('pages') }); + expect(snapshot.data().averagePages).to.equal(75); }); + }); - it('can get multiple aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count() - }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().count).to.equal(2); + it('can get multiple aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count() }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().count).to.equal(2); }); + }); - it('can get duplicate aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - totalPagesX: sum('pages'), - averagePagesY: average('pages') - }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().totalPagesX).to.equal(150); - expect(snapshot.data().averagePagesY).to.equal(75); + it('can get duplicate aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + totalPagesX: sum('pages'), + averagePagesY: average('pages') }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().totalPagesX).to.equal(150); + expect(snapshot.data().averagePagesY).to.equal(75); }); + }); - it('can perform max (5) aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count(), - totalPagesX: sum('pages'), - averagePagesY: average('pages') - }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().count).to.equal(2); - expect(snapshot.data().totalPagesX).to.equal(150); - expect(snapshot.data().averagePagesY).to.equal(75); + it('can perform max (5) aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages') }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().count).to.equal(2); + expect(snapshot.data().totalPagesX).to.equal(150); + expect(snapshot.data().averagePagesY).to.equal(75); }); + }); - it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const promise = getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count(), - totalPagesX: sum('pages'), - averagePagesY: average('pages'), - countZ: count() - }); - - await expect(promise).to.eventually.be.rejectedWith( - /INVALID_ARGUMENT.*maximum number of aggregations/ - ); + it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const promise = getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages'), + countZ: count() }); + + await expect(promise).to.eventually.be.rejectedWith( + /INVALID_ARGUMENT.*maximum number of aggregations/ + ); }); + }); - it('aggregate query supports collection groups', () => { - return withTestDb(persistence, async db => { - const collectionGroupId = doc(collection(db, 'aggregateQueryTest')).id; - const docPaths = [ - `${collectionGroupId}/cg-doc1`, - `abc/123/${collectionGroupId}/cg-doc2`, - `zzz${collectionGroupId}/cg-doc3`, - `abc/123/zzz${collectionGroupId}/cg-doc4`, - `abc/123/zzz/${collectionGroupId}` - ]; - const batch = writeBatch(db); - for (const docPath of docPaths) { - batch.set(doc(db, docPath), { x: 2 }); + it('aggregate query supports collection groups', () => { + return withTestDb(persistence, async db => { + const collectionGroupId = doc(collection(db, 'aggregateQueryTest')).id; + const docPaths = [ + `${collectionGroupId}/cg-doc1`, + `abc/123/${collectionGroupId}/cg-doc2`, + `zzz${collectionGroupId}/cg-doc3`, + `abc/123/zzz${collectionGroupId}/cg-doc4`, + `abc/123/zzz/${collectionGroupId}` + ]; + const batch = writeBatch(db); + for (const docPath of docPaths) { + batch.set(doc(db, docPath), { x: 2 }); + } + await batch.commit(); + const snapshot = await getAggregateFromServer( + collectionGroup(db, collectionGroupId), + { + count: count(), + sum: sum('x'), + avg: average('x') } - await batch.commit(); - const snapshot = await getAggregateFromServer( - collectionGroup(db, collectionGroupId), - { - count: count(), - sum: sum('x'), - avg: average('x') - } - ); - expect(snapshot.data().count).to.equal(2); - expect(snapshot.data().sum).to.equal(4); - expect(snapshot.data().avg).to.equal(2); - }); + ); + expect(snapshot.data().count).to.equal(2); + expect(snapshot.data().sum).to.equal(4); + expect(snapshot.data().avg).to.equal(2); }); + }); - it('performs aggregations on documents with all aggregated fields using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, - b: { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, - c: { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, - d: { author: 'authorD', title: 'titleD', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - averageYear: average('year'), - count: count() - }); - expect(snapshot.data().totalPages).to.equal(300); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().averageYear).to.equal(2007); - expect(snapshot.data().count).to.equal(3); + it('performs aggregations on documents with all aggregated fields using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, + b: { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, + c: { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, + d: { author: 'authorD', title: 'titleD', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + averageYear: average('year'), + count: count() }); + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().averageYear).to.equal(2007); + expect(snapshot.data().count).to.equal(3); }); + }); - it('performs aggregates on multiple fields where one aggregate could cause short-circuit due to NaN using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: Number.NaN - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating'), - totalPages: sum('pages'), - averageYear: average('year') - }); - expect(snapshot.data().totalRating).to.be.NaN; - expect(snapshot.data().totalPages).to.equal(300); - expect(snapshot.data().averageYear).to.equal(2000); + it('performs aggregates on multiple fields where one aggregate could cause short-circuit due to NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating'), + totalPages: sum('pages'), + averageYear: average('year') }); + expect(snapshot.data().totalRating).to.be.NaN; + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averageYear).to.equal(2000); }); + }); - it('returns undefined when getting the result of an unrequested aggregation', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 + it('returns undefined when getting the result of an unrequested aggregation', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + totalRating: sum('rating'), + averageRating: average('rating') } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - totalRating: sum('rating'), - averageRating: average('rating') - } - ); + ); - // @ts-expect-error - const totalPages = snapshot.data().totalPages; - expect(totalPages).to.equal(undefined); - }); + // @ts-expect-error + const totalPages = snapshot.data().totalPages; + expect(totalPages).to.equal(undefined); }); + }); - it('performs aggregates when using `in` operator getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 + it('performs aggregates when using `in` operator getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('rating', 'in', [5, 3])), + { + totalRating: sum('rating'), + averageRating: average('rating'), + totalPages: sum('pages'), + averagePages: average('pages'), + countOfDocs: count() } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('rating', 'in', [5, 3])), - { - totalRating: sum('rating'), - averageRating: average('rating'), - totalPages: sum('pages'), - averagePages: average('pages'), - countOfDocs: count() - } - ); - expect(snapshot.data().totalRating).to.equal(8); - expect(snapshot.data().averageRating).to.equal(4); - expect(snapshot.data().totalPages).to.equal(200); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().countOfDocs).to.equal(2); - }); + ); + expect(snapshot.data().totalRating).to.equal(8); + expect(snapshot.data().averageRating).to.equal(4); + expect(snapshot.data().totalPages).to.equal(200); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().countOfDocs).to.equal(2); }); + }); - it('performs aggregates when using `array-contains-any` operator getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: [5, 1000] - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: [4] - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: [2222, 3] - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: [0] + it('performs aggregates when using `array-contains-any` operator getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: [5, 1000] + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: [4] + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: [2222, 3] + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: [0] + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('rating', 'array-contains-any', [5, 3])), + { + totalRating: sum('rating'), + averageRating: average('rating'), + totalPages: sum('pages'), + averagePages: average('pages'), + countOfDocs: count() } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('rating', 'array-contains-any', [5, 3])), - { - totalRating: sum('rating'), - averageRating: average('rating'), - totalPages: sum('pages'), - averagePages: average('pages'), - countOfDocs: count() - } - ); - expect(snapshot.data().totalRating).to.equal(0); - expect(snapshot.data().averageRating).to.be.null; - expect(snapshot.data().totalPages).to.equal(200); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().countOfDocs).to.equal(2); - }); + ); + expect(snapshot.data().totalRating).to.equal(0); + expect(snapshot.data().averageRating).to.be.null; + expect(snapshot.data().totalPages).to.equal(200); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().countOfDocs).to.equal(2); }); + }); - it('performs aggregations on nested map values using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - metadata: { pages: 100, rating: { critic: 2, user: 5 } } - }, - b: { - author: 'authorB', - title: 'titleB', - metadata: { pages: 50, rating: { critic: 4, user: 4 } } - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('metadata.pages'), - averagePages: average('metadata.pages'), - averageCriticRating: average('metadata.rating.critic'), - totalUserRating: sum('metadata.rating.user'), - count: count() - }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().averageCriticRating).to.equal(3); - expect(snapshot.data().totalUserRating).to.equal(9); - expect(snapshot.data().count).to.equal(2); + it('performs aggregations on nested map values using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + metadata: { pages: 100, rating: { critic: 2, user: 5 } } + }, + b: { + author: 'authorB', + title: 'titleB', + metadata: { pages: 50, rating: { critic: 4, user: 4 } } + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('metadata.pages'), + averagePages: average('metadata.pages'), + averageCriticRating: average('metadata.rating.critic'), + totalUserRating: sum('metadata.rating.user'), + count: count() }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().averageCriticRating).to.equal(3); + expect(snapshot.data().totalUserRating).to.equal(9); + expect(snapshot.data().count).to.equal(2); }); + }); - it('performs sum that results in float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4.5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 3 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(12.5); + it('performs sum that results in float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(12.5); }); + }); - it('performs sum of ints and floats that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4.5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 3.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(13); + it('performs sum of ints and floats that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(13); }); + }); - it('performs sum that overflows max int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_SAFE_INTEGER - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_SAFE_INTEGER - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal( - Number.MAX_SAFE_INTEGER + Number.MAX_SAFE_INTEGER - ); + it('performs sum that overflows max int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_SAFE_INTEGER + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal( + Number.MAX_SAFE_INTEGER + Number.MAX_SAFE_INTEGER + ); }); + }); - it('performs sum that can overflow integer values during accumulation using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_SAFE_INTEGER - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 1 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 50, - year: 2020, - rating: -101 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal( - Number.MAX_SAFE_INTEGER - 100 - ); + it('performs sum that can overflow integer values during accumulation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 1 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 50, + year: 2020, + rating: -101 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal( + Number.MAX_SAFE_INTEGER - 100 + ); }); + }); - it('performs sum that is negative using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_SAFE_INTEGER - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MIN_SAFE_INTEGER - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 50, - year: 2020, - rating: -101 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: -10000 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(-10101); + it('performs sum that is negative using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MIN_SAFE_INTEGER + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 50, + year: 2020, + rating: -101 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: -10000 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(-10101); }); + }); - it('performs sum that is positive infinity using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); + it('performs sum that is positive infinity using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); }); + }); - it('performs sum that is negative infinity using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: -Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: -Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(Number.NEGATIVE_INFINITY); + it('performs sum that is negative infinity using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(Number.NEGATIVE_INFINITY); }); + }); - it('performs sum that is valid but could overflow during aggregation using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: -Number.MAX_VALUE - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: -Number.MAX_VALUE - }, - e: { - author: 'authorE', - title: 'titleE', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - f: { - author: 'authorF', - title: 'titleF', - pages: 50, - year: 2020, - rating: -Number.MAX_VALUE - }, - g: { - author: 'authorG', - title: 'titleG', - pages: 100, - year: 1980, - rating: -Number.MAX_VALUE - }, - h: { - author: 'authorH', - title: 'titleDH', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(0); + it('performs sum that is valid but could overflow during aggregation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + }, + e: { + author: 'authorE', + title: 'titleE', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + f: { + author: 'authorF', + title: 'titleF', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + }, + g: { + author: 'authorG', + title: 'titleG', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + h: { + author: 'authorH', + title: 'titleDH', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(0); }); + }); - it('performs sum that includes NaN using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: Number.NaN - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.be.NaN; + it('performs sum that includes NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.be.NaN; }); + }); - it('performs sum over a result set of zero documents using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 + it('performs sum over a result set of zero documents using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + totalRating: sum('rating') } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - totalRating: sum('rating') - } - ); - expect(snapshot.data().totalRating).to.equal(0); - }); + ); + expect(snapshot.data().totalRating).to.equal(0); }); + }); - it('performs sum only on numeric fields using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: '3' - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 1 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating'), - countOfDocs: count() - }); - expect(snapshot.data().totalRating).to.equal(10); - expect(snapshot.data().countOfDocs).to.equal(4); + it('performs sum only on numeric fields using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: '3' + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 1 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating'), + countOfDocs: count() }); + expect(snapshot.data().totalRating).to.equal(10); + expect(snapshot.data().countOfDocs).to.equal(4); }); + }); - it('performs sum of min IEEE754 using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MIN_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(Number.MIN_VALUE); + it('performs sum of min IEEE754 using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(Number.MIN_VALUE); }); + }); - it('performs average of ints that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(5); + it('performs average of ints that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(5); }); + }); - it('performs average of floats that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10.5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(10); + it('performs average of floats that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(10); }); + }); - it('performs average of floats and ints that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9.5 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 150, - year: 2021, - rating: 10.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(10); + it('performs average of floats and ints that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9.5 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(10); }); + }); - it('performs average of float that results in float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5.5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4.5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 3.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(4.5); + it('performs average of float that results in float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(4.5); }); + }); - it('performs average of floats and ints that results in a float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10.5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 150, - year: 2021, - rating: 10 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(10); + it('performs average of floats and ints that results in a float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(10); }); + }); - it('performs average of ints that results in a float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 150, - year: 2021, - rating: 10 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(9.5); + it('performs average of ints that results in a float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(9.5); }); + }); - it('performs average causing underflow using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MIN_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(0); + it('performs average causing underflow using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(0); }); + }); - it('performs average of min IEEE754 using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MIN_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(Number.MIN_VALUE); + it('performs average of min IEEE754 using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(Number.MIN_VALUE); }); + }); - it('performs average that could overflow IEEE754 during accumulation using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(Number.MAX_VALUE); + it('performs average that could overflow IEEE754 during accumulation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(Number.MAX_VALUE); }); + }); - it('performs average that includes NaN using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: Number.NaN - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.be.NaN; + it('performs average that includes NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.be.NaN; }); + }); - it('performs average over a result set of zero documents using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 + it('performs average over a result set of zero documents using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + averageRating: average('rating') } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - averageRating: average('rating') - } - ); - expect(snapshot.data().averageRating).to.be.null; - }); + ); + expect(snapshot.data().averageRating).to.be.null; }); + }); - it('performs average only on numeric fields using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: '3' - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 6 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating'), - countOfDocs: count() - }); - expect(snapshot.data().averageRating).to.equal(5); - expect(snapshot.data().countOfDocs).to.equal(4); + it('performs average only on numeric fields using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: '3' + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 6 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating'), + countOfDocs: count() }); + expect(snapshot.data().averageRating).to.equal(5); + expect(snapshot.data().countOfDocs).to.equal(4); }); - } -); + }); +}); diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index bdda174cd1f..dcc9f46a508 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -2653,10 +2653,10 @@ describe('Aggregate queries', () => { it('aggregateQuerySnapshotEqual on different queries be falsy', () => { const testDocs = [ - { author: 'authorA', title: 'titleA', rating: 1 }, - { author: 'authorA', title: 'titleB', rating: 5 }, - { author: 'authorB', title: 'titleC', rating: 4 }, - { author: 'authorB', title: 'titleD', rating: 3 } + { author: 'authorA', title: 'titleA' }, + { author: 'authorA', title: 'titleB' }, + { author: 'authorB', title: 'titleC' }, + { author: 'authorB', title: 'titleD' } ]; return withTestCollectionAndInitialData(testDocs, async coll => { const query1 = query(coll, where('author', '==', 'authorA')); @@ -2729,6 +2729,9 @@ apiDescribe.skip('Aggregation queries - sum / average', () => { const snapshot1 = await getAggregate(query1, { sum: sum('rating') }); const snapshot2 = await getAggregate(query1, { avg: average('rating') }); + // `snapshot1` and `snapshot2` have different types and therefore the + // following use of `aggregateQuerySnapshotEqual(...)` will cause a + // TS error. To test the method for JS users, we ignore the TS error. // @ts-expect-error expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.false; }); @@ -2746,6 +2749,9 @@ apiDescribe.skip('Aggregation queries - sum / average', () => { const snapshot1 = await getAggregate(query1, { foo: average('rating') }); const snapshot2 = await getAggregate(query1, { bar: average('rating') }); + // `snapshot1` and `snapshot2` have different types and therefore the + // following use of `aggregateQuerySnapshotEqual(...)` will cause a + // TS error. To test the method for JS users, we ignore the TS error. // @ts-expect-error expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.false; }); @@ -2760,8 +2766,14 @@ apiDescribe.skip('Aggregation queries - sum / average', () => { ]; return withTestCollectionAndInitialData(testDocs, async coll => { const query1 = query(coll, where('author', '==', 'authorA')); - const snapshot1 = await getAggregate(query1, { foo: average('rating') }); - const snapshot2 = await getAggregate(query1, { foo: average('rating') }); + const snapshot1 = await getAggregate(query1, { + foo: average('rating'), + bar: sum('rating') + }); + const snapshot2 = await getAggregate(query1, { + bar: sum('rating'), + foo: average('rating') + }); expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.true; }); From 05385c36dbb231e99e4c902df3782678cc03db63 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:48:43 -0700 Subject: [PATCH 7/9] More test updates. --- .../test/integration/api/aggregation.test.ts | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index c68ffc8e4af..c22153096da 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -888,6 +888,31 @@ apiDescribe('Aggregation queries - sum / average', (persistence: boolean) => { }); }); + it('performs sum that is positive infinity using getAggregationFromServer v2', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 1e293 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); + }); + }); + it('performs sum that is negative infinity using getAggregationFromServer', () => { const testDocs = { a: { @@ -1248,7 +1273,7 @@ apiDescribe('Aggregation queries - sum / average', (persistence: boolean) => { title: 'titleA', pages: 100, year: 1980, - rating: 10.5 + rating: 8.6 }, b: { author: 'authorB', @@ -1269,7 +1294,7 @@ apiDescribe('Aggregation queries - sum / average', (persistence: boolean) => { const snapshot = await getAggregateFromServer(coll, { averageRating: average('rating') }); - expect(snapshot.data().averageRating).to.equal(10); + expect(snapshot.data().averageRating).to.equal(9.2); }); }); @@ -1288,13 +1313,6 @@ apiDescribe('Aggregation queries - sum / average', (persistence: boolean) => { pages: 50, year: 2020, rating: 9 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 150, - year: 2021, - rating: 10 } }; return withTestCollection(persistence, testDocs, async coll => { From 106df3fc4456d72dd08f80929ffa511f481a8829 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:49:47 -0700 Subject: [PATCH 8/9] Skipping sum avg tests until backend is ready. --- packages/firestore/test/integration/api/aggregation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index c22153096da..de52259c4c2 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -326,7 +326,7 @@ apiDescribe('Aggregation queries', (persistence: boolean) => { }); // TODO (sum/avg) enable these tests when sum/avg is supported by the backend -apiDescribe('Aggregation queries - sum / average', (persistence: boolean) => { +apiDescribe.skip('Aggregation queries - sum / average', (persistence: boolean) => { it('can run sum query getAggregationFromServer', () => { const testDocs = { a: { author: 'authorA', title: 'titleA', pages: 100 }, From e6a5c16bfa294dbb2f1f4f3212108ea1cab35cb8 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:53:14 -0700 Subject: [PATCH 9/9] Prettier --- .../test/integration/api/aggregation.test.ts | 2149 +++++++++-------- 1 file changed, 1076 insertions(+), 1073 deletions(-) diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index de52259c4c2..907934c5cfa 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -326,1190 +326,1193 @@ apiDescribe('Aggregation queries', (persistence: boolean) => { }); // TODO (sum/avg) enable these tests when sum/avg is supported by the backend -apiDescribe.skip('Aggregation queries - sum / average', (persistence: boolean) => { - it('can run sum query getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages') +apiDescribe.skip( + 'Aggregation queries - sum / average', + (persistence: boolean) => { + it('can run sum query getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages') + }); + expect(snapshot.data().totalPages).to.equal(150); }); - expect(snapshot.data().totalPages).to.equal(150); }); - }); - it('can run average query getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averagePages: average('pages') + it('can run average query getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averagePages: average('pages') + }); + expect(snapshot.data().averagePages).to.equal(75); }); - expect(snapshot.data().averagePages).to.equal(75); }); - }); - it('can get multiple aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count() + it('can get multiple aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count() + }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().count).to.equal(2); }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().count).to.equal(2); }); - }); - it('can get duplicate aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - totalPagesX: sum('pages'), - averagePagesY: average('pages') + it('can get duplicate aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + totalPagesX: sum('pages'), + averagePagesY: average('pages') + }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().totalPagesX).to.equal(150); + expect(snapshot.data().averagePagesY).to.equal(75); }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().totalPagesX).to.equal(150); - expect(snapshot.data().averagePagesY).to.equal(75); }); - }); - it('can perform max (5) aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count(), - totalPagesX: sum('pages'), - averagePagesY: average('pages') + it('can perform max (5) aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages') + }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().count).to.equal(2); + expect(snapshot.data().totalPagesX).to.equal(150); + expect(snapshot.data().averagePagesY).to.equal(75); }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().count).to.equal(2); - expect(snapshot.data().totalPagesX).to.equal(150); - expect(snapshot.data().averagePagesY).to.equal(75); }); - }); - it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const promise = getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count(), - totalPagesX: sum('pages'), - averagePagesY: average('pages'), - countZ: count() - }); + it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const promise = getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages'), + countZ: count() + }); - await expect(promise).to.eventually.be.rejectedWith( - /INVALID_ARGUMENT.*maximum number of aggregations/ - ); + await expect(promise).to.eventually.be.rejectedWith( + /INVALID_ARGUMENT.*maximum number of aggregations/ + ); + }); }); - }); - it('aggregate query supports collection groups', () => { - return withTestDb(persistence, async db => { - const collectionGroupId = doc(collection(db, 'aggregateQueryTest')).id; - const docPaths = [ - `${collectionGroupId}/cg-doc1`, - `abc/123/${collectionGroupId}/cg-doc2`, - `zzz${collectionGroupId}/cg-doc3`, - `abc/123/zzz${collectionGroupId}/cg-doc4`, - `abc/123/zzz/${collectionGroupId}` - ]; - const batch = writeBatch(db); - for (const docPath of docPaths) { - batch.set(doc(db, docPath), { x: 2 }); - } - await batch.commit(); - const snapshot = await getAggregateFromServer( - collectionGroup(db, collectionGroupId), - { - count: count(), - sum: sum('x'), - avg: average('x') + it('aggregate query supports collection groups', () => { + return withTestDb(persistence, async db => { + const collectionGroupId = doc(collection(db, 'aggregateQueryTest')).id; + const docPaths = [ + `${collectionGroupId}/cg-doc1`, + `abc/123/${collectionGroupId}/cg-doc2`, + `zzz${collectionGroupId}/cg-doc3`, + `abc/123/zzz${collectionGroupId}/cg-doc4`, + `abc/123/zzz/${collectionGroupId}` + ]; + const batch = writeBatch(db); + for (const docPath of docPaths) { + batch.set(doc(db, docPath), { x: 2 }); } - ); - expect(snapshot.data().count).to.equal(2); - expect(snapshot.data().sum).to.equal(4); - expect(snapshot.data().avg).to.equal(2); + await batch.commit(); + const snapshot = await getAggregateFromServer( + collectionGroup(db, collectionGroupId), + { + count: count(), + sum: sum('x'), + avg: average('x') + } + ); + expect(snapshot.data().count).to.equal(2); + expect(snapshot.data().sum).to.equal(4); + expect(snapshot.data().avg).to.equal(2); + }); }); - }); - it('performs aggregations on documents with all aggregated fields using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, - b: { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, - c: { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, - d: { author: 'authorD', title: 'titleD', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - averageYear: average('year'), - count: count() + it('performs aggregations on documents with all aggregated fields using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, + b: { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, + c: { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, + d: { author: 'authorD', title: 'titleD', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + averageYear: average('year'), + count: count() + }); + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().averageYear).to.equal(2007); + expect(snapshot.data().count).to.equal(3); }); - expect(snapshot.data().totalPages).to.equal(300); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().averageYear).to.equal(2007); - expect(snapshot.data().count).to.equal(3); }); - }); - it('performs aggregates on multiple fields where one aggregate could cause short-circuit due to NaN using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: Number.NaN - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating'), - totalPages: sum('pages'), - averageYear: average('year') + it('performs aggregates on multiple fields where one aggregate could cause short-circuit due to NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating'), + totalPages: sum('pages'), + averageYear: average('year') + }); + expect(snapshot.data().totalRating).to.be.NaN; + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averageYear).to.equal(2000); }); - expect(snapshot.data().totalRating).to.be.NaN; - expect(snapshot.data().totalPages).to.equal(300); - expect(snapshot.data().averageYear).to.equal(2000); }); - }); - it('returns undefined when getting the result of an unrequested aggregation', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - totalRating: sum('rating'), - averageRating: average('rating') + it('returns undefined when getting the result of an unrequested aggregation', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 } - ); + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + totalRating: sum('rating'), + averageRating: average('rating') + } + ); - // @ts-expect-error - const totalPages = snapshot.data().totalPages; - expect(totalPages).to.equal(undefined); + // @ts-expect-error + const totalPages = snapshot.data().totalPages; + expect(totalPages).to.equal(undefined); + }); }); - }); - it('performs aggregates when using `in` operator getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('rating', 'in', [5, 3])), - { - totalRating: sum('rating'), - averageRating: average('rating'), - totalPages: sum('pages'), - averagePages: average('pages'), - countOfDocs: count() + it('performs aggregates when using `in` operator getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 } - ); - expect(snapshot.data().totalRating).to.equal(8); - expect(snapshot.data().averageRating).to.equal(4); - expect(snapshot.data().totalPages).to.equal(200); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().countOfDocs).to.equal(2); + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('rating', 'in', [5, 3])), + { + totalRating: sum('rating'), + averageRating: average('rating'), + totalPages: sum('pages'), + averagePages: average('pages'), + countOfDocs: count() + } + ); + expect(snapshot.data().totalRating).to.equal(8); + expect(snapshot.data().averageRating).to.equal(4); + expect(snapshot.data().totalPages).to.equal(200); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().countOfDocs).to.equal(2); + }); }); - }); - it('performs aggregates when using `array-contains-any` operator getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: [5, 1000] - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: [4] - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: [2222, 3] - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: [0] - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('rating', 'array-contains-any', [5, 3])), - { - totalRating: sum('rating'), - averageRating: average('rating'), - totalPages: sum('pages'), - averagePages: average('pages'), - countOfDocs: count() + it('performs aggregates when using `array-contains-any` operator getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: [5, 1000] + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: [4] + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: [2222, 3] + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: [0] } - ); - expect(snapshot.data().totalRating).to.equal(0); - expect(snapshot.data().averageRating).to.be.null; - expect(snapshot.data().totalPages).to.equal(200); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().countOfDocs).to.equal(2); + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('rating', 'array-contains-any', [5, 3])), + { + totalRating: sum('rating'), + averageRating: average('rating'), + totalPages: sum('pages'), + averagePages: average('pages'), + countOfDocs: count() + } + ); + expect(snapshot.data().totalRating).to.equal(0); + expect(snapshot.data().averageRating).to.be.null; + expect(snapshot.data().totalPages).to.equal(200); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().countOfDocs).to.equal(2); + }); }); - }); - it('performs aggregations on nested map values using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - metadata: { pages: 100, rating: { critic: 2, user: 5 } } - }, - b: { - author: 'authorB', - title: 'titleB', - metadata: { pages: 50, rating: { critic: 4, user: 4 } } - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('metadata.pages'), - averagePages: average('metadata.pages'), - averageCriticRating: average('metadata.rating.critic'), - totalUserRating: sum('metadata.rating.user'), - count: count() + it('performs aggregations on nested map values using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + metadata: { pages: 100, rating: { critic: 2, user: 5 } } + }, + b: { + author: 'authorB', + title: 'titleB', + metadata: { pages: 50, rating: { critic: 4, user: 4 } } + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('metadata.pages'), + averagePages: average('metadata.pages'), + averageCriticRating: average('metadata.rating.critic'), + totalUserRating: sum('metadata.rating.user'), + count: count() + }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().averageCriticRating).to.equal(3); + expect(snapshot.data().totalUserRating).to.equal(9); + expect(snapshot.data().count).to.equal(2); }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().averageCriticRating).to.equal(3); - expect(snapshot.data().totalUserRating).to.equal(9); - expect(snapshot.data().count).to.equal(2); }); - }); - it('performs sum that results in float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4.5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 3 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum that results in float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(12.5); }); - expect(snapshot.data().totalRating).to.equal(12.5); }); - }); - it('performs sum of ints and floats that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4.5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 3.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum of ints and floats that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(13); }); - expect(snapshot.data().totalRating).to.equal(13); }); - }); - it('performs sum that overflows max int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_SAFE_INTEGER - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_SAFE_INTEGER - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum that overflows max int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_SAFE_INTEGER + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal( + Number.MAX_SAFE_INTEGER + Number.MAX_SAFE_INTEGER + ); }); - expect(snapshot.data().totalRating).to.equal( - Number.MAX_SAFE_INTEGER + Number.MAX_SAFE_INTEGER - ); }); - }); - it('performs sum that can overflow integer values during accumulation using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_SAFE_INTEGER - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 1 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 50, - year: 2020, - rating: -101 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum that can overflow integer values during accumulation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 1 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 50, + year: 2020, + rating: -101 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal( + Number.MAX_SAFE_INTEGER - 100 + ); }); - expect(snapshot.data().totalRating).to.equal( - Number.MAX_SAFE_INTEGER - 100 - ); }); - }); - it('performs sum that is negative using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_SAFE_INTEGER - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MIN_SAFE_INTEGER - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 50, - year: 2020, - rating: -101 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: -10000 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum that is negative using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MIN_SAFE_INTEGER + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 50, + year: 2020, + rating: -101 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: -10000 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(-10101); }); - expect(snapshot.data().totalRating).to.equal(-10101); }); - }); - it('performs sum that is positive infinity using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum that is positive infinity using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); }); - expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); }); - }); - it('performs sum that is positive infinity using getAggregationFromServer v2', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 1e293 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum that is positive infinity using getAggregationFromServer v2', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 1e293 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); }); - expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); }); - }); - it('performs sum that is negative infinity using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: -Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: -Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum that is negative infinity using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(Number.NEGATIVE_INFINITY); }); - expect(snapshot.data().totalRating).to.equal(Number.NEGATIVE_INFINITY); }); - }); - it('performs sum that is valid but could overflow during aggregation using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: -Number.MAX_VALUE - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: -Number.MAX_VALUE - }, - e: { - author: 'authorE', - title: 'titleE', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - f: { - author: 'authorF', - title: 'titleF', - pages: 50, - year: 2020, - rating: -Number.MAX_VALUE - }, - g: { - author: 'authorG', - title: 'titleG', - pages: 100, - year: 1980, - rating: -Number.MAX_VALUE - }, - h: { - author: 'authorH', - title: 'titleDH', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum that is valid but could overflow during aggregation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + }, + e: { + author: 'authorE', + title: 'titleE', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + f: { + author: 'authorF', + title: 'titleF', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + }, + g: { + author: 'authorG', + title: 'titleG', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + h: { + author: 'authorH', + title: 'titleDH', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(0); }); - expect(snapshot.data().totalRating).to.equal(0); }); - }); - it('performs sum that includes NaN using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: Number.NaN - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum that includes NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.be.NaN; }); - expect(snapshot.data().totalRating).to.be.NaN; }); - }); - it('performs sum over a result set of zero documents using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - totalRating: sum('rating') + it('performs sum over a result set of zero documents using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 } - ); - expect(snapshot.data().totalRating).to.equal(0); + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + totalRating: sum('rating') + } + ); + expect(snapshot.data().totalRating).to.equal(0); + }); }); - }); - it('performs sum only on numeric fields using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: '3' - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 1 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating'), - countOfDocs: count() + it('performs sum only on numeric fields using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: '3' + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 1 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating'), + countOfDocs: count() + }); + expect(snapshot.data().totalRating).to.equal(10); + expect(snapshot.data().countOfDocs).to.equal(4); }); - expect(snapshot.data().totalRating).to.equal(10); - expect(snapshot.data().countOfDocs).to.equal(4); }); - }); - it('performs sum of min IEEE754 using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MIN_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') + it('performs sum of min IEEE754 using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') + }); + expect(snapshot.data().totalRating).to.equal(Number.MIN_VALUE); }); - expect(snapshot.data().totalRating).to.equal(Number.MIN_VALUE); }); - }); - it('performs average of ints that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average of ints that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(5); }); - expect(snapshot.data().averageRating).to.equal(5); }); - }); - it('performs average of floats that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10.5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average of floats that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(10); }); - expect(snapshot.data().averageRating).to.equal(10); }); - }); - it('performs average of floats and ints that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9.5 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 150, - year: 2021, - rating: 10.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average of floats and ints that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9.5 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(10); }); - expect(snapshot.data().averageRating).to.equal(10); }); - }); - it('performs average of float that results in float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5.5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4.5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 3.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average of float that results in float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(4.5); }); - expect(snapshot.data().averageRating).to.equal(4.5); }); - }); - it('performs average of floats and ints that results in a float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 8.6 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 150, - year: 2021, - rating: 10 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average of floats and ints that results in a float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 8.6 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(9.2); }); - expect(snapshot.data().averageRating).to.equal(9.2); }); - }); - it('performs average of ints that results in a float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average of ints that results in a float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(9.5); }); - expect(snapshot.data().averageRating).to.equal(9.5); }); - }); - it('performs average causing underflow using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MIN_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average causing underflow using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(0); }); - expect(snapshot.data().averageRating).to.equal(0); }); - }); - it('performs average of min IEEE754 using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MIN_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average of min IEEE754 using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(Number.MIN_VALUE); }); - expect(snapshot.data().averageRating).to.equal(Number.MIN_VALUE); }); - }); - it('performs average that could overflow IEEE754 during accumulation using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average that could overflow IEEE754 during accumulation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.equal(Number.MAX_VALUE); }); - expect(snapshot.data().averageRating).to.equal(Number.MAX_VALUE); }); - }); - it('performs average that includes NaN using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: Number.NaN - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') + it('performs average that includes NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') + }); + expect(snapshot.data().averageRating).to.be.NaN; }); - expect(snapshot.data().averageRating).to.be.NaN; }); - }); - it('performs average over a result set of zero documents using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - averageRating: average('rating') + it('performs average over a result set of zero documents using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 } - ); - expect(snapshot.data().averageRating).to.be.null; + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + averageRating: average('rating') + } + ); + expect(snapshot.data().averageRating).to.be.null; + }); }); - }); - it('performs average only on numeric fields using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: '3' - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 6 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating'), - countOfDocs: count() + it('performs average only on numeric fields using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: '3' + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 6 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating'), + countOfDocs: count() + }); + expect(snapshot.data().averageRating).to.equal(5); + expect(snapshot.data().countOfDocs).to.equal(4); }); - expect(snapshot.data().averageRating).to.equal(5); - expect(snapshot.data().countOfDocs).to.equal(4); }); - }); -}); + } +);