firestore-node-mock 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,73 @@
1
+ import * as timestamp from '../timestamp.js';
2
+ import pkg from 'lodash';
3
+ const { merge } = pkg;
4
+
5
+ export default function buildDocFromHash(hash = {}, id = 'abc123', selectFields = undefined) {
6
+ const exists = !!hash || false;
7
+ return {
8
+ createTime: (hash && hash._createTime) || timestamp.Timestamp.now(),
9
+ exists,
10
+ id: (hash && hash.id) || id,
11
+ readTime: hash && hash._readTime,
12
+ ref: hash && hash._ref,
13
+ metadata: {
14
+ hasPendingWrites: 'Server',
15
+ },
16
+ updateTime: hash && hash._updateTime,
17
+ data() {
18
+ if (!exists) {
19
+ // From Firestore docs: "Returns 'undefined' if the document doesn't exist."
20
+ // See https://firebase.google.com/docs/reference/js/firestore_.documentsnapshot#documentsnapshotdata
21
+ return undefined;
22
+ }
23
+ let copy = { ...hash };
24
+ if (!hash._ref.parent.firestore.options.includeIdsInData) {
25
+ delete copy.id;
26
+ }
27
+ delete copy._collections;
28
+ delete copy._createTime;
29
+ delete copy._readTime;
30
+ delete copy._ref;
31
+ delete copy._updateTime;
32
+
33
+ if (selectFields !== undefined) {
34
+ copy = selectFields.reduce((acc, field) => {
35
+ const path = field.split('.');
36
+ return merge(acc, buildDocFromPath(copy, path));
37
+ }, {});
38
+ }
39
+
40
+ return copy;
41
+ },
42
+ get(fieldPath) {
43
+ // The field path can be compound: from the firestore docs
44
+ // fieldPath The path (e.g. 'foo' or 'foo.bar') to a specific field.
45
+ const parts = fieldPath.split('.');
46
+ const data = this.data();
47
+ return parts.reduce((acc, part, index) => {
48
+ const value = acc[part];
49
+ // if no key is found
50
+ if (value === undefined) {
51
+ // return null if we are on the last item in parts
52
+ // otherwise, return an empty object, so we can continue to iterate
53
+ return parts.length - 1 === index ? null : {};
54
+ }
55
+
56
+ // if there is a value, return it
57
+ return value;
58
+ }, data);
59
+ },
60
+ };
61
+ };
62
+
63
+ function buildDocFromPath(data, path) {
64
+ if (data === undefined || data === null) {
65
+ return {};
66
+ }
67
+
68
+ const [root, ...subPath] = path;
69
+ const rootData = data[root];
70
+ return {
71
+ [root]: subPath.length ? buildDocFromPath(rootData, subPath) : rootData
72
+ };
73
+ }
@@ -0,0 +1,29 @@
1
+ import type { MockedDocument, DocumentHash } from './buildDocFromHash.js';
2
+
3
+ export type Comparator = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'not-in' | 'array-contains-any';
4
+
5
+ export interface QueryFilter {
6
+ key: string;
7
+ comp: Comparator;
8
+ value: string;
9
+ }
10
+
11
+ export interface MockedQuerySnapshot<Doc = MockedDocument> {
12
+ empty: boolean;
13
+ size: number;
14
+ docs: Array<Doc>;
15
+ forEach(callbackfn: (value: Doc, index: number, array: Array<Doc>) => void): void;
16
+ docChanges(): Array<never>;
17
+ }
18
+
19
+ /**
20
+ * Builds a query result from the given array of record objects.
21
+ *
22
+ * @param requestedRecords
23
+ * @param filters
24
+ */
25
+ export default function buildQuerySnapShot(
26
+ requestedRecords: Array<DocumentHash>,
27
+ filters?: Array<QueryFilter>,
28
+ selectFields?: string[],
29
+ ): MockedQuerySnapshot;
@@ -0,0 +1,306 @@
1
+ import buildDocFromHash from './buildDocFromHash.js';
2
+
3
+ export default function buildQuerySnapShot(requestedRecords, filters, selectFields) {
4
+ const definiteRecords = requestedRecords.filter(rec => !!rec);
5
+ const results = _filteredDocuments(definiteRecords, filters);
6
+ const docs = results.map(doc => buildDocFromHash(doc, 'abc123', selectFields));
7
+
8
+ return {
9
+ empty: results.length < 1,
10
+ size: results.length,
11
+ docs,
12
+ forEach(callback) {
13
+ docs.forEach(callback);
14
+ },
15
+ docChanges() {
16
+ return [];
17
+ },
18
+ };
19
+ };
20
+
21
+ /**
22
+ * @typedef DocumentHash
23
+ * @type {import('./buildDocFromHash.js').DocumentHash}
24
+ */
25
+
26
+ /**
27
+ * @typedef Comparator
28
+ * @type {import('./buildQuerySnapShot.js').Comparator}
29
+ */
30
+
31
+ /**
32
+ * Applies query filters to an array of mock document data.
33
+ *
34
+ * @param {Array<DocumentHash>} records The array of records to filter.
35
+ * @param {Array<{ key: string; comp: Comparator; value: unknown }>=} filters The filters to apply.
36
+ * If no filters are provided, then the records array is returned as-is.
37
+ *
38
+ * @returns {Array<import('./buildDocFromHash.js').DocumentHash>} The filtered documents.
39
+ */
40
+ function _filteredDocuments(records, filters) {
41
+ if (!filters || !Array.isArray(filters) || filters.length === 0) {
42
+ return records;
43
+ }
44
+
45
+ filters.forEach(({ key, comp, value }) => {
46
+ // https://firebase.google.com/docs/reference/js/firebase.firestore#wherefilterop
47
+ // Convert values to string to make Array comparisons work
48
+ // See https://jsbin.com/bibawaf/edit?js,console
49
+
50
+ switch (comp) {
51
+ // https://firebase.google.com/docs/firestore/query-data/queries#query_operators
52
+ case '<':
53
+ records = _recordsLessThanValue(records, key, value);
54
+ break;
55
+
56
+ case '<=':
57
+ records = _recordsLessThanOrEqualToValue(records, key, value);
58
+ break;
59
+
60
+ case '==':
61
+ records = _recordsEqualToValue(records, key, value);
62
+ break;
63
+
64
+ case '!=':
65
+ records = _recordsNotEqualToValue(records, key, value);
66
+ break;
67
+
68
+ case '>=':
69
+ records = _recordsGreaterThanOrEqualToValue(records, key, value);
70
+ break;
71
+
72
+ case '>':
73
+ records = _recordsGreaterThanValue(records, key, value);
74
+ break;
75
+
76
+ case 'array-contains':
77
+ records = _recordsArrayContainsValue(records, key, value);
78
+ break;
79
+
80
+ case 'in':
81
+ records = _recordsWithValueInList(records, key, value);
82
+ break;
83
+
84
+ case 'not-in':
85
+ records = _recordsWithValueNotInList(records, key, value);
86
+ break;
87
+
88
+ case 'array-contains-any':
89
+ records = _recordsWithOneOfValues(records, key, value);
90
+ break;
91
+ }
92
+ });
93
+
94
+ return records;
95
+ }
96
+
97
+ function _recordsWithKey(records, key) {
98
+ return records.filter(record => record && getValueByPath(record, key) !== undefined);
99
+ }
100
+
101
+ function _recordsWithNonNullKey(records, key) {
102
+ return records.filter(
103
+ record =>
104
+ record && getValueByPath(record, key) !== undefined && getValueByPath(record, key) !== null,
105
+ );
106
+ }
107
+
108
+ function _shouldCompareNumerically(a, b) {
109
+ return typeof a === 'number' && typeof b === 'number';
110
+ }
111
+
112
+ function _shouldCompareTimestamp(a, b) {
113
+ //We check whether toMillis method exists to support both Timestamp mock and Firestore Timestamp object
114
+ //B is expected to be Date, not Timestamp, just like Firestore does
115
+ return (
116
+ typeof a === 'object' && a !== null && typeof a.toMillis === 'function' && b instanceof Date
117
+ );
118
+ }
119
+
120
+ /**
121
+ * @param {Array<DocumentHash>} records
122
+ * @param {string} key
123
+ * @param {unknown} value
124
+ * @returns {Array<DocumentHash>}
125
+ */
126
+ function _recordsLessThanValue(records, key, value) {
127
+ return _recordsWithNonNullKey(records, key).filter(record => {
128
+ const recordValue = getValueByPath(record, key);
129
+ if (_shouldCompareNumerically(recordValue, value)) {
130
+ return recordValue < value;
131
+ }
132
+ if (_shouldCompareTimestamp(recordValue, value)) {
133
+ return recordValue.toMillis() < value;
134
+ }
135
+ return String(recordValue) < String(value);
136
+ });
137
+ }
138
+
139
+ /**
140
+ * @param {Array<DocumentHash>} records
141
+ * @param {string} key
142
+ * @param {unknown} value
143
+ * @returns {Array<DocumentHash>}
144
+ */
145
+ function _recordsLessThanOrEqualToValue(records, key, value) {
146
+ return _recordsWithNonNullKey(records, key).filter(record => {
147
+ const recordValue = getValueByPath(record, key);
148
+ if (_shouldCompareNumerically(recordValue, value)) {
149
+ return recordValue <= value;
150
+ }
151
+ if (_shouldCompareTimestamp(recordValue, value)) {
152
+ return recordValue.toMillis() <= value;
153
+ }
154
+ return String(recordValue) <= String(value);
155
+ });
156
+ }
157
+
158
+ /**
159
+ * @param {Array<DocumentHash>} records
160
+ * @param {string} key
161
+ * @param {unknown} value
162
+ * @returns {Array<DocumentHash>}
163
+ */
164
+ function _recordsEqualToValue(records, key, value) {
165
+ return _recordsWithKey(records, key).filter(record => {
166
+ const recordValue = getValueByPath(record, key);
167
+ if (_shouldCompareTimestamp(recordValue, value)) {
168
+ //NOTE: for equality, we must compare numbers!
169
+ return recordValue.toMillis() === value.getTime();
170
+ }
171
+ return String(recordValue) === String(value);
172
+ });
173
+ }
174
+
175
+ /**
176
+ * @param {Array<DocumentHash>} records
177
+ * @param {string} key
178
+ * @param {unknown} value
179
+ * @returns {Array<DocumentHash>}
180
+ */
181
+ function _recordsNotEqualToValue(records, key, value) {
182
+ return _recordsWithKey(records, key).filter(record => {
183
+ const recordValue = getValueByPath(record, key);
184
+ if (_shouldCompareTimestamp(recordValue, value)) {
185
+ //NOTE: for equality, we must compare numbers!
186
+ return recordValue.toMillis() !== value.getTime();
187
+ }
188
+ return String(recordValue) !== String(value);
189
+ });
190
+ }
191
+
192
+ /**
193
+ * @param {Array<DocumentHash>} records
194
+ * @param {string} key
195
+ * @param {unknown} value
196
+ * @returns {Array<DocumentHash>}
197
+ */
198
+ function _recordsGreaterThanOrEqualToValue(records, key, value) {
199
+ return _recordsWithNonNullKey(records, key).filter(record => {
200
+ const recordValue = getValueByPath(record, key);
201
+ if (_shouldCompareNumerically(recordValue, value)) {
202
+ return recordValue >= value;
203
+ }
204
+ if (_shouldCompareTimestamp(recordValue, value)) {
205
+ return recordValue.toMillis() >= value;
206
+ }
207
+ return String(recordValue) >= String(value);
208
+ });
209
+ }
210
+
211
+ /**
212
+ * @param {Array<DocumentHash>} records
213
+ * @param {string} key
214
+ * @param {unknown} value
215
+ * @returns {Array<DocumentHash>}
216
+ */
217
+ function _recordsGreaterThanValue(records, key, value) {
218
+ return _recordsWithNonNullKey(records, key).filter(record => {
219
+ const recordValue = getValueByPath(record, key);
220
+ if (_shouldCompareNumerically(recordValue, value)) {
221
+ return recordValue > value;
222
+ }
223
+ if (_shouldCompareTimestamp(recordValue, value)) {
224
+ return recordValue.toMillis() > value;
225
+ }
226
+ return String(recordValue) > String(value);
227
+ });
228
+ }
229
+
230
+ /**
231
+ * @see https://firebase.google.com/docs/firestore/query-data/queries#array_membership
232
+ *
233
+ * @param {Array<DocumentHash>} records
234
+ * @param {string} key
235
+ * @param {unknown} value
236
+ * @returns {Array<DocumentHash>}
237
+ */
238
+ function _recordsArrayContainsValue(records, key, value) {
239
+ return records.filter(
240
+ record =>
241
+ record &&
242
+ getValueByPath(record, key) &&
243
+ Array.isArray(getValueByPath(record, key)) &&
244
+ getValueByPath(record, key).includes(value),
245
+ );
246
+ }
247
+
248
+ /**
249
+ * @see https://firebase.google.com/docs/firestore/query-data/queries#in_not-in_and_array-contains-any
250
+ *
251
+ * @param {Array<DocumentHash>} records
252
+ * @param {string} key
253
+ * @param {unknown} value
254
+ * @returns {Array<DocumentHash>}
255
+ */
256
+ function _recordsWithValueInList(records, key, value) {
257
+ // TODO: Throw an error when a value is passed that contains more than 10 values
258
+ return records.filter(record => {
259
+ if (!record || getValueByPath(record, key) === undefined) {
260
+ return false;
261
+ }
262
+ return value && Array.isArray(value) && value.includes(getValueByPath(record, key));
263
+ });
264
+ }
265
+
266
+ /**
267
+ * @see https://firebase.google.com/docs/firestore/query-data/queries#not-in
268
+ *
269
+ * @param {Array<DocumentHash>} records
270
+ * @param {string} key
271
+ * @param {unknown} value
272
+ * @returns {Array<DocumentHash>}
273
+ */
274
+ function _recordsWithValueNotInList(records, key, value) {
275
+ // TODO: Throw an error when a value is passed that contains more than 10 values
276
+ return _recordsWithKey(records, key).filter(
277
+ record => value && Array.isArray(value) && !value.includes(getValueByPath(record, key)),
278
+ );
279
+ }
280
+
281
+ /**
282
+ * @see https://firebase.google.com/docs/firestore/query-data/queries#in_not-in_and_array-contains-any
283
+ *
284
+ * @param {Array<DocumentHash>} records
285
+ * @param {string} key
286
+ * @param {unknown} value
287
+ * @returns {Array<DocumentHash>}
288
+ */
289
+ function _recordsWithOneOfValues(records, key, value) {
290
+ // TODO: Throw an error when a value is passed that contains more than 10 values
291
+ return records.filter(
292
+ record =>
293
+ record &&
294
+ getValueByPath(record, key) &&
295
+ Array.isArray(getValueByPath(record, key)) &&
296
+ value &&
297
+ Array.isArray(value) &&
298
+ getValueByPath(record, key).some(v => value.includes(v)),
299
+ );
300
+ }
301
+
302
+ function getValueByPath(record, path) {
303
+ const keys = path.split('.');
304
+ return keys.reduce((nestedObject = {}, key) => nestedObject[key], record);
305
+ }
306
+
@@ -0,0 +1,3 @@
1
+ export const includeIdsInData: boolean;
2
+ export const mutable: boolean;
3
+ export const simulateQueryFilters: boolean;
@@ -0,0 +1,5 @@
1
+ export default {
2
+ includeIdsInData: false,
3
+ mutable: false,
4
+ simulateQueryFilters: false,
5
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @private
3
+ * @class
4
+ */
5
+ declare abstract class Path<T> {
6
+ protected readonly segments: string[];
7
+ /**
8
+ * @private
9
+ * @hideconstructor
10
+ * @param segments
11
+ */
12
+ constructor(segments: string[]);
13
+ compareTo(other: Path<T>): number;
14
+ toArray(): string[];
15
+ isEqual(other: Path<T>): boolean;
16
+ }
17
+
18
+ /**
19
+ * @class
20
+ */
21
+ export declare class FieldPath extends Path<FieldPath> implements FieldPath {
22
+ private static _DOCUMENT_ID;
23
+ constructor(...segments: string[]);
24
+ static documentId(): FieldPath;
25
+ isEqual(other: FieldPath): boolean;
26
+ }
package/mocks/path.js ADDED
@@ -0,0 +1,39 @@
1
+ export class Path {
2
+ constructor(segments) {
3
+ this.segments = segments;
4
+ }
5
+ compareTo(other) {
6
+ const len = Math.min(this.segments.length, other.segments.length);
7
+ for (let i = 0; i < len; i++) {
8
+ if (this.segments[i] < other.segments[i]) {
9
+ return -1;
10
+ }
11
+ if (this.segments[i] > other.segments[i]) {
12
+ return 1;
13
+ }
14
+ }
15
+ if (this.segments.length < other.segments.length) {
16
+ return -1;
17
+ }
18
+ if (this.segments.length > other.segments.length) {
19
+ return 1;
20
+ }
21
+ return 0;
22
+ }
23
+ isEqual(other) {
24
+ return this === other || this.compareTo(other) === 0;
25
+ }
26
+ }
27
+
28
+ export class FieldPath extends Path {
29
+ constructor(...segments) {
30
+ super(segments);
31
+ }
32
+ static documentId() {
33
+ return FieldPath._DOCUMENT_ID;
34
+ }
35
+ isEqual(other) {
36
+ return super.isEqual(other);
37
+ }
38
+ }
39
+ FieldPath._DOCUMENT_ID = new FieldPath('__name__');
@@ -0,0 +1,32 @@
1
+ import { Mock } from 'node:test';
2
+ import type { FakeFirestore } from './firestore.js';
3
+ import type { MockedQuerySnapshot } from './helpers/buildQuerySnapShot.js';
4
+
5
+ export class Query {
6
+ constructor(collectionName: string, firestore: typeof FakeFirestore);
7
+
8
+ get(): Promise<MockedQuerySnapshot>;
9
+ select(): Query;
10
+ where(): Query;
11
+ offset(): Query;
12
+ limit(): Query;
13
+ orderBy(): Query;
14
+ startAfter(): Query;
15
+ startAt(): Query;
16
+ withConverter(): Query;
17
+ onSnapshot(): () => void;
18
+ }
19
+
20
+ export const mocks: {
21
+ mockGet: Mock<any>,
22
+ mockSelect: Mock<any>,
23
+ mockWhere: Mock<any>,
24
+ mockLimit: Mock<any>,
25
+ mockOrderBy: Mock<any>,
26
+ mockOffset: Mock<any>,
27
+ mockStartAfter: Mock<any>,
28
+ mockStartAt: Mock<any>,
29
+ mockQueryOnSnapshot: Mock<any>,
30
+ mockQueryOnSnapshotUnsubscribe: Mock<any>,
31
+ mockWithConverter: Mock<any>,
32
+ };
package/mocks/query.js ADDED
@@ -0,0 +1,162 @@
1
+ import { mock } from 'node:test';
2
+ import buildQuerySnapShot from './helpers/buildQuerySnapShot.js';
3
+
4
+ export const mockGet = mock.fn();
5
+ export const mockSelect = mock.fn();
6
+ export const mockWhere = mock.fn();
7
+ export const mockLimit = mock.fn();
8
+ export const mockOrderBy = mock.fn();
9
+ export const mockOffset = mock.fn();
10
+ export const mockStartAfter = mock.fn();
11
+ export const mockStartAt = mock.fn();
12
+ export const mockQueryOnSnapshot = mock.fn();
13
+ export const mockQueryOnSnapshotUnsubscribe = mock.fn();
14
+ export const mockWithConverter = mock.fn();
15
+
16
+ export class Query {
17
+ constructor(collectionName, firestore, isGroupQuery = false) {
18
+ this.collectionName = collectionName;
19
+ this.firestore = firestore;
20
+ this.filters = [];
21
+ this.selectFields = undefined;
22
+ this.isGroupQuery = isGroupQuery;
23
+ }
24
+
25
+ get() {
26
+ mockGet(...arguments);
27
+ return Promise.resolve(this._get());
28
+ }
29
+
30
+ _get() {
31
+ // Simulate collectionGroup query
32
+
33
+ // Get Firestore collections whose name match `this.collectionName`; return their documents
34
+ const requestedRecords = [];
35
+
36
+ const queue = [
37
+ {
38
+ lastParent: '',
39
+ collections: this.firestore.database,
40
+ },
41
+ ];
42
+
43
+ while (queue.length > 0) {
44
+ // Get a collection
45
+ const { lastParent, collections } = queue.shift();
46
+
47
+ Object.entries(collections).forEach(([collectionPath, docs]) => {
48
+ const prefix = lastParent ? `${lastParent}/` : '';
49
+
50
+ const newLastParent = `${prefix}${collectionPath}`;
51
+ const lastPathComponent = collectionPath.split('/').pop();
52
+
53
+ // If this is a matching collection, grep its documents
54
+ if (lastPathComponent === this.collectionName) {
55
+ const docHashes = docs.map(doc => {
56
+ // Fetch the document from the mock db
57
+ const path = `${newLastParent}/${doc.id}`;
58
+ return {
59
+ ...doc,
60
+ _ref: this.firestore._doc(path),
61
+ };
62
+ });
63
+ requestedRecords.push(...docHashes);
64
+ }
65
+
66
+ // Enqueue adjacent collections for next run
67
+ docs.forEach(doc => {
68
+ if (doc._collections) {
69
+ queue.push({
70
+ lastParent: `${prefix}${collectionPath}/${doc.id}`,
71
+ collections: doc._collections,
72
+ });
73
+ }
74
+ });
75
+ });
76
+ }
77
+
78
+ // Return the requested documents
79
+ const isFilteringEnabled = this.firestore.options.simulateQueryFilters;
80
+ return buildQuerySnapShot(
81
+ requestedRecords,
82
+ isFilteringEnabled ? this.filters : undefined,
83
+ this.selectFields,
84
+ );
85
+ }
86
+
87
+ select(...fieldPaths) {
88
+ this.selectFields = fieldPaths;
89
+ return mockSelect(...fieldPaths) || this;
90
+ }
91
+
92
+ where(key, comp, value) {
93
+ const result = mockWhere(...arguments);
94
+ if (result) {
95
+ return result;
96
+ }
97
+
98
+ // Firestore has been tested to throw an error at this point when trying to compare null as a quantity
99
+ if (value === null && !['==', '!='].includes(comp)) {
100
+ throw new Error(
101
+ `FakeFirebaseError: Invalid query. Null only supports '==' and '!=' comparisons.`,
102
+ );
103
+ }
104
+ this.filters.push({ key, comp, value });
105
+ return result || this;
106
+ }
107
+
108
+ offset() {
109
+ return mockOffset(...arguments) || this;
110
+ }
111
+
112
+ limit() {
113
+ return mockLimit(...arguments) || this;
114
+ }
115
+
116
+ orderBy() {
117
+ return mockOrderBy(...arguments) || this;
118
+ }
119
+
120
+ startAfter() {
121
+ return mockStartAfter(...arguments) || this;
122
+ }
123
+
124
+ startAt() {
125
+ return mockStartAt(...arguments) || this;
126
+ }
127
+
128
+ withConverter() {
129
+ return mockWithConverter(...arguments) || this;
130
+ }
131
+
132
+ onSnapshot() {
133
+ mockQueryOnSnapshot(...arguments);
134
+ const [callback, errorCallback] = arguments;
135
+ try {
136
+ callback(this._get());
137
+ } catch (e) {
138
+ if (errorCallback) {
139
+ errorCallback(e);
140
+ } else {
141
+ throw e;
142
+ }
143
+ }
144
+
145
+ // Returns an unsubscribe mock
146
+ return mockQueryOnSnapshotUnsubscribe;
147
+ }
148
+ }
149
+
150
+ export const mocks = {
151
+ mockGet,
152
+ mockSelect,
153
+ mockWhere,
154
+ mockLimit,
155
+ mockOrderBy,
156
+ mockOffset,
157
+ mockStartAfter,
158
+ mockStartAt,
159
+ mockQueryOnSnapshot,
160
+ mockQueryOnSnapshotUnsubscribe,
161
+ mockWithConverter,
162
+ };
@@ -0,0 +1,20 @@
1
+ import type { StubOverrides, StubOptions } from './firebase.js';
2
+ import type { FakeFirestore } from './firestore.js';
3
+
4
+ declare class Firestore extends FakeFirestore {
5
+ constructor();
6
+ }
7
+
8
+ interface GCloudFirestoreMock {
9
+ Firestore: typeof Firestore;
10
+ Query: typeof Firestore.Query;
11
+ CollectionReference: typeof Firestore.CollectionReference;
12
+ DocumentReference: typeof Firestore.DocumentReference;
13
+ FieldValue: typeof Firestore.FieldValue;
14
+ FieldPath: typeof Firestore.FieldPath;
15
+ Timestamp: typeof Firestore.Timestamp;
16
+ Transaction: typeof Firestore.Transaction;
17
+ }
18
+
19
+ export const firestoreStub: (overrides: StubOverrides, options?: StubOptions) => GCloudFirestoreMock;
20
+ export const mockReactNativeFirestore: (overrides: StubOverrides, options?: StubOptions) => void;