diff --git a/.changeset/shiny-forks-pull.md b/.changeset/shiny-forks-pull.md new file mode 100644 index 00000000000..76d7cdbcafc --- /dev/null +++ b/.changeset/shiny-forks-pull.md @@ -0,0 +1,7 @@ +--- +'firebase': minor +'@firebase/firestore': minor +'@firebase/firestore-types': minor +--- + +[feature] Added `not-in` and `!=` query operators for use with `.where()`. `not-in` finds documents where a specified field’s value is not in a specified array. `!=` finds documents where a specified field's value does not equal the specified value. Neither query operator will match documents where the specified field is not present. diff --git a/packages/firebase/index.d.ts b/packages/firebase/index.d.ts index f907504dbbd..7732e440c83 100644 --- a/packages/firebase/index.d.ts +++ b/packages/firebase/index.d.ts @@ -9073,17 +9073,20 @@ declare namespace firebase.firestore { /** * Filter conditions in a `Query.where()` clause are specified using the - * strings '<', '<=', '==', '>=', '>', 'array-contains', 'in', and 'array-contains-any'. + * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', + * 'array-contains-any', and 'not-in'. */ export type WhereFilterOp = | '<' | '<=' | '==' + | '!=' | '>=' | '>' | 'array-contains' | 'in' - | 'array-contains-any'; + | 'array-contains-any' + | 'not-in'; /** * A `Query` refers to a Query which you can read or listen to. You can also diff --git a/packages/firestore-types/index.d.ts b/packages/firestore-types/index.d.ts index 3fac06d74f0..75ae5d4ef62 100644 --- a/packages/firestore-types/index.d.ts +++ b/packages/firestore-types/index.d.ts @@ -294,11 +294,13 @@ export type WhereFilterOp = | '<' | '<=' | '==' + | '!=' | '>=' | '>' | 'array-contains' | 'in' - | 'array-contains-any'; + | 'array-contains-any' + | 'not-in'; export class Query { protected constructor(); diff --git a/packages/firestore/exp-types/index.d.ts b/packages/firestore/exp-types/index.d.ts index f14d6803b94..13dffca924b 100644 --- a/packages/firestore/exp-types/index.d.ts +++ b/packages/firestore/exp-types/index.d.ts @@ -277,11 +277,13 @@ export type WhereFilterOp = | '<' | '<=' | '==' + | '!=' | '>=' | '>' | 'array-contains' | 'in' - | 'array-contains-any'; + | 'array-contains-any' + | 'not-in'; export class Query { protected constructor(); diff --git a/packages/firestore/lite-types/index.d.ts b/packages/firestore/lite-types/index.d.ts index 1a7bb12e411..be4e6f84846 100644 --- a/packages/firestore/lite-types/index.d.ts +++ b/packages/firestore/lite-types/index.d.ts @@ -227,11 +227,13 @@ export type WhereFilterOp = | '<' | '<=' | '==' + | '!=' | '>=' | '>' | 'array-contains' | 'in' - | 'array-contains-any'; + | 'array-contains-any' + | 'not-in'; export class Query { protected constructor(); diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index df9d16ad5ea..7851d0f42a7 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -1882,24 +1882,20 @@ export class Query implements PublicQuery { validateExactNumberOfArgs('Query.where', arguments, 3); validateDefined('Query.where', 3, value); - // TODO(ne-queries): Add 'not-in' and '!=' to validation. - let op: Operator; - if ((opStr as unknown) === 'not-in' || (opStr as unknown) === '!=') { - op = opStr as Operator; - } else { - // Enumerated from the WhereFilterOp type in index.d.ts. - const whereFilterOpEnums = [ - Operator.LESS_THAN, - Operator.LESS_THAN_OR_EQUAL, - Operator.EQUAL, - Operator.GREATER_THAN_OR_EQUAL, - Operator.GREATER_THAN, - Operator.ARRAY_CONTAINS, - Operator.IN, - Operator.ARRAY_CONTAINS_ANY - ]; - op = validateStringEnum('Query.where', whereFilterOpEnums, 2, opStr); - } + // Enumerated from the WhereFilterOp type in index.d.ts. + const whereFilterOpEnums = [ + Operator.LESS_THAN, + Operator.LESS_THAN_OR_EQUAL, + Operator.EQUAL, + Operator.NOT_EQUAL, + Operator.GREATER_THAN_OR_EQUAL, + Operator.GREATER_THAN, + Operator.ARRAY_CONTAINS, + Operator.IN, + Operator.ARRAY_CONTAINS_ANY, + Operator.NOT_IN + ]; + const op = validateStringEnum('Query.where', whereFilterOpEnums, 2, opStr); const fieldPath = fieldPathFromArgument('Query.where', field); const filter = newQueryFilter( diff --git a/packages/firestore/src/core/query.ts b/packages/firestore/src/core/query.ts index 0b4e217c53d..07a07c51bc3 100644 --- a/packages/firestore/src/core/query.ts +++ b/packages/firestore/src/core/query.ts @@ -606,19 +606,17 @@ export class FieldFilter extends Filter { } } else if (isNullValue(value)) { if (op !== Operator.EQUAL && op !== Operator.NOT_EQUAL) { - // TODO(ne-queries): Update error message to include != comparison. throw new FirestoreError( Code.INVALID_ARGUMENT, - 'Invalid query. Null supports only equality comparisons.' + "Invalid query. Null only supports '==' and '!=' comparisons." ); } return new FieldFilter(field, op, value); } else if (isNanValue(value)) { if (op !== Operator.EQUAL && op !== Operator.NOT_EQUAL) { - // TODO(ne-queries): Update error message to include != comparison. throw new FirestoreError( Code.INVALID_ARGUMENT, - 'Invalid query. NaN supports only equality comparisons.' + "Invalid query. NaN only supports '==' and '!=' comparisons." ); } return new FieldFilter(field, op, value); @@ -712,7 +710,8 @@ export class FieldFilter extends Filter { Operator.LESS_THAN_OR_EQUAL, Operator.GREATER_THAN, Operator.GREATER_THAN_OR_EQUAL, - Operator.NOT_EQUAL + Operator.NOT_EQUAL, + Operator.NOT_IN ].indexOf(this.op) >= 0 ); } diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index aa549b6fe64..abf591b6b47 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -25,8 +25,6 @@ import { EventsAccumulator } from '../util/events_accumulator'; import * as firebaseExport from '../util/firebase_export'; import { apiDescribe, - notEqualOp, - notInOp, withTestCollection, withTestDb, withTestDbs, @@ -644,14 +642,6 @@ apiDescribe('Database', (persistence: boolean) => { }); }); - it('inequality and NOT_IN on different fields works', () => { - return withTestCollection(persistence, {}, async coll => { - expect(() => - coll.where('x', '>=', 32).where('y', notInOp, [1, 2]) - ).not.to.throw(); - }); - }); - it('inequality and array-contains-any on different fields works', () => { return withTestCollection(persistence, {}, async coll => { expect(() => @@ -669,12 +659,8 @@ apiDescribe('Database', (persistence: boolean) => { it('!= same as orderBy works.', () => { return withTestCollection(persistence, {}, async coll => { - expect(() => - coll.where('x', notEqualOp, 32).orderBy('x') - ).not.to.throw(); - expect(() => - coll.orderBy('x').where('x', notEqualOp, 32) - ).not.to.throw(); + expect(() => coll.where('x', '!=', 32).orderBy('x')).not.to.throw(); + expect(() => coll.orderBy('x').where('x', '!=', 32)).not.to.throw(); }); }); @@ -692,10 +678,10 @@ apiDescribe('Database', (persistence: boolean) => { it('!= same as first orderBy works.', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.where('x', notEqualOp, 32).orderBy('x').orderBy('y') + coll.where('x', '!=', 32).orderBy('x').orderBy('y') ).not.to.throw(); expect(() => - coll.orderBy('x').where('x', notEqualOp, 32).orderBy('y') + coll.orderBy('x').where('x', '!=', 32).orderBy('y') ).not.to.throw(); }); }); @@ -720,14 +706,6 @@ apiDescribe('Database', (persistence: boolean) => { }); }); - it('NOT_IN different than orderBy works', () => { - return withTestCollection(persistence, {}, async coll => { - expect(() => - coll.orderBy('x').where('y', notInOp, [1, 2]) - ).not.to.throw(); - }); - }); - it('array-contains-any different than orderBy works', () => { return withTestCollection(persistence, {}, async coll => { expect(() => diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index b86bffb6462..4e7a2c1543e 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -24,8 +24,6 @@ import { EventsAccumulator } from '../util/events_accumulator'; import * as firebaseExport from '../util/firebase_export'; import { apiDescribe, - notEqualOp, - notInOp, toChangesArray, toDataArray, withTestCollection, @@ -687,60 +685,52 @@ apiDescribe('Queries', (persistence: boolean) => { }); it('can use != filters', async () => { + // These documents are ordered by value in "zip" since the '!=' filter is + // an inequality, which results in documents being sorted by value. const testDocs = { - a: { zip: 98101 }, + a: { zip: Number.NaN }, b: { zip: 91102 }, - c: { zip: '98101' }, - d: { zip: [98101] }, - e: { zip: ['98101', { zip: 98101 }] }, - f: { zip: { code: 500 } }, - g: { zip: [98101, 98102] }, - h: { code: 500 }, - i: { zip: null }, - j: { zip: Number.NaN } + c: { zip: 98101 }, + d: { zip: '98101' }, + e: { zip: [98101] }, + f: { zip: [98101, 98102] }, + g: { zip: ['98101', { zip: 98101 }] }, + h: { zip: { code: 500 } }, + i: { code: 500 }, + j: { zip: null } }; await withTestCollection(persistence, testDocs, async coll => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let expected: { [name: string]: any } = { ...testDocs }; - delete expected.a; - delete expected.h; + delete expected.c; delete expected.i; - const snapshot = await coll.where('zip', notEqualOp, 98101).get(); - expect(toDataArray(snapshot)).to.have.deep.members( - Object.values(expected) - ); + delete expected.j; + const snapshot = await coll.where('zip', '!=', 98101).get(); + expect(toDataArray(snapshot)).to.deep.equal(Object.values(expected)); // With objects. - const snapshot2 = await coll - .where('zip', notEqualOp, { code: 500 }) - .get(); + const snapshot2 = await coll.where('zip', '!=', { code: 500 }).get(); expected = { ...testDocs }; - delete expected.f; delete expected.h; delete expected.i; - expect(toDataArray(snapshot2)).to.have.deep.members( - Object.values(expected) - ); + delete expected.j; + expect(toDataArray(snapshot2)).to.deep.equal(Object.values(expected)); // With null. - const snapshot3 = await coll.where('zip', notEqualOp, null).get(); + const snapshot3 = await coll.where('zip', '!=', null).get(); expected = { ...testDocs }; - delete expected.h; delete expected.i; - expect(toDataArray(snapshot3)).to.have.deep.members( - Object.values(expected) - ); + delete expected.j; + expect(toDataArray(snapshot3)).to.deep.equal(Object.values(expected)); // With NaN. - const snapshot4 = await coll.where('zip', notEqualOp, Number.NaN).get(); + const snapshot4 = await coll.where('zip', '!=', Number.NaN).get(); expected = { ...testDocs }; - delete expected.h; + delete expected.a; delete expected.i; delete expected.j; - expect(toDataArray(snapshot4)).to.have.deep.members( - Object.values(expected) - ); + expect(toDataArray(snapshot4)).to.deep.equal(Object.values(expected)); }); }); @@ -753,7 +743,7 @@ apiDescribe('Queries', (persistence: boolean) => { }; await withTestCollection(persistence, testDocs, async coll => { const snapshot = await coll - .where(FieldPath.documentId(), notEqualOp, 'aa') + .where(FieldPath.documentId(), '!=', 'aa') .get(); expect(toDataArray(snapshot)).to.deep.equal([ @@ -832,59 +822,65 @@ apiDescribe('Queries', (persistence: boolean) => { }); }); - // TODO(ne-queries): re-enable in next PR to make public. - // eslint-disable-next-line no-restricted-properties - it.skip('can use NOT_IN filters', async () => { + it('can use NOT_IN filters', async () => { + // These documents are ordered by value in "zip" since the 'not-in' filter is + // an inequality, which results in documents being sorted by value. const testDocs = { - a: { zip: 98101 }, + a: { zip: Number.NaN }, b: { zip: 91102 }, - c: { zip: 98103 }, - d: { zip: [98101] }, - e: { zip: ['98101', { zip: 98101 }] }, - f: { zip: { code: 500 } }, - g: { zip: [98101, 98102] }, - h: { code: 500 }, - i: { zip: null }, - j: { zip: Number.NaN } + c: { zip: 98101 }, + d: { zip: 98103 }, + e: { zip: [98101] }, + f: { zip: [98101, 98102] }, + g: { zip: ['98101', { zip: 98101 }] }, + h: { zip: { code: 500 } }, + i: { code: 500 }, + j: { zip: null } }; await withTestCollection(persistence, testDocs, async coll => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let expected: { [name: string]: any } = { ...testDocs }; - delete expected.a; delete expected.c; - delete expected.g; - delete expected.h; + delete expected.d; + delete expected.f; + delete expected.i; + delete expected.j; const snapshot = await coll - .where('zip', notInOp, [98101, 98103, [98101, 98102]]) + .where('zip', 'not-in', [98101, 98103, [98101, 98102]]) .get(); expect(toDataArray(snapshot)).to.deep.equal(Object.values(expected)); // With objects. - const snapshot2 = await coll.where('zip', notInOp, [{ code: 500 }]).get(); + const snapshot2 = await coll + .where('zip', 'not-in', [{ code: 500 }]) + .get(); expected = { ...testDocs }; - delete expected.f; delete expected.h; + delete expected.i; + delete expected.j; expect(toDataArray(snapshot2)).to.deep.equal(Object.values(expected)); // With null. - const snapshot3 = await coll.where('zip', notInOp, [null]).get(); + const snapshot3 = await coll.where('zip', 'not-in', [null]).get(); expect(toDataArray(snapshot3)).to.deep.equal([]); // With NaN. - const snapshot4 = await coll.where('zip', notInOp, [Number.NaN]).get(); + const snapshot4 = await coll.where('zip', 'not-in', [Number.NaN]).get(); expected = { ...testDocs }; - delete expected.h; + delete expected.a; + delete expected.i; delete expected.j; expect(toDataArray(snapshot4)).to.deep.equal(Object.values(expected)); // With NaN and a number. const snapshot5 = await coll - .where('zip', notInOp, [Number.NaN, 98101]) + .where('zip', 'not-in', [Number.NaN, 98101]) .get(); expected = { ...testDocs }; delete expected.a; - delete expected.h; + delete expected.c; + delete expected.i; delete expected.j; expect(toDataArray(snapshot5)).to.deep.equal(Object.values(expected)); }); @@ -899,7 +895,7 @@ apiDescribe('Queries', (persistence: boolean) => { }; await withTestCollection(persistence, testDocs, async coll => { const snapshot = await coll - .where(FieldPath.documentId(), notInOp, ['aa', 'ab']) + .where(FieldPath.documentId(), 'not-in', ['aa', 'ab']) .get(); expect(toDataArray(snapshot)).to.deep.equal([ diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index b425661f6ad..9595e270f60 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -24,9 +24,7 @@ import { apiDescribe, withAlternateTestDb, withTestCollection, - withTestDb, - notInOp, - notEqualOp + withTestDb } from '../util/helpers'; import { ALT_PROJECT_ID, DEFAULT_PROJECT_ID } from '../util/settings'; @@ -941,7 +939,7 @@ apiDescribe('Validation:', (persistence: boolean) => { const collection = db.collection('test') as any; expect(() => collection.where('a', 'foo' as any, 'b')).to.throw( 'Invalid value "foo" provided to function Query.where() for its second argument. ' + - 'Acceptable values: <, <=, ==, >=, >, array-contains, in, array-contains-any' + 'Acceptable values: <, <=, ==, !=, >=, >, array-contains, in, array-contains-any, not-in' ); } ); @@ -952,15 +950,15 @@ apiDescribe('Validation:', (persistence: boolean) => { db => { const collection = db.collection('test'); expect(() => collection.where('a', '>', null)).to.throw( - 'Invalid query. Null supports only equality comparisons.' + "Invalid query. Null only supports '==' and '!=' comparisons." ); expect(() => collection.where('a', 'array-contains', null)).to.throw( - 'Invalid query. Null supports only equality comparisons.' + "Invalid query. Null only supports '==' and '!=' comparisons." ); expect(() => collection.where('a', 'in', null)).to.throw( "Invalid Query. A non-empty array is required for 'in' filters." ); - expect(() => collection.where('a', notInOp, null)).to.throw( + expect(() => collection.where('a', 'not-in', null)).to.throw( "Invalid Query. A non-empty array is required for 'not-in' filters." ); expect(() => @@ -970,15 +968,17 @@ apiDescribe('Validation:', (persistence: boolean) => { ); expect(() => collection.where('a', '>', Number.NaN)).to.throw( - 'Invalid query. NaN supports only equality comparisons.' + "Invalid query. NaN only supports '==' and '!=' comparisons." ); expect(() => collection.where('a', 'array-contains', Number.NaN) - ).to.throw('Invalid query. NaN supports only equality comparisons.'); + ).to.throw( + "Invalid query. NaN only supports '==' and '!=' comparisons." + ); expect(() => collection.where('a', 'in', Number.NaN)).to.throw( "Invalid Query. A non-empty array is required for 'in' filters." ); - expect(() => collection.where('a', notInOp, Number.NaN)).to.throw( + expect(() => collection.where('a', 'not-in', Number.NaN)).to.throw( "Invalid Query. A non-empty array is required for 'not-in' filters." ); expect(() => @@ -1131,7 +1131,7 @@ apiDescribe('Validation:', (persistence: boolean) => { validationIt(persistence, 'with more than one != query fail', db => { const collection = db.collection('test'); expect(() => - collection.where('x', notEqualOp, 32).where('x', notEqualOp, 33) + collection.where('x', '!=', 32).where('x', '!=', 33) ).to.throw("Invalid query. You cannot use more than one '!=' filter."); }); @@ -1141,7 +1141,22 @@ apiDescribe('Validation:', (persistence: boolean) => { db => { const collection = db.collection('test'); expect(() => - collection.where('y', '>', 32).where('x', notEqualOp, 33) + collection.where('y', '>', 32).where('x', '!=', 33) + ).to.throw( + 'Invalid query. All where filters with an ' + + 'inequality (<, <=, >, or >=) must be on the same field.' + + ` But you have inequality filters on 'y' and 'x` + ); + } + ); + + validationIt( + persistence, + 'with != and inequality queries on different fields fail', + db => { + const collection = db.collection('test'); + expect(() => + collection.where('y', '>', 32).where('x', 'not-in', [33]) ).to.throw( 'Invalid query. All where filters with an ' + 'inequality (<, <=, >, or >=) must be on the same field.' + @@ -1172,9 +1187,9 @@ apiDescribe('Validation:', (persistence: boolean) => { expect(() => collection.orderBy('y').orderBy('x').where('x', '>', 32) ).to.throw(reason); - expect(() => - collection.where('x', notEqualOp, 32).orderBy('y') - ).to.throw(reason); + expect(() => collection.where('x', '!=', 32).orderBy('y')).to.throw( + reason + ); } ); @@ -1211,7 +1226,7 @@ apiDescribe('Validation:', (persistence: boolean) => { expect(() => db .collection('test') - .where('foo', notInOp, [2, 3]) + .where('foo', 'not-in', [2, 3]) .where('foo', 'array-contains', 1) ).to.throw( "Invalid query. You cannot use 'array-contains' filters with " + @@ -1223,8 +1238,8 @@ apiDescribe('Validation:', (persistence: boolean) => { expect(() => db .collection('test') - .where('foo', notInOp, [2, 3]) - .where('foo', notEqualOp, 4) + .where('foo', 'not-in', [2, 3]) + .where('foo', '!=', 4) ).to.throw( "Invalid query. You cannot use '!=' filters with 'not-in' filters." ); @@ -1232,8 +1247,8 @@ apiDescribe('Validation:', (persistence: boolean) => { expect(() => db .collection('test') - .where('foo', notEqualOp, 4) - .where('foo', notInOp, [2, 3]) + .where('foo', '!=', 4) + .where('foo', 'not-in', [2, 3]) ).to.throw( "Invalid query. You cannot use 'not-in' filters with '!=' filters." ); @@ -1250,8 +1265,8 @@ apiDescribe('Validation:', (persistence: boolean) => { expect(() => db .collection('test') - .where('foo', notInOp, [1, 2]) - .where('foo', notInOp, [2, 3]) + .where('foo', 'not-in', [1, 2]) + .where('foo', 'not-in', [2, 3]) ).to.throw( "Invalid query. You cannot use more than one 'not-in' filter." ); @@ -1289,7 +1304,7 @@ apiDescribe('Validation:', (persistence: boolean) => { expect(() => db .collection('test') - .where('foo', notInOp, [2, 3]) + .where('foo', 'not-in', [2, 3]) .where('foo', 'array-contains-any', [2, 3]) ).to.throw( "Invalid query. You cannot use 'array-contains-any' filters with " + @@ -1300,7 +1315,7 @@ apiDescribe('Validation:', (persistence: boolean) => { db .collection('test') .where('foo', 'array-contains-any', [2, 3]) - .where('foo', notInOp, [2, 3]) + .where('foo', 'not-in', [2, 3]) ).to.throw( "Invalid query. You cannot use 'not-in' filters with " + "'array-contains-any' filters." @@ -1309,7 +1324,7 @@ apiDescribe('Validation:', (persistence: boolean) => { expect(() => db .collection('test') - .where('foo', notInOp, [2, 3]) + .where('foo', 'not-in', [2, 3]) .where('foo', 'in', [2, 3]) ).to.throw( "Invalid query. You cannot use 'in' filters with 'not-in' filters." @@ -1319,7 +1334,7 @@ apiDescribe('Validation:', (persistence: boolean) => { db .collection('test') .where('foo', 'in', [2, 3]) - .where('foo', notInOp, [2, 3]) + .where('foo', 'not-in', [2, 3]) ).to.throw( "Invalid query. You cannot use 'not-in' filters with 'in' filters." ); @@ -1350,7 +1365,7 @@ apiDescribe('Validation:', (persistence: boolean) => { expect(() => db .collection('test') - .where('foo', notInOp, [2, 3]) + .where('foo', 'not-in', [2, 3]) .where('foo', 'array-contains', 2) .where('foo', 'array-contains-any', [2]) ).to.throw( @@ -1363,7 +1378,7 @@ apiDescribe('Validation:', (persistence: boolean) => { .collection('test') .where('foo', 'array-contains', 2) .where('foo', 'in', [2]) - .where('foo', notInOp, [2, 3]) + .where('foo', 'not-in', [2, 3]) ).to.throw( "Invalid query. You cannot use 'not-in' filters with " + "'array-contains' filters." diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 558fdfaf2eb..16e44ff8a87 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -276,13 +276,3 @@ export function withTestCollectionSettings( } ); } - -// TODO(ne-queries): This exists just so we don't have to do the cast -// repeatedly. Once we expose '!=' publicly we can remove it and just use '!=' -// in all the tests. -export const notEqualOp = '!=' as firestore.WhereFilterOp; - -// TODO(ne-queries): This exists just so we don't have to do the cast -// repeatedly. Once we expose 'not-in' publicly we can remove it and just use 'in' -// in all the tests. -export const notInOp = 'not-in' as firestore.WhereFilterOp; diff --git a/packages/firestore/test/unit/core/query.test.ts b/packages/firestore/test/unit/core/query.test.ts index 5d8ff136ec2..931c3bbf243 100644 --- a/packages/firestore/test/unit/core/query.test.ts +++ b/packages/firestore/test/unit/core/query.test.ts @@ -677,7 +677,7 @@ describe('Query', () => { ); assertCanonicalId( query('collection', filter('a', 'not-in', [1, 2, 3])), - 'collection|f:anot-in[1,2,3]|ob:__name__asc' + 'collection|f:anot-in[1,2,3]|ob:aasc,__name__asc' ); assertCanonicalId( query('collection', filter('a', 'array-contains-any', [1, 2, 3])),