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.
- package/README.md +479 -0
- package/index.d.ts +6 -0
- package/index.d.ts.map +1 -0
- package/index.js +6 -0
- package/index.js.map +1 -0
- package/mocks/auth.d.ts +34 -0
- package/mocks/auth.js +69 -0
- package/mocks/fieldValue.d.ts +23 -0
- package/mocks/fieldValue.js +57 -0
- package/mocks/firebase.d.ts +37 -0
- package/mocks/firebase.js +79 -0
- package/mocks/firestore.d.ts +181 -0
- package/mocks/firestore.js +610 -0
- package/mocks/googleCloudFirestore.d.ts +20 -0
- package/mocks/googleCloudFirestore.js +43 -0
- package/mocks/helpers/buildDocFromHash.d.ts +28 -0
- package/mocks/helpers/buildDocFromHash.js +73 -0
- package/mocks/helpers/buildQuerySnapShot.d.ts +29 -0
- package/mocks/helpers/buildQuerySnapShot.js +306 -0
- package/mocks/helpers/defaultMockOptions.d.ts +3 -0
- package/mocks/helpers/defaultMockOptions.js +5 -0
- package/mocks/path.d.ts +26 -0
- package/mocks/path.js +39 -0
- package/mocks/query.d.ts +32 -0
- package/mocks/query.js +162 -0
- package/mocks/reactNativeFirebaseFirestore.d.ts +20 -0
- package/mocks/reactNativeFirebaseFirestore.js +43 -0
- package/mocks/timestamp.d.ts +22 -0
- package/mocks/timestamp.js +103 -0
- package/mocks/transaction.d.ts +22 -0
- package/mocks/transaction.js +62 -0
- package/package.json +61 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { mock } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import * as timestamp from './timestamp.js';
|
|
5
|
+
import * as fieldValue from './fieldValue.js';
|
|
6
|
+
import * as query from './query.js';
|
|
7
|
+
import * as transaction from './transaction.js';
|
|
8
|
+
import * as path from './path.js';
|
|
9
|
+
|
|
10
|
+
import buildDocFromHash from './helpers/buildDocFromHash.js';
|
|
11
|
+
import buildQuerySnapShot from './helpers/buildQuerySnapShot.js';
|
|
12
|
+
|
|
13
|
+
export const mockCollectionGroup = mock.fn();
|
|
14
|
+
export const mockBatch = mock.fn();
|
|
15
|
+
export const mockRunTransaction = mock.fn();
|
|
16
|
+
export const mockRecursiveDelete = mock.fn();
|
|
17
|
+
|
|
18
|
+
export const mockSettings = mock.fn();
|
|
19
|
+
export const mockUseEmulator = mock.fn();
|
|
20
|
+
export const mockCollection = mock.fn();
|
|
21
|
+
export const mockDoc = mock.fn();
|
|
22
|
+
export const mockCreate = mock.fn();
|
|
23
|
+
export const mockUpdate = mock.fn();
|
|
24
|
+
export const mockSet = mock.fn();
|
|
25
|
+
export const mockAdd = mock.fn();
|
|
26
|
+
export const mockDelete = mock.fn();
|
|
27
|
+
export const mockListDocuments = mock.fn();
|
|
28
|
+
export const mockListCollections = mock.fn();
|
|
29
|
+
|
|
30
|
+
export const mockBatchDelete = mock.fn();
|
|
31
|
+
export const mockBatchCommit = mock.fn();
|
|
32
|
+
export const mockBatchUpdate = mock.fn();
|
|
33
|
+
export const mockBatchSet = mock.fn();
|
|
34
|
+
export const mockBatchCreate = mock.fn();
|
|
35
|
+
|
|
36
|
+
export const mockOnSnapShot = mock.fn();
|
|
37
|
+
|
|
38
|
+
const _randomId = () => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString();
|
|
39
|
+
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
export class FakeFirestore {
|
|
42
|
+
constructor(stubbedDatabase = {}, options = {}) {
|
|
43
|
+
this.database = timestamp.convertTimestamps(stubbedDatabase);
|
|
44
|
+
this.query = new query.Query('', this);
|
|
45
|
+
this.options = options;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
set collectionName(collectionName) {
|
|
49
|
+
this.query.collectionName = collectionName;
|
|
50
|
+
this.recordToFetch = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get collectionName() {
|
|
54
|
+
return this.query.collectionName;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getAll(...params) {
|
|
58
|
+
//Strip ReadOptions object
|
|
59
|
+
params = params.filter(arg => arg instanceof FakeFirestore.DocumentReference);
|
|
60
|
+
|
|
61
|
+
return Promise.all(transaction.mocks.mockGetAll(...params) || [...params].map(r => r.get()));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @returns {import('./firestore.js').FirestoreBatch}
|
|
66
|
+
*/
|
|
67
|
+
batch() {
|
|
68
|
+
mockBatch(...arguments);
|
|
69
|
+
return {
|
|
70
|
+
_ref: this,
|
|
71
|
+
delete() {
|
|
72
|
+
mockBatchDelete(...arguments);
|
|
73
|
+
return this;
|
|
74
|
+
},
|
|
75
|
+
set(doc, data, setOptions = {}) {
|
|
76
|
+
mockBatchSet(...arguments);
|
|
77
|
+
this._ref._updateData(doc.path, data, setOptions.merge);
|
|
78
|
+
return this;
|
|
79
|
+
},
|
|
80
|
+
update(doc, data) {
|
|
81
|
+
mockBatchUpdate(...arguments);
|
|
82
|
+
this._ref._updateData(doc.path, data, true);
|
|
83
|
+
return this;
|
|
84
|
+
},
|
|
85
|
+
create(doc, data) {
|
|
86
|
+
mockBatchCreate(...arguments);
|
|
87
|
+
this._ref._updateData(doc.path, data, false);
|
|
88
|
+
return this;
|
|
89
|
+
},
|
|
90
|
+
commit() {
|
|
91
|
+
mockBatchCommit(...arguments);
|
|
92
|
+
return Promise.resolve([]);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
settings() {
|
|
98
|
+
mockSettings(...arguments);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
useEmulator() {
|
|
103
|
+
mockUseEmulator(...arguments);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
collection(path) {
|
|
107
|
+
// Accept any collection path
|
|
108
|
+
// See https://firebase.google.com/docs/reference/js/firestore_#collection
|
|
109
|
+
mockCollection(...arguments);
|
|
110
|
+
|
|
111
|
+
if (path === undefined) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`FakeFirebaseError: Function Firestore.collection() requires 1 argument, but was called with 0 arguments.`,
|
|
114
|
+
);
|
|
115
|
+
} else if (!path || typeof path !== 'string') {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`FakeFirebaseError: Function Firestore.collection() requires its first argument to be of type non-empty string, but it was: ${JSON.stringify(
|
|
118
|
+
path,
|
|
119
|
+
)}`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Ignore leading slash
|
|
124
|
+
const pathArray = path.replace(/^\/+/, '').split('/');
|
|
125
|
+
// Must be collection-level, so odd-numbered elements
|
|
126
|
+
if (pathArray.length % 2 !== 1) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`FakeFirebaseError: Invalid collection reference. Collection references must have an odd number of segments, but ${path} has ${pathArray.length}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const { coll } = this._docAndColForPathArray(pathArray);
|
|
133
|
+
return coll;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
collectionGroup(collectionId) {
|
|
137
|
+
mockCollectionGroup(...arguments);
|
|
138
|
+
return new FakeFirestore.Query(collectionId, this, true);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
doc(path) {
|
|
142
|
+
mockDoc(path);
|
|
143
|
+
return this._doc(path);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_doc(path) {
|
|
147
|
+
// Accept any document path
|
|
148
|
+
// See https://firebase.google.com/docs/reference/js/firestore_#doc
|
|
149
|
+
|
|
150
|
+
if (path === undefined) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`FakeFirebaseError: Function Firestore.doc() requires 1 argument, but was called with 0 arguments.`,
|
|
153
|
+
);
|
|
154
|
+
} else if (!path || typeof path !== 'string') {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`FakeFirebaseError: Function Firestore.doc() requires its first argument to be of type non-empty string, but it was: ${JSON.stringify(
|
|
157
|
+
path,
|
|
158
|
+
)}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Ignore leading slash
|
|
163
|
+
const pathArray = path.replace(/^\/+/, '').split('/');
|
|
164
|
+
// Must be document-level, so even-numbered elements
|
|
165
|
+
if (pathArray.length % 2 !== 0) {
|
|
166
|
+
throw new Error(`FakeFirebaseError: Invalid document reference. Document references must have an even number of segments, but ${path} has ${pathArray.length}
|
|
167
|
+
`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { doc } = this._docAndColForPathArray(pathArray);
|
|
171
|
+
return doc;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_docAndColForPathArray(pathArray) {
|
|
175
|
+
let doc = null;
|
|
176
|
+
let coll = null;
|
|
177
|
+
for (let index = 0; index < pathArray.length; index += 2) {
|
|
178
|
+
const collectionId = pathArray[index] || '';
|
|
179
|
+
const documentId = pathArray[index + 1] || '';
|
|
180
|
+
|
|
181
|
+
coll = new FakeFirestore.CollectionReference(collectionId, doc, this);
|
|
182
|
+
if (!documentId) {
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
doc = new FakeFirestore.DocumentReference(documentId, coll);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { doc, coll };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
runTransaction(updateFunction) {
|
|
192
|
+
mockRunTransaction(...arguments);
|
|
193
|
+
return updateFunction(new FakeFirestore.Transaction());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
_updateData(path, object, merge) {
|
|
197
|
+
// Do not update unless explicity set to mutable.
|
|
198
|
+
if (!this.options.mutable) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// note: this logic could be deduplicated
|
|
203
|
+
const pathArray = path.replace(/^\/+/, '').split('/');
|
|
204
|
+
|
|
205
|
+
// Must be document-level, so even-numbered elements
|
|
206
|
+
if (pathArray.length % 2 !== 0) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`FakeFirebaseError: Invalid document reference. Document references must have an even number of segments, but ${path} has ${pathArray.length}`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// The parent entry is the id of the document
|
|
213
|
+
const docId = pathArray.pop();
|
|
214
|
+
// Find the parent of docId. Run through the path, creating missing entries
|
|
215
|
+
const parent = pathArray.reduce((last, entry, index) => {
|
|
216
|
+
const isCollection = index % 2 === 0;
|
|
217
|
+
if (isCollection) {
|
|
218
|
+
return last[entry] || (last[entry] = []);
|
|
219
|
+
} else {
|
|
220
|
+
const existingDoc = last.find(doc => doc.id === entry);
|
|
221
|
+
if (existingDoc) {
|
|
222
|
+
// return _collections, creating it if it doesn't already exist
|
|
223
|
+
return existingDoc._collections || (existingDoc._collections = {});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const _collections = {};
|
|
227
|
+
last.push({ id: entry, _collections });
|
|
228
|
+
return _collections;
|
|
229
|
+
}
|
|
230
|
+
}, this.database);
|
|
231
|
+
|
|
232
|
+
// parent should now be an array of documents
|
|
233
|
+
// Replace existing data, if it's there, or add to the end of the array
|
|
234
|
+
const oldIndex = parent.findIndex(doc => doc.id === docId);
|
|
235
|
+
parent[oldIndex >= 0 ? oldIndex : parent.length] = {
|
|
236
|
+
...(merge ? parent[oldIndex] : undefined),
|
|
237
|
+
...object,
|
|
238
|
+
id: docId,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
recursiveDelete(ref, bulkWriter) {
|
|
243
|
+
mockRecursiveDelete(...arguments);
|
|
244
|
+
return Promise.resolve();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
FakeFirestore.Query = query.Query;
|
|
249
|
+
FakeFirestore.FieldValue = fieldValue.FieldValue;
|
|
250
|
+
FakeFirestore.Timestamp = timestamp.Timestamp;
|
|
251
|
+
FakeFirestore.Transaction = transaction.Transaction;
|
|
252
|
+
FakeFirestore.FieldPath = path.FieldPath;
|
|
253
|
+
|
|
254
|
+
/*
|
|
255
|
+
* ============
|
|
256
|
+
* Document Reference
|
|
257
|
+
* ============
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
/** @type {typeof import('./firestore.js').FakeFirestore.DocumentReference} */
|
|
261
|
+
FakeFirestore.DocumentReference = class DocumentReference {
|
|
262
|
+
constructor(id, parent) {
|
|
263
|
+
this.id = id;
|
|
264
|
+
this.parent = parent;
|
|
265
|
+
this.firestore = parent.firestore;
|
|
266
|
+
this.path = parent.path.split('/').concat(id).join('/');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
collection(collectionName) {
|
|
270
|
+
mockCollection(...arguments);
|
|
271
|
+
return new FakeFirestore.CollectionReference(collectionName, this);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
listCollections() {
|
|
275
|
+
mockListCollections();
|
|
276
|
+
|
|
277
|
+
const document = this._getRawObject();
|
|
278
|
+
if (!document._collections) {
|
|
279
|
+
return Promise.resolve([]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const collectionRefs = [];
|
|
283
|
+
for (const collectionId of Object.keys(document._collections)) {
|
|
284
|
+
collectionRefs.push(new FakeFirestore.CollectionReference(collectionId, this));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return Promise.resolve(collectionRefs);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
delete() {
|
|
291
|
+
mockDelete(...arguments);
|
|
292
|
+
return Promise.resolve();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
onSnapshot() {
|
|
296
|
+
mockOnSnapShot(...arguments);
|
|
297
|
+
let callback;
|
|
298
|
+
let errorCallback;
|
|
299
|
+
// eslint-disable-next-line
|
|
300
|
+
let options;
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
if (typeof arguments[0] === 'function') {
|
|
304
|
+
[callback, errorCallback] = arguments;
|
|
305
|
+
} else {
|
|
306
|
+
// eslint-disable-next-line no-unused-vars
|
|
307
|
+
[options, callback, errorCallback] = arguments;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
callback(this._get());
|
|
311
|
+
} catch (e) {
|
|
312
|
+
if (errorCallback) {
|
|
313
|
+
errorCallback(e);
|
|
314
|
+
} else {
|
|
315
|
+
throw e;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Returns an unsubscribe function
|
|
320
|
+
return () => {};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
get() {
|
|
324
|
+
query.mocks.mockGet(...arguments);
|
|
325
|
+
const data = this._get();
|
|
326
|
+
return Promise.resolve(data);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
create(object) {
|
|
330
|
+
mockCreate(...arguments);
|
|
331
|
+
this.firestore._updateData(this.path, object, false);
|
|
332
|
+
return Promise.resolve(
|
|
333
|
+
buildDocFromHash({ ...object, _ref: this, _updateTime: timestamp.Timestamp.now() }),
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
update(object) {
|
|
338
|
+
mockUpdate(...arguments);
|
|
339
|
+
if (this._get().exists) {
|
|
340
|
+
this.firestore._updateData(this.path, object, true);
|
|
341
|
+
}
|
|
342
|
+
return Promise.resolve(
|
|
343
|
+
buildDocFromHash({ ...object, _ref: this, _updateTime: timestamp.Timestamp.now() }),
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
set(object, setOptions = {}) {
|
|
348
|
+
mockSet(...arguments);
|
|
349
|
+
this.firestore._updateData(this.path, object, setOptions.merge);
|
|
350
|
+
return Promise.resolve(
|
|
351
|
+
buildDocFromHash({ ...object, _ref: this, _updateTime: timestamp.Timestamp.now() }),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
isEqual(other) {
|
|
356
|
+
return (
|
|
357
|
+
other instanceof FakeFirestore.DocumentReference &&
|
|
358
|
+
other.firestore === this.firestore &&
|
|
359
|
+
other.path === this.path
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
orderBy() {
|
|
364
|
+
return this.query.orderBy(...arguments);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
limit() {
|
|
368
|
+
return this.query.limit(...arguments);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
offset() {
|
|
372
|
+
return this.query.offset(...arguments);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
startAfter() {
|
|
376
|
+
return this.query.startAfter(...arguments);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
startAt() {
|
|
380
|
+
return this.query.startAt(...arguments);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* A private method for internal use.
|
|
385
|
+
* @returns {Object|null} The raw object of the document or null.
|
|
386
|
+
*/
|
|
387
|
+
_getRawObject() {
|
|
388
|
+
// Ignore leading slash
|
|
389
|
+
const pathArray = this.path.replace(/^\/+/, '').split('/');
|
|
390
|
+
|
|
391
|
+
if (pathArray[0] === 'database') {
|
|
392
|
+
pathArray.shift(); // drop 'database'; it was included in legacy paths, but we don't need it now
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let requestedRecords = this.firestore.database[pathArray.shift()];
|
|
396
|
+
let document = null;
|
|
397
|
+
if (requestedRecords) {
|
|
398
|
+
const documentId = pathArray.shift();
|
|
399
|
+
document = requestedRecords.find(record => record.id === documentId);
|
|
400
|
+
} else {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
for (let index = 0; index < pathArray.length; index += 2) {
|
|
405
|
+
const collectionId = pathArray[index];
|
|
406
|
+
const documentId = pathArray[index + 1];
|
|
407
|
+
|
|
408
|
+
if (!document || !document._collections) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
requestedRecords = document._collections[collectionId] || [];
|
|
412
|
+
if (requestedRecords.length === 0) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
document = requestedRecords.find(record => record.id === documentId);
|
|
417
|
+
if (!document) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// +2 skips to next document
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (!!document || false) {
|
|
425
|
+
return document;
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
_get() {
|
|
431
|
+
const document = this._getRawObject();
|
|
432
|
+
|
|
433
|
+
if (document) {
|
|
434
|
+
document._ref = this;
|
|
435
|
+
document._readTime = timestamp.Timestamp.now();
|
|
436
|
+
return buildDocFromHash(document);
|
|
437
|
+
} else {
|
|
438
|
+
return {
|
|
439
|
+
createTime: undefined,
|
|
440
|
+
exists: false,
|
|
441
|
+
data: () => undefined,
|
|
442
|
+
id: this.id,
|
|
443
|
+
readTime: undefined,
|
|
444
|
+
ref: this,
|
|
445
|
+
updateTime: undefined,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* @returns {DocumentReference}
|
|
452
|
+
*/
|
|
453
|
+
withConverter() {
|
|
454
|
+
query.mocks.mockWithConverter(...arguments);
|
|
455
|
+
return this;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
/*
|
|
460
|
+
* ============
|
|
461
|
+
* Collection Reference
|
|
462
|
+
* ============
|
|
463
|
+
*/
|
|
464
|
+
|
|
465
|
+
FakeFirestore.CollectionReference = class CollectionReference extends FakeFirestore.Query {
|
|
466
|
+
constructor(id, parent, firestore) {
|
|
467
|
+
super(id, firestore || parent.firestore);
|
|
468
|
+
|
|
469
|
+
this.id = id;
|
|
470
|
+
this.parent = parent;
|
|
471
|
+
if (parent) {
|
|
472
|
+
this.path = parent.path.concat(`/${id}`);
|
|
473
|
+
} else {
|
|
474
|
+
this.path = id;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
add(object) {
|
|
479
|
+
mockAdd(...arguments);
|
|
480
|
+
const newDoc = new FakeFirestore.DocumentReference(_randomId(), this);
|
|
481
|
+
this.firestore._updateData(newDoc.path, object);
|
|
482
|
+
return Promise.resolve(newDoc);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
doc(id = _randomId()) {
|
|
486
|
+
mockDoc(id);
|
|
487
|
+
return new FakeFirestore.DocumentReference(id, this, this.firestore);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* A private method, meant mainly to be used by `get` and other internal objects to retrieve
|
|
492
|
+
* the list of database records referenced by this CollectionReference.
|
|
493
|
+
* @returns {Object[]} An array of mocked document records.
|
|
494
|
+
*/
|
|
495
|
+
_records() {
|
|
496
|
+
// Support subcollections as paths: "collection/documentId/subcollection"
|
|
497
|
+
if (this.firestore.database[this.path]) {
|
|
498
|
+
return this.firestore.database[this.path];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Ignore leading slash
|
|
502
|
+
const pathArray = this.path.replace(/^\/+/, '').split('/');
|
|
503
|
+
|
|
504
|
+
let requestedRecords = this.firestore.database[pathArray.shift()];
|
|
505
|
+
if (pathArray.length === 0) {
|
|
506
|
+
return requestedRecords || [];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Since we're a collection, we can assume that pathArray.length % 2 is always 0
|
|
510
|
+
|
|
511
|
+
for (let index = 0; index < pathArray.length; index += 2) {
|
|
512
|
+
const documentId = pathArray[index];
|
|
513
|
+
const collectionId = pathArray[index + 1];
|
|
514
|
+
|
|
515
|
+
if (!requestedRecords) {
|
|
516
|
+
return [];
|
|
517
|
+
}
|
|
518
|
+
const document = requestedRecords.find(record => record.id === documentId);
|
|
519
|
+
if (!document || !document._collections) {
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
requestedRecords = document._collections[collectionId] || [];
|
|
524
|
+
if (requestedRecords.length === 0) {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// +2 skips to next collection
|
|
529
|
+
}
|
|
530
|
+
return requestedRecords;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
listDocuments() {
|
|
534
|
+
mockListDocuments();
|
|
535
|
+
// Returns all documents, including documents with no data but with
|
|
536
|
+
// subcollections: see https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html#listDocuments
|
|
537
|
+
return Promise.resolve(
|
|
538
|
+
this._records().map(rec => new FakeFirestore.DocumentReference(rec.id, this, this.firestore)),
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
get() {
|
|
543
|
+
query.mocks.mockGet(...arguments);
|
|
544
|
+
return Promise.resolve(this._get());
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
_get() {
|
|
548
|
+
// Make sure we have a 'good enough' document reference
|
|
549
|
+
const records = this._records().map(rec => ({
|
|
550
|
+
...rec,
|
|
551
|
+
_ref: new FakeFirestore.DocumentReference(rec.id, this, this.firestore),
|
|
552
|
+
}));
|
|
553
|
+
// Firestore does not return documents with no local data
|
|
554
|
+
const isFilteringEnabled = this.firestore.options.simulateQueryFilters;
|
|
555
|
+
return buildQuerySnapShot(
|
|
556
|
+
records,
|
|
557
|
+
isFilteringEnabled ? this.filters : undefined,
|
|
558
|
+
this.selectFields,
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
isEqual(other) {
|
|
563
|
+
return (
|
|
564
|
+
other instanceof FakeFirestore.CollectionReference &&
|
|
565
|
+
other.firestore === this.firestore &&
|
|
566
|
+
other.path === this.path
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
// Re-exporting mocks from other modules for convenience (keeping backward compatibility with original structure)
|
|
572
|
+
export const {
|
|
573
|
+
mockGet,
|
|
574
|
+
mockSelect,
|
|
575
|
+
mockWhere,
|
|
576
|
+
mockLimit,
|
|
577
|
+
mockOrderBy,
|
|
578
|
+
mockOffset,
|
|
579
|
+
mockStartAfter,
|
|
580
|
+
mockStartAt,
|
|
581
|
+
mockQueryOnSnapshot,
|
|
582
|
+
mockQueryOnSnapshotUnsubscribe,
|
|
583
|
+
mockWithConverter
|
|
584
|
+
} = query.mocks;
|
|
585
|
+
|
|
586
|
+
export const {
|
|
587
|
+
mockGetAll,
|
|
588
|
+
mockGetAllTransaction,
|
|
589
|
+
mockGetTransaction,
|
|
590
|
+
mockSetTransaction,
|
|
591
|
+
mockUpdateTransaction,
|
|
592
|
+
mockDeleteTransaction,
|
|
593
|
+
mockCreateTransaction
|
|
594
|
+
} = transaction.mocks;
|
|
595
|
+
|
|
596
|
+
export const {
|
|
597
|
+
mockArrayUnionFieldValue,
|
|
598
|
+
mockArrayRemoveFieldValue,
|
|
599
|
+
mockDeleteFieldValue,
|
|
600
|
+
mockIncrementFieldValue,
|
|
601
|
+
mockServerTimestampFieldValue
|
|
602
|
+
} = fieldValue.mocks;
|
|
603
|
+
|
|
604
|
+
export const {
|
|
605
|
+
mockTimestampToDate,
|
|
606
|
+
mockTimestampToMillis,
|
|
607
|
+
mockTimestampFromDate,
|
|
608
|
+
mockTimestampFromMillis,
|
|
609
|
+
mockTimestampNow
|
|
610
|
+
} = timestamp.mocks;
|
|
@@ -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 mockGoogleCloudFirestore: (overrides: StubOverrides, options?: StubOptions) => void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { mock } from 'node:test';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { FakeFirestore } from './firestore.js';
|
|
4
|
+
import defaultOptions from './helpers/defaultMockOptions.js';
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
|
|
8
|
+
export const firestoreStub = (overrides, options = defaultOptions) => {
|
|
9
|
+
class Firestore extends FakeFirestore {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(overrides.database, options);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
Query: FakeFirestore.Query,
|
|
16
|
+
CollectionReference: FakeFirestore.CollectionReference,
|
|
17
|
+
DocumentReference: FakeFirestore.DocumentReference,
|
|
18
|
+
FieldValue: FakeFirestore.FieldValue,
|
|
19
|
+
FieldPath: FakeFirestore.FieldPath,
|
|
20
|
+
Timestamp: FakeFirestore.Timestamp,
|
|
21
|
+
Transaction: FakeFirestore.Transaction,
|
|
22
|
+
/** @type {Firestore.constructor} */
|
|
23
|
+
Firestore,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const mockGoogleCloudFirestore = (overrides = {}, options = defaultOptions) => {
|
|
28
|
+
mockModuleIfFound('@google-cloud/firestore', overrides, options);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function mockModuleIfFound(moduleName, overrides, options) {
|
|
32
|
+
try {
|
|
33
|
+
require.resolve(moduleName);
|
|
34
|
+
const stub = firestoreStub(overrides, options);
|
|
35
|
+
mock.module(moduleName, {
|
|
36
|
+
defaultExport: stub,
|
|
37
|
+
namedExports: stub,
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
console.info(`Module ${moduleName} not found, mocking skipped.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FakeFirestore, FakeFirestoreDatabase } from '../firestore.js';
|
|
2
|
+
|
|
3
|
+
export type DocumentData = { [field: string]: unknown };
|
|
4
|
+
|
|
5
|
+
export interface DocumentHash extends DocumentData {
|
|
6
|
+
id?: string;
|
|
7
|
+
_collections: FakeFirestoreDatabase;
|
|
8
|
+
_createTime?: typeof FakeFirestore.Timestamp;
|
|
9
|
+
_readTime?: typeof FakeFirestore.Timestamp;
|
|
10
|
+
_ref: typeof FakeFirestore.DocumentReference;
|
|
11
|
+
_updateTime?: typeof FakeFirestore.Timestamp;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MockedDocument<T = DocumentData> {
|
|
15
|
+
createTime: typeof FakeFirestore.Timestamp;
|
|
16
|
+
exists: boolean;
|
|
17
|
+
id: string;
|
|
18
|
+
readTime: typeof FakeFirestore.Timestamp;
|
|
19
|
+
ref: typeof FakeFirestore.DocumentReference;
|
|
20
|
+
metadata: {
|
|
21
|
+
hasPendingWrites: 'Server';
|
|
22
|
+
};
|
|
23
|
+
updateTime: typeof FakeFirestore.Timestamp;
|
|
24
|
+
data(): T | undefined;
|
|
25
|
+
get(fieldPath: string): unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function buildDocFromHash(hash?: DocumentHash, id?: string, selectFields?: string[]): MockedDocument;
|