firetender-admin 0.13.0 → 0.13.2

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.
Files changed (111) hide show
  1. package/dist/cjs/FiretenderCollection.js.map +1 -0
  2. package/dist/cjs/{src/FiretenderDoc.js → FiretenderDoc.js} +3 -6
  3. package/dist/cjs/FiretenderDoc.js.map +1 -0
  4. package/dist/cjs/errors.js.map +1 -0
  5. package/dist/{esm/src → cjs}/firestore-deps-admin.d.ts +1 -3
  6. package/dist/cjs/{src/firestore-deps-admin.js → firestore-deps-admin.js} +1 -5
  7. package/dist/cjs/firestore-deps-admin.js.map +1 -0
  8. package/dist/cjs/{src/firestore-deps-web.d.ts → firestore-deps-web.d.ts} +1 -2
  9. package/dist/cjs/{src/firestore-deps-web.js → firestore-deps-web.js} +1 -4
  10. package/dist/cjs/firestore-deps-web.js.map +1 -0
  11. package/dist/cjs/{src/firestore-deps.d.ts → firestore-deps.d.ts} +1 -3
  12. package/dist/cjs/{src/firestore-deps.js → firestore-deps.js} +1 -5
  13. package/dist/cjs/firestore-deps.js.map +1 -0
  14. package/dist/cjs/index.js.map +1 -0
  15. package/dist/cjs/package.json +3 -0
  16. package/dist/cjs/proxy.js.map +1 -0
  17. package/dist/cjs/timestamps.js.map +1 -0
  18. package/dist/{esm/src → cjs}/ts-helpers.d.ts +0 -3
  19. package/dist/{esm/src → cjs}/ts-helpers.js.map +1 -1
  20. package/dist/esm/FiretenderCollection.js.map +1 -0
  21. package/dist/esm/{src/FiretenderDoc.js → FiretenderDoc.js} +4 -7
  22. package/dist/esm/FiretenderDoc.js.map +1 -0
  23. package/dist/esm/errors.js.map +1 -0
  24. package/dist/{cjs/src → esm}/firestore-deps-admin.d.ts +1 -3
  25. package/dist/esm/{src/firestore-deps-admin.js → firestore-deps-admin.js} +1 -3
  26. package/dist/esm/firestore-deps-admin.js.map +1 -0
  27. package/dist/esm/{src/firestore-deps-web.d.ts → firestore-deps-web.d.ts} +1 -2
  28. package/dist/esm/{src/firestore-deps-web.js → firestore-deps-web.js} +1 -2
  29. package/dist/esm/firestore-deps-web.js.map +1 -0
  30. package/dist/esm/{src/firestore-deps.d.ts → firestore-deps.d.ts} +1 -3
  31. package/dist/esm/{src/firestore-deps.js → firestore-deps.js} +1 -3
  32. package/dist/esm/firestore-deps.js.map +1 -0
  33. package/dist/esm/index.js.map +1 -0
  34. package/dist/esm/package.json +3 -0
  35. package/dist/esm/proxy.js.map +1 -0
  36. package/dist/esm/timestamps.js.map +1 -0
  37. package/dist/{cjs/src → esm}/ts-helpers.d.ts +0 -3
  38. package/dist/esm/ts-helpers.js.map +1 -0
  39. package/package.json +13 -14
  40. package/src/FiretenderDoc.ts +4 -10
  41. package/src/firestore-deps-admin.ts +0 -4
  42. package/src/firestore-deps-web.ts +0 -2
  43. package/src/firestore-deps.ts +0 -4
  44. package/src/ts-helpers.ts +0 -10
  45. package/dist/cjs/jest.config.d.ts +0 -3
  46. package/dist/cjs/jest.config.js +0 -23
  47. package/dist/cjs/jest.config.js.map +0 -1
  48. package/dist/cjs/src/FiretenderCollection.js.map +0 -1
  49. package/dist/cjs/src/FiretenderDoc.js.map +0 -1
  50. package/dist/cjs/src/__tests__/FiretenderCollection.spec.d.ts +0 -1
  51. package/dist/cjs/src/__tests__/FiretenderCollection.spec.js +0 -332
  52. package/dist/cjs/src/__tests__/FiretenderCollection.spec.js.map +0 -1
  53. package/dist/cjs/src/__tests__/FiretenderDoc.spec.d.ts +0 -6
  54. package/dist/cjs/src/__tests__/FiretenderDoc.spec.js +0 -1400
  55. package/dist/cjs/src/__tests__/FiretenderDoc.spec.js.map +0 -1
  56. package/dist/cjs/src/__tests__/firestore-emulator.d.ts +0 -15
  57. package/dist/cjs/src/__tests__/firestore-emulator.js +0 -81
  58. package/dist/cjs/src/__tests__/firestore-emulator.js.map +0 -1
  59. package/dist/cjs/src/errors.js.map +0 -1
  60. package/dist/cjs/src/firestore-deps-admin.js.map +0 -1
  61. package/dist/cjs/src/firestore-deps-web.js.map +0 -1
  62. package/dist/cjs/src/firestore-deps.js.map +0 -1
  63. package/dist/cjs/src/index.js.map +0 -1
  64. package/dist/cjs/src/proxy.js.map +0 -1
  65. package/dist/cjs/src/timestamps.js.map +0 -1
  66. package/dist/cjs/src/ts-helpers.js.map +0 -1
  67. package/dist/esm/jest.config.d.ts +0 -3
  68. package/dist/esm/jest.config.js +0 -21
  69. package/dist/esm/jest.config.js.map +0 -1
  70. package/dist/esm/src/FiretenderCollection.js.map +0 -1
  71. package/dist/esm/src/FiretenderDoc.js.map +0 -1
  72. package/dist/esm/src/__tests__/FiretenderCollection.spec.d.ts +0 -1
  73. package/dist/esm/src/__tests__/FiretenderCollection.spec.js +0 -330
  74. package/dist/esm/src/__tests__/FiretenderCollection.spec.js.map +0 -1
  75. package/dist/esm/src/__tests__/FiretenderDoc.spec.d.ts +0 -6
  76. package/dist/esm/src/__tests__/FiretenderDoc.spec.js +0 -1398
  77. package/dist/esm/src/__tests__/FiretenderDoc.spec.js.map +0 -1
  78. package/dist/esm/src/__tests__/firestore-emulator.d.ts +0 -15
  79. package/dist/esm/src/__tests__/firestore-emulator.js +0 -75
  80. package/dist/esm/src/__tests__/firestore-emulator.js.map +0 -1
  81. package/dist/esm/src/errors.js.map +0 -1
  82. package/dist/esm/src/firestore-deps-admin.js.map +0 -1
  83. package/dist/esm/src/firestore-deps-web.js.map +0 -1
  84. package/dist/esm/src/firestore-deps.js.map +0 -1
  85. package/dist/esm/src/index.js.map +0 -1
  86. package/dist/esm/src/proxy.js.map +0 -1
  87. package/dist/esm/src/timestamps.js.map +0 -1
  88. /package/dist/cjs/{src/FiretenderCollection.d.ts → FiretenderCollection.d.ts} +0 -0
  89. /package/dist/cjs/{src/FiretenderCollection.js → FiretenderCollection.js} +0 -0
  90. /package/dist/cjs/{src/FiretenderDoc.d.ts → FiretenderDoc.d.ts} +0 -0
  91. /package/dist/cjs/{src/errors.d.ts → errors.d.ts} +0 -0
  92. /package/dist/cjs/{src/errors.js → errors.js} +0 -0
  93. /package/dist/cjs/{src/index.d.ts → index.d.ts} +0 -0
  94. /package/dist/cjs/{src/index.js → index.js} +0 -0
  95. /package/dist/cjs/{src/proxy.d.ts → proxy.d.ts} +0 -0
  96. /package/dist/cjs/{src/proxy.js → proxy.js} +0 -0
  97. /package/dist/cjs/{src/timestamps.d.ts → timestamps.d.ts} +0 -0
  98. /package/dist/cjs/{src/timestamps.js → timestamps.js} +0 -0
  99. /package/dist/cjs/{src/ts-helpers.js → ts-helpers.js} +0 -0
  100. /package/dist/esm/{src/FiretenderCollection.d.ts → FiretenderCollection.d.ts} +0 -0
  101. /package/dist/esm/{src/FiretenderCollection.js → FiretenderCollection.js} +0 -0
  102. /package/dist/esm/{src/FiretenderDoc.d.ts → FiretenderDoc.d.ts} +0 -0
  103. /package/dist/esm/{src/errors.d.ts → errors.d.ts} +0 -0
  104. /package/dist/esm/{src/errors.js → errors.js} +0 -0
  105. /package/dist/esm/{src/index.d.ts → index.d.ts} +0 -0
  106. /package/dist/esm/{src/index.js → index.js} +0 -0
  107. /package/dist/esm/{src/proxy.d.ts → proxy.d.ts} +0 -0
  108. /package/dist/esm/{src/proxy.js → proxy.js} +0 -0
  109. /package/dist/esm/{src/timestamps.d.ts → timestamps.d.ts} +0 -0
  110. /package/dist/esm/{src/timestamps.js → timestamps.js} +0 -0
  111. /package/dist/esm/{src/ts-helpers.js → ts-helpers.js} +0 -0
@@ -1,1400 +0,0 @@
1
- "use strict";
2
- /**
3
- * Start before testing: firebase emulators:start --project=firetender
4
- *
5
- * TODO: #5 zod effects and preprocessing, but disallowing transforms
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- const zod_1 = require("zod");
9
- const errors_1 = require("../errors");
10
- const firestore_deps_1 = require("../firestore-deps");
11
- const FiretenderDoc_1 = require("../FiretenderDoc");
12
- const timestamps_1 = require("../timestamps");
13
- const firestore_emulator_1 = require("./firestore-emulator");
14
- const testDataSchema = zod_1.z.object({
15
- email: zod_1.z.string().email(),
16
- ttl: timestamps_1.timestampSchema.optional(),
17
- recordOfPrimitives: zod_1.z.record(zod_1.z.string()).default({}),
18
- recordOfObjects: zod_1.z
19
- .record(zod_1.z.object({
20
- rating: zod_1.z.number(),
21
- tags: zod_1.z.array(zod_1.z.string()).default([]),
22
- favoriteColor: zod_1.z.string().optional(),
23
- }))
24
- .default({}),
25
- nestedRecords: zod_1.z.record(zod_1.z.record(zod_1.z.number())).default({}),
26
- arrayOfObjects: zod_1.z
27
- .array(zod_1.z.object({
28
- name: zod_1.z.string(),
29
- entries: zod_1.z.record(zod_1.z.number()).default({}),
30
- favoriteColor: zod_1.z.string().optional(),
31
- }))
32
- .default([]),
33
- arrayOfDiscUnions: zod_1.z
34
- .array(zod_1.z.discriminatedUnion("type", [
35
- zod_1.z.object({ type: zod_1.z.literal("A"), someNumber: zod_1.z.number() }),
36
- zod_1.z.object({ type: zod_1.z.literal("B"), someString: zod_1.z.string() }),
37
- zod_1.z.object({ type: zod_1.z.literal("C"), someBoolean: zod_1.z.boolean() }),
38
- ]))
39
- .optional(),
40
- unreadable: zod_1.z.boolean().optional(),
41
- });
42
- let firestore;
43
- let testCollection;
44
- beforeAll(async () => {
45
- firestore = await (0, firestore_emulator_1.getFirestoreEmulator)();
46
- testCollection = (0, firestore_deps_1.collection)(firestore, "doctests");
47
- });
48
- afterAll(firestore_emulator_1.cleanupFirestoreEmulator);
49
- async function createAndLoadDoc(data, options = {}) {
50
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, data);
51
- return new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef, options).load();
52
- }
53
- describe("load", () => {
54
- it("must be called before referencing the accessors.", async () => {
55
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, (0, firestore_deps_1.doc)(testCollection, "foo"));
56
- expect(testDoc.isLoaded).toBe(false);
57
- expect(() => testDoc.r.email).toThrowError("load() must be called before reading the document.");
58
- expect(() => testDoc.w.email).toThrowError("load() must be called before updating the document.");
59
- });
60
- it("throws for a non-existent doc.", async () => {
61
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, (0, firestore_deps_1.doc)(testCollection, "foo"));
62
- await expect(testDoc.load()).rejects.toThrowError("does not exist");
63
- });
64
- it("throws for a doc blocked by Firestore rules.", async () => {
65
- // Admin can read anywhere, so this test does not throw an error.
66
- if (firestore_deps_1.FIRESTORE_DEPS_TYPE === "admin")
67
- return;
68
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, {
69
- email: "bob@example.com",
70
- unreadable: true,
71
- });
72
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef);
73
- await expect(testDoc.load()).rejects.toThrowError(errors_1.FiretenderIOError);
74
- });
75
- it("throws for a created but not yet written doc.", async () => {
76
- const testDoc = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, {
77
- email: "bob@example.com",
78
- });
79
- await expect(testDoc.load()).rejects.toThrowError("should not be called for new documents.");
80
- });
81
- it("throws for an invalid doc.", async () => {
82
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, {}); // Missing email.
83
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef);
84
- await expect(testDoc.load()).rejects.toThrowError('"message": "Required"');
85
- });
86
- it("always reads from Firestore if force is set.", async () => {
87
- const testDoc = await createAndLoadDoc({
88
- email: "bob@example.com",
89
- });
90
- // testDoc does not show a change in Firestore until after a forced load.
91
- await (0, firestore_deps_1.updateDoc)(testDoc.docRef, { email: "alice@example.com" });
92
- await testDoc.load(); // Does nothing.
93
- expect(testDoc.r.email).toBe("bob@example.com");
94
- await testDoc.load({ force: true });
95
- expect(testDoc.r.email).toBe("alice@example.com");
96
- });
97
- it("waits if a load call is already in progress", async () => {
98
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, { email: "bob@example.com" });
99
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef);
100
- const loadingPromise1 = testDoc.load();
101
- expect(testDoc.isLoaded).toBeFalsy();
102
- const loadingPromise2 = testDoc.load();
103
- expect(testDoc.isLoaded).toBeFalsy();
104
- const loadingPromise3 = testDoc.load();
105
- expect(testDoc.isLoaded).toBeFalsy();
106
- await Promise.all([loadingPromise1, loadingPromise2, loadingPromise3]);
107
- expect(testDoc.isLoaded).toBeTruthy();
108
- expect(testDoc.r.email).toBe("bob@example.com");
109
- });
110
- });
111
- describe("listener", () => {
112
- it("can listen for changes to a doc", async () => {
113
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, { email: "bob@example.com" });
114
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef);
115
- let callbackCount = 0;
116
- await testDoc.load({
117
- listen: () => {
118
- callbackCount += 1;
119
- },
120
- });
121
- expect(testDoc.isListening).toBeTruthy();
122
- expect(callbackCount).toEqual(0);
123
- expect(testDoc.r.email).toBe("bob@example.com");
124
- await (0, firestore_deps_1.updateDoc)(testDoc.docRef, { email: "alice@example.com" });
125
- expect(callbackCount).toEqual(1);
126
- expect(testDoc.r.email).toBe("alice@example.com");
127
- testDoc.stopListening();
128
- expect(testDoc.isListening).toBeFalsy();
129
- await (0, firestore_deps_1.updateDoc)(testDoc.docRef, { email: "cindy@example.com" });
130
- expect(callbackCount).toEqual(1);
131
- expect(testDoc.r.email).toBe("alice@example.com");
132
- });
133
- it("can merge local and Firestore changse", async () => {
134
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, { email: "bob@example.com" });
135
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef);
136
- let callbackCount = 0;
137
- await testDoc.load({
138
- listen: () => {
139
- callbackCount += 1;
140
- },
141
- });
142
- expect(callbackCount).toEqual(0);
143
- expect(testDoc.r.email).toBe("bob@example.com");
144
- testDoc.w.ttl = new firestore_deps_1.Timestamp(123, 456000);
145
- await (0, firestore_deps_1.updateDoc)(testDoc.docRef, { email: "alice@example.com" });
146
- expect(callbackCount).toEqual(0);
147
- await testDoc.write();
148
- expect(callbackCount).toEqual(1);
149
- expect(testDoc.r).toEqual({
150
- email: "alice@example.com",
151
- ttl: new firestore_deps_1.Timestamp(123, 456000),
152
- recordOfPrimitives: {},
153
- recordOfObjects: {},
154
- nestedRecords: {},
155
- arrayOfObjects: [],
156
- });
157
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
158
- expect(result).toEqual({
159
- email: "alice@example.com",
160
- ttl: new firestore_deps_1.Timestamp(123, 456000),
161
- });
162
- });
163
- it("marks the doc as new if the remote doc is deleted", async () => {
164
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, { email: "bob@example.com" });
165
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef);
166
- let callbackCount = 0;
167
- await testDoc.load({
168
- listen: () => {
169
- callbackCount += 1;
170
- },
171
- });
172
- expect(callbackCount).toEqual(0);
173
- await (0, firestore_deps_1.deleteDoc)(testDoc.docRef);
174
- expect(callbackCount).toEqual(1);
175
- expect(testDoc.isNew).toBeTruthy();
176
- await testDoc.write();
177
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
178
- expect(result).toEqual({
179
- email: "bob@example.com",
180
- recordOfPrimitives: {},
181
- recordOfObjects: {},
182
- nestedRecords: {},
183
- arrayOfObjects: [],
184
- });
185
- });
186
- it("ignores server timestamp's initial update of null", async () => {
187
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, { email: "bob@example.com" });
188
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef);
189
- let callbackCount = 0;
190
- await testDoc.load({
191
- listen: () => {
192
- callbackCount += 1;
193
- },
194
- });
195
- const nowMillis = Date.now();
196
- await testDoc.update((doc) => {
197
- doc.ttl = (0, timestamps_1.serverTimestamp)();
198
- });
199
- // Wait a second for the timestamp to be set by the server.
200
- await new Promise((resolve) => setTimeout(resolve, 1000));
201
- expect(callbackCount).toEqual(1);
202
- const doc = await (0, firestore_deps_1.getDoc)(testDoc.docRef);
203
- const millisDiff = Math.abs(doc.data()?.ttl.toMillis() - nowMillis);
204
- expect(millisDiff).toBeLessThan(10000); // Less than 10 seconds apart.
205
- });
206
- });
207
- describe("patch", () => {
208
- it("applies patches to the data.", async () => {
209
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, {});
210
- const patcher = (data) => {
211
- data.email = "alice@example.com";
212
- return true;
213
- };
214
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef, {
215
- patchers: [patcher],
216
- savePatchAfterDelay: 0, // Save at next opportunity.
217
- });
218
- await testDoc.load();
219
- expect(testDoc.r.email).toBe("alice@example.com");
220
- await new Promise((resolve) => setTimeout(resolve, 500));
221
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
222
- expect(result).toEqual({
223
- email: "alice@example.com",
224
- recordOfPrimitives: {},
225
- recordOfObjects: {},
226
- nestedRecords: {},
227
- arrayOfObjects: [],
228
- });
229
- });
230
- });
231
- describe("read-only accessor (.r)", () => {
232
- it("reads a primitive field.", async () => {
233
- const testDoc = await createAndLoadDoc({
234
- email: "bob@example.com",
235
- });
236
- expect(testDoc.r.email).toBe("bob@example.com");
237
- });
238
- it("does not contain a missing optional field.", async () => {
239
- const testDoc = await createAndLoadDoc({
240
- email: "bob@example.com",
241
- });
242
- expect("ttl" in testDoc.r).toBe(false);
243
- });
244
- });
245
- describe("writable accessor (.w)", () => {
246
- it("enforces schema rules when a field is set.", async () => {
247
- const testDoc = await createAndLoadDoc({
248
- email: "bob@example.com",
249
- });
250
- expect(() => {
251
- testDoc.w.email = "not a valid email";
252
- }).toThrowError("Invalid email");
253
- });
254
- it("allows symbol properties to pass through objects.", async () => {
255
- const testDoc = await createAndLoadDoc({
256
- email: "bob@example.com",
257
- recordOfObjects: {
258
- "ice cream": {
259
- rating: 10,
260
- },
261
- },
262
- });
263
- // Converting an Object to a string gets its Symbol.toStringTag property.
264
- expect(String(testDoc.w.recordOfObjects)).toBe("[object Object]");
265
- });
266
- it("allows symbol properties to pass through arrays.", async () => {
267
- const testDoc = await createAndLoadDoc({
268
- email: "bob@example.com",
269
- arrayOfObjects: [
270
- { name: "foo", entries: {} },
271
- { name: "bar", entries: { a: 111, b: 222 } },
272
- ],
273
- });
274
- // Converting an Array to a string gets its Symbol.toStringTag property.
275
- expect(String(testDoc.w.arrayOfObjects)).toBe("[object Object],[object Object]");
276
- });
277
- it("can replace all document data", async () => {
278
- const testDoc = await createAndLoadDoc({
279
- email: "bob@example.com",
280
- recordOfObjects: {
281
- "ice cream": {
282
- rating: 10,
283
- },
284
- },
285
- });
286
- testDoc.w = { email: "alice@example.com" };
287
- await testDoc.write();
288
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
289
- expect(result).toEqual({
290
- email: "alice@example.com",
291
- recordOfPrimitives: {},
292
- recordOfObjects: {},
293
- nestedRecords: {},
294
- arrayOfObjects: [],
295
- });
296
- });
297
- it("clears an optional field with delete operator", async () => {
298
- const testDoc = await createAndLoadDoc({
299
- email: "bob@example.com",
300
- ttl: new firestore_deps_1.Timestamp(123, 456000),
301
- });
302
- delete testDoc.w.ttl;
303
- await testDoc.write();
304
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
305
- expect(result).toEqual({
306
- email: "bob@example.com",
307
- });
308
- // Note that Typescript itself prevents you from deleting required fields.
309
- // Attempting "delete testDoc.w.email;" is a TS compiler error.
310
- });
311
- it("clears an optional field set to undefined", async () => {
312
- const testDoc = await createAndLoadDoc({
313
- email: "bob@example.com",
314
- ttl: new firestore_deps_1.Timestamp(123, 456000),
315
- });
316
- testDoc.w.ttl = undefined;
317
- await testDoc.write();
318
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
319
- expect(result).toEqual({
320
- email: "bob@example.com",
321
- });
322
- });
323
- it("deeply removes undefined fields", async () => {
324
- const testDoc = await createAndLoadDoc({
325
- email: "bob@example.com",
326
- recordOfObjects: {
327
- c: { rating: 3, tags: ["111", "222"], favoriteColor: "red" },
328
- d: { rating: 4, tags: ["333", "444"] },
329
- },
330
- arrayOfObjects: [
331
- { name: "abc", entries: { x: 1, y: 2 } },
332
- { name: "xyz", favoriteColor: "blue" },
333
- ],
334
- });
335
- await testDoc.update((doc) => {
336
- doc.recordOfObjects.c = { rating: 5, tags: [], favoriteColor: undefined };
337
- doc.arrayOfObjects[1] = {
338
- name: "xyz",
339
- entries: {},
340
- favoriteColor: undefined,
341
- };
342
- });
343
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
344
- expect(result).toEqual({
345
- email: "bob@example.com",
346
- recordOfObjects: {
347
- c: { rating: 5, tags: [] },
348
- d: { rating: 4, tags: ["333", "444"] },
349
- },
350
- arrayOfObjects: [
351
- { name: "abc", entries: { x: 1, y: 2 } },
352
- { name: "xyz", entries: {} },
353
- ],
354
- });
355
- });
356
- it("throws in readonly mode.", async () => {
357
- const testDoc = await createAndLoadDoc({
358
- email: "bob@example.com",
359
- }, { readonly: true });
360
- expect(testDoc.isReadonly).toBeTruthy();
361
- expect(() => {
362
- testDoc.w.email = "alice@example.com";
363
- }).toThrowError("An attempt was made to modify or write a read-only doc");
364
- });
365
- });
366
- describe("write", () => {
367
- it("sets a primitive field and updates Firestore.", async () => {
368
- const testDoc = await createAndLoadDoc({
369
- email: "bob@example.com",
370
- });
371
- await testDoc.write(); // Should be a no-op since nothing has been changed.
372
- testDoc.w.email = "alice@example.com";
373
- expect(testDoc.r.email).toBe("alice@example.com");
374
- await testDoc.write();
375
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
376
- expect(result).toEqual({ email: "alice@example.com" });
377
- });
378
- it("can update multiple fields.", async () => {
379
- const testDoc = await createAndLoadDoc({
380
- email: "bob@example.com",
381
- });
382
- testDoc.w.email = "alice@example.com";
383
- testDoc.w.ttl = new firestore_deps_1.Timestamp(123, 456000);
384
- await testDoc.write();
385
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
386
- expect(result).toEqual({
387
- email: "alice@example.com",
388
- ttl: new firestore_deps_1.Timestamp(123, 456000),
389
- });
390
- });
391
- it("provides context on errors when adding a doc.", async () => {
392
- // Admin can read anywhere, so this test does not throw an error.
393
- if (firestore_deps_1.FIRESTORE_DEPS_TYPE === "admin")
394
- return;
395
- const badRef = (0, firestore_deps_1.collection)(firestore, "not-in-access-rules");
396
- const badDoc = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, badRef, {
397
- email: "bob@example.com",
398
- });
399
- await expect(badDoc.write()).rejects.toThrowError();
400
- try {
401
- await badDoc.write();
402
- }
403
- catch (error) {
404
- expect(error.firetenderContext).toEqual({
405
- call: "addDoc",
406
- ref: "not-in-access-rules",
407
- data: {
408
- email: "bob@example.com",
409
- recordOfPrimitives: {},
410
- recordOfObjects: {},
411
- nestedRecords: {},
412
- arrayOfObjects: [],
413
- },
414
- });
415
- }
416
- });
417
- it("provides context on errors when updating a doc.", async () => {
418
- const testDoc = await createAndLoadDoc({
419
- email: "bob@example.com",
420
- });
421
- await (0, firestore_deps_1.deleteDoc)(testDoc.docRef);
422
- testDoc.w.email = "alice@example.com";
423
- await expect(testDoc.write()).rejects.toThrowError();
424
- try {
425
- await testDoc.write();
426
- }
427
- catch (error) {
428
- expect(error.firetenderContext).toEqual({
429
- call: "updateDoc",
430
- ref: testDoc.docRef.path,
431
- data: { email: "alice@example.com" },
432
- });
433
- }
434
- });
435
- it("throws in readonly mode.", async () => {
436
- const testDoc = await createAndLoadDoc({
437
- email: "bob@example.com",
438
- }, { readonly: true });
439
- await expect(testDoc.write()).rejects.toThrowError("An attempt was made to modify or write a read-only doc");
440
- });
441
- });
442
- describe("update", () => {
443
- it("loads, updates, and writes a document", async () => {
444
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, { email: "bob@example.com" });
445
- await new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef).update((data) => {
446
- data.email = "alice@example.com";
447
- data.arrayOfObjects.push({ name: "foo", entries: {} });
448
- });
449
- const result = (await (0, firestore_deps_1.getDoc)(docRef)).data();
450
- expect(result).toEqual({
451
- email: "alice@example.com",
452
- arrayOfObjects: [
453
- {
454
- name: "foo",
455
- entries: {},
456
- },
457
- ],
458
- });
459
- });
460
- it("throws in readonly mode.", async () => {
461
- const testDoc = await createAndLoadDoc({
462
- email: "bob@example.com",
463
- }, { readonly: true });
464
- expect(testDoc.isReadonly).toBeTruthy();
465
- await expect(testDoc.update((data) => {
466
- data.email = "alice@example.com";
467
- })).rejects.toThrowError("An attempt was made to modify or write a read-only doc");
468
- });
469
- it("updates a new doc.", async () => {
470
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, testCollection, {
471
- createDoc: true,
472
- initialData: {
473
- email: "bob@example.com",
474
- },
475
- });
476
- expect(testDoc.isNew).toBeTruthy();
477
- await testDoc.update((data) => {
478
- data.email = "alice@example.com";
479
- });
480
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
481
- expect(result).toEqual({
482
- email: "alice@example.com",
483
- recordOfPrimitives: {},
484
- nestedRecords: {},
485
- recordOfObjects: {},
486
- arrayOfObjects: [],
487
- });
488
- });
489
- });
490
- describe("record of primitives", () => {
491
- const initialState = {
492
- email: "bob@example.com",
493
- recordOfPrimitives: {
494
- foo: "xyz",
495
- },
496
- };
497
- it("reads an entry.", async () => {
498
- const testDoc = await createAndLoadDoc(initialState);
499
- expect(testDoc.r.recordOfPrimitives.foo).toBe("xyz");
500
- });
501
- it("modifies an existing entry.", async () => {
502
- const testDoc = await createAndLoadDoc(initialState);
503
- testDoc.w.recordOfPrimitives.foo = "abc";
504
- await testDoc.write();
505
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
506
- expect(result).toEqual({
507
- email: "bob@example.com",
508
- recordOfPrimitives: {
509
- foo: "abc",
510
- },
511
- });
512
- });
513
- it("adds an entry.", async () => {
514
- const testDoc = await createAndLoadDoc(initialState);
515
- testDoc.w.recordOfPrimitives.bar = "abc";
516
- await testDoc.write();
517
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
518
- expect(result).toEqual({
519
- email: "bob@example.com",
520
- recordOfPrimitives: {
521
- foo: "xyz",
522
- bar: "abc",
523
- },
524
- });
525
- });
526
- it("deletes an entry.", async () => {
527
- const testDoc = await createAndLoadDoc({
528
- email: "bob@example.com",
529
- recordOfPrimitives: { foo: "xyz" },
530
- });
531
- delete testDoc.w.recordOfPrimitives.foo;
532
- await testDoc.write();
533
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
534
- expect(result).toEqual({
535
- email: "bob@example.com",
536
- recordOfPrimitives: {},
537
- });
538
- });
539
- it("can set all record contents.", async () => {
540
- const testDoc = await createAndLoadDoc({
541
- email: "bob@example.com",
542
- recordOfPrimitives: { foo: "xyz" },
543
- });
544
- testDoc.w.recordOfPrimitives = { bar: "abc" };
545
- await testDoc.write();
546
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
547
- expect(result).toEqual({
548
- email: "bob@example.com",
549
- recordOfPrimitives: {
550
- bar: "abc",
551
- },
552
- });
553
- });
554
- });
555
- describe("record of objects", () => {
556
- const initialState = {
557
- email: "bob@example.com",
558
- recordOfObjects: {
559
- "ice cream": {
560
- rating: 10,
561
- },
562
- spinach: {
563
- rating: 5,
564
- tags: ["green", "healthy"],
565
- },
566
- },
567
- };
568
- it("reads an entry.", async () => {
569
- const testDoc = await createAndLoadDoc(initialState);
570
- expect("ice cream" in testDoc.r.recordOfObjects).toBe(true);
571
- expect(testDoc.r.recordOfObjects["ice cream"].rating).toBe(10);
572
- expect(testDoc.r.recordOfObjects["ice cream"].tags.length).toBe(0);
573
- expect(testDoc.r.recordOfObjects.spinach.tags.includes("green")).toBe(true);
574
- });
575
- it("modifies an entry.", async () => {
576
- const testDoc = await createAndLoadDoc(initialState);
577
- testDoc.w.recordOfObjects["ice cream"] = {
578
- rating: 8,
579
- tags: ["too much lactose"],
580
- };
581
- testDoc.w.recordOfObjects.spinach.rating = 6;
582
- await testDoc.write();
583
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
584
- expect(result).toEqual({
585
- email: "bob@example.com",
586
- recordOfObjects: {
587
- "ice cream": {
588
- rating: 8,
589
- tags: ["too much lactose"],
590
- },
591
- spinach: {
592
- rating: 6,
593
- tags: ["green", "healthy"],
594
- },
595
- },
596
- });
597
- });
598
- it("correctly updates a parent field followed by a child.", async () => {
599
- const testDoc = await createAndLoadDoc(initialState);
600
- testDoc.w.recordOfObjects["ice cream"] = { rating: 4, tags: ["abc"] };
601
- testDoc.w.recordOfObjects["ice cream"].rating = 5;
602
- testDoc.w.recordOfObjects["ice cream"].tags.push("xyz");
603
- // .updates is private, so we coerce testDoc into "any".
604
- const updates = testDoc.updates;
605
- expect(updates.size).toBe(1);
606
- expect(updates.keys().next().value).toBe("recordOfObjects.ice cream");
607
- expect(updates.values().next().value).toEqual({
608
- rating: 5,
609
- tags: ["abc", "xyz"],
610
- });
611
- await testDoc.write();
612
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
613
- expect(result).toEqual({
614
- email: "bob@example.com",
615
- recordOfObjects: {
616
- "ice cream": {
617
- rating: 5,
618
- tags: ["abc", "xyz"],
619
- },
620
- spinach: {
621
- rating: 5,
622
- tags: ["green", "healthy"],
623
- },
624
- },
625
- });
626
- });
627
- it("correctly updates a child field followed by a parent.", async () => {
628
- const testDoc = await createAndLoadDoc(initialState);
629
- testDoc.w.recordOfObjects["ice cream"].rating = 5;
630
- testDoc.w.recordOfObjects["ice cream"].tags.push("xyz");
631
- testDoc.w.recordOfObjects["ice cream"] = { rating: 4, tags: ["abc"] };
632
- // .updates is private, so we coerce testDoc into "any".
633
- const updates = testDoc.updates;
634
- expect(updates.size).toBe(1);
635
- expect(updates.keys().next().value).toBe("recordOfObjects.ice cream");
636
- expect(updates.values().next().value).toEqual({
637
- rating: 4,
638
- tags: ["abc"],
639
- });
640
- await testDoc.write();
641
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
642
- expect(result).toEqual({
643
- email: "bob@example.com",
644
- recordOfObjects: {
645
- "ice cream": {
646
- rating: 4,
647
- tags: ["abc"],
648
- },
649
- spinach: {
650
- rating: 5,
651
- tags: ["green", "healthy"],
652
- },
653
- },
654
- });
655
- });
656
- it("adds an entry", async () => {
657
- const testDoc = await createAndLoadDoc(initialState);
658
- testDoc.w.recordOfObjects.tacos = { rating: 9, tags: ["crunchy"] };
659
- await testDoc.write();
660
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
661
- expect(result).toEqual({
662
- email: "bob@example.com",
663
- recordOfObjects: {
664
- "ice cream": {
665
- rating: 10,
666
- },
667
- spinach: {
668
- rating: 5,
669
- tags: ["green", "healthy"],
670
- },
671
- tacos: {
672
- rating: 9,
673
- tags: ["crunchy"],
674
- },
675
- },
676
- });
677
- });
678
- it("deletes an entry", async () => {
679
- const testDoc = await createAndLoadDoc(initialState);
680
- delete testDoc.w.recordOfObjects.spinach;
681
- await testDoc.write();
682
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
683
- expect(result).toEqual({
684
- email: "bob@example.com",
685
- recordOfObjects: {
686
- "ice cream": {
687
- rating: 10,
688
- },
689
- },
690
- });
691
- });
692
- it("can set all record contents.", async () => {
693
- const testDoc = await createAndLoadDoc(initialState);
694
- testDoc.w.recordOfObjects = {
695
- tacos: {
696
- rating: 9,
697
- tags: ["crunchy"],
698
- },
699
- };
700
- await testDoc.write();
701
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
702
- expect(result).toEqual({
703
- email: "bob@example.com",
704
- recordOfObjects: {
705
- tacos: {
706
- rating: 9,
707
- tags: ["crunchy"],
708
- },
709
- },
710
- });
711
- });
712
- });
713
- describe("nested records", () => {
714
- const initialState = {
715
- email: "bob@example.com",
716
- nestedRecords: {
717
- x: {
718
- a: 111,
719
- b: 222,
720
- },
721
- y: {
722
- c: 333,
723
- },
724
- },
725
- };
726
- it("reads an entry.", async () => {
727
- const testDoc = await createAndLoadDoc(initialState);
728
- expect("x" in testDoc.r.nestedRecords).toBe(true);
729
- expect("a" in testDoc.r.nestedRecords.x).toBe(true);
730
- expect(testDoc.r.nestedRecords.x.a).toBe(111);
731
- expect(testDoc.r.nestedRecords.x.b).toBe(222);
732
- expect(testDoc.r.nestedRecords.y.c).toBe(333);
733
- });
734
- it("modifies an entry.", async () => {
735
- const testDoc = await createAndLoadDoc(initialState);
736
- testDoc.w.nestedRecords.x.b = 234;
737
- testDoc.w.nestedRecords.y = { d: 444 };
738
- await testDoc.write();
739
- expect(testDoc.r.nestedRecords.x.b).toBe(234);
740
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
741
- expect(result).toEqual({
742
- email: "bob@example.com",
743
- nestedRecords: {
744
- x: {
745
- a: 111,
746
- b: 234,
747
- },
748
- y: {
749
- d: 444,
750
- },
751
- },
752
- });
753
- });
754
- it("adds an entry", async () => {
755
- const testDoc = await createAndLoadDoc(initialState);
756
- testDoc.w.nestedRecords.y.d = 444;
757
- testDoc.w.nestedRecords.z = { e: 555 };
758
- await testDoc.write();
759
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
760
- expect(result).toEqual({
761
- email: "bob@example.com",
762
- nestedRecords: {
763
- x: {
764
- a: 111,
765
- b: 222,
766
- },
767
- y: {
768
- c: 333,
769
- d: 444,
770
- },
771
- z: {
772
- e: 555,
773
- },
774
- },
775
- });
776
- });
777
- it("deletes an entry", async () => {
778
- const testDoc = await createAndLoadDoc(initialState);
779
- delete testDoc.w.nestedRecords.x.a;
780
- delete testDoc.w.nestedRecords.y;
781
- await testDoc.write();
782
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
783
- expect(result).toEqual({
784
- email: "bob@example.com",
785
- nestedRecords: {
786
- x: {
787
- b: 222,
788
- },
789
- },
790
- });
791
- });
792
- });
793
- describe("array of objects", () => {
794
- const initialState = {
795
- email: "bob@example.com",
796
- arrayOfObjects: [
797
- { name: "foo", entries: {} },
798
- { name: "bar", entries: { a: 111, b: 222 }, favoriteColor: "blue" },
799
- ],
800
- };
801
- it("reads an entry.", async () => {
802
- const testDoc = await createAndLoadDoc(initialState);
803
- expect(testDoc.r.arrayOfObjects.length).toBe(2);
804
- expect(testDoc.r.arrayOfObjects[0].name).toBe("foo");
805
- expect(testDoc.r.arrayOfObjects[1].name).toBe("bar");
806
- expect("a" in testDoc.r.arrayOfObjects[1].entries).toBe(true);
807
- expect(testDoc.r.arrayOfObjects[1].entries.b).toBe(222);
808
- });
809
- it("modifies an entry.", async () => {
810
- const testDoc = await createAndLoadDoc(initialState);
811
- testDoc.w.arrayOfObjects[0] = {
812
- name: "constants",
813
- entries: { pi: 3.14159, e: 2.71828 },
814
- };
815
- testDoc.w.arrayOfObjects[1].name += "bell";
816
- testDoc.w.arrayOfObjects[1].entries.a = 123;
817
- await testDoc.write();
818
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
819
- expect(result).toEqual({
820
- email: "bob@example.com",
821
- arrayOfObjects: [
822
- { name: "constants", entries: { pi: 3.14159, e: 2.71828 } },
823
- { name: "barbell", entries: { a: 123, b: 222 }, favoriteColor: "blue" },
824
- ],
825
- });
826
- });
827
- it("adds an entry", async () => {
828
- const testDoc = await createAndLoadDoc(initialState);
829
- testDoc.w.arrayOfObjects.push({ name: "baz", entries: { c: 333 } });
830
- await testDoc.write();
831
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
832
- expect(result).toEqual({
833
- email: "bob@example.com",
834
- arrayOfObjects: [
835
- { name: "foo", entries: {} },
836
- { name: "bar", entries: { a: 111, b: 222 }, favoriteColor: "blue" },
837
- { name: "baz", entries: { c: 333 } },
838
- ],
839
- });
840
- });
841
- it("deletes an entry", async () => {
842
- const testDoc = await createAndLoadDoc(initialState);
843
- delete testDoc.w.arrayOfObjects[0];
844
- await testDoc.write();
845
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
846
- expect(result).toEqual({
847
- email: "bob@example.com",
848
- arrayOfObjects: [
849
- { name: "bar", entries: { a: 111, b: 222 }, favoriteColor: "blue" },
850
- ],
851
- });
852
- });
853
- it("handles an array index that is out of bounds.", async () => {
854
- const testDoc = await createAndLoadDoc(initialState);
855
- expect(delete testDoc.w.arrayOfObjects[99]).toBeTruthy();
856
- });
857
- it("fails to delete for malformed indices.", async () => {
858
- const testDoc = await createAndLoadDoc(initialState);
859
- expect(() => {
860
- delete testDoc.w.arrayOfObjects[-99];
861
- }).toThrow(TypeError);
862
- });
863
- it("deletes the correct entry when there are multiple copies.", async () => {
864
- const testDoc = await createAndLoadDoc({
865
- email: "bob@example.com",
866
- arrayOfObjects: [
867
- { name: "A", entries: {} },
868
- { name: "foo", entries: {} },
869
- { name: "B", entries: {} },
870
- { name: "foo", entries: {} },
871
- { name: "C", entries: {} },
872
- { name: "foo", entries: {} },
873
- { name: "D", entries: {} },
874
- { name: "foo", entries: {} },
875
- { name: "E", entries: {} },
876
- ],
877
- });
878
- delete testDoc.w.arrayOfObjects[5];
879
- await testDoc.write();
880
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
881
- expect(result).toEqual({
882
- email: "bob@example.com",
883
- arrayOfObjects: [
884
- { name: "A", entries: {} },
885
- { name: "foo", entries: {} },
886
- { name: "B", entries: {} },
887
- { name: "foo", entries: {} },
888
- { name: "C", entries: {} },
889
- { name: "D", entries: {} },
890
- { name: "foo", entries: {} },
891
- { name: "E", entries: {} },
892
- ],
893
- });
894
- });
895
- it("removes a field within an entry with the delete operator", async () => {
896
- const testDoc = await createAndLoadDoc(initialState);
897
- delete testDoc.w.arrayOfObjects[1].entries.a;
898
- await testDoc.write();
899
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
900
- expect(result).toEqual({
901
- email: "bob@example.com",
902
- arrayOfObjects: [
903
- { name: "foo", entries: {} },
904
- { name: "bar", entries: { b: 222 }, favoriteColor: "blue" },
905
- ],
906
- });
907
- });
908
- it("removes a field within an entry when set to undefined", async () => {
909
- const testDoc = await createAndLoadDoc(initialState);
910
- testDoc.w.arrayOfObjects[1].favoriteColor = undefined;
911
- await testDoc.write();
912
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
913
- expect(result).toEqual({
914
- email: "bob@example.com",
915
- arrayOfObjects: [
916
- { name: "foo", entries: {} },
917
- { name: "bar", entries: { a: 111, b: 222 } },
918
- ],
919
- });
920
- });
921
- it("can set the array contents.", async () => {
922
- const testDoc = await createAndLoadDoc(initialState);
923
- testDoc.w.arrayOfObjects = [
924
- { name: "baz", entries: {} },
925
- { name: "qux", entries: { a: 111, b: 222 } },
926
- ];
927
- await testDoc.write();
928
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
929
- expect(result).toEqual({
930
- email: "bob@example.com",
931
- arrayOfObjects: [
932
- { name: "baz", entries: {} },
933
- { name: "qux", entries: { a: 111, b: 222 } },
934
- ],
935
- });
936
- });
937
- it("truncates an array using its length property.", async () => {
938
- const testDoc = await createAndLoadDoc(initialState);
939
- testDoc.w.arrayOfObjects.length = 1;
940
- await testDoc.write();
941
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
942
- expect(result).toEqual({
943
- email: "bob@example.com",
944
- arrayOfObjects: [{ name: "foo", entries: {} }],
945
- });
946
- });
947
- });
948
- describe("array of discriminating unions", () => {
949
- const initialState = {
950
- email: "bob@example.com",
951
- arrayOfDiscUnions: [
952
- { type: "A", someNumber: 123 },
953
- { type: "B", someString: "yo" },
954
- { type: "C", someBoolean: true },
955
- ],
956
- };
957
- it("reads entries.", async () => {
958
- const testDoc = await createAndLoadDoc(initialState);
959
- expect(testDoc.r.arrayOfDiscUnions).toBeDefined();
960
- expect(testDoc.r.arrayOfDiscUnions.length).toBe(3);
961
- expect(testDoc.r.arrayOfDiscUnions[0].type).toBe("A");
962
- expect(testDoc.r.arrayOfDiscUnions[0].someNumber).toBe(123);
963
- expect(testDoc.r.arrayOfDiscUnions[1].type).toBe("B");
964
- expect(testDoc.r.arrayOfDiscUnions[1].someString).toBe("yo");
965
- expect(testDoc.r.arrayOfDiscUnions[2].type).toBe("C");
966
- expect(testDoc.r.arrayOfDiscUnions[2].someBoolean).toBe(true);
967
- });
968
- it("updates entries and changes type", async () => {
969
- const testDoc = await createAndLoadDoc(initialState);
970
- const entry0 = testDoc.w.arrayOfDiscUnions[0];
971
- expect(entry0.type).toBe("A");
972
- if (entry0.type === "A") {
973
- entry0.someNumber = 456;
974
- }
975
- testDoc.w.arrayOfDiscUnions[1] = {
976
- type: "C",
977
- someBoolean: false,
978
- };
979
- testDoc.w.arrayOfDiscUnions.push({
980
- type: "B",
981
- someString: "hello",
982
- });
983
- await testDoc.write();
984
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
985
- expect(result).toEqual({
986
- email: "bob@example.com",
987
- arrayOfDiscUnions: [
988
- { type: "A", someNumber: 456 },
989
- { type: "C", someBoolean: false },
990
- { type: "C", someBoolean: true },
991
- { type: "B", someString: "hello" },
992
- ],
993
- });
994
- });
995
- });
996
- describe("createNewDoc", () => {
997
- const initialState = {
998
- email: "bob@example.com",
999
- };
1000
- it("does not provide an ID or doc ref until write is called.", async () => {
1001
- const testDoc = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, initialState);
1002
- expect(() => testDoc.id).toThrowError("id can only be accessed after");
1003
- expect(() => testDoc.docRef).toThrowError("docRef can only be accessed after");
1004
- });
1005
- it("adds a document without a specified ID.", async () => {
1006
- const testDoc = await FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, initialState).write();
1007
- expect(testDoc.id).toMatch(/^[A-Za-z0-9]{12,}$/);
1008
- expect(testDoc.docRef).toBeDefined();
1009
- const result = testDoc.docRef
1010
- ? (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data()
1011
- : undefined;
1012
- expect(result).toEqual({
1013
- email: "bob@example.com",
1014
- recordOfPrimitives: {},
1015
- nestedRecords: {},
1016
- recordOfObjects: {},
1017
- arrayOfObjects: [],
1018
- });
1019
- });
1020
- it("adds a document with a given ID.", async () => {
1021
- const docID = "abcdef123456";
1022
- const testDoc = await FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, (0, firestore_deps_1.doc)(testCollection, docID), initialState).write();
1023
- expect(testDoc.id).toBe(docID);
1024
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
1025
- expect(result).toEqual({
1026
- email: "bob@example.com",
1027
- recordOfPrimitives: {},
1028
- nestedRecords: {},
1029
- recordOfObjects: {},
1030
- arrayOfObjects: [],
1031
- });
1032
- });
1033
- it("can change an added document, before and after writing.", async () => {
1034
- const testDoc = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, initialState);
1035
- testDoc.w.recordOfObjects.x = { rating: 5, tags: ["hi"] };
1036
- expect(testDoc.isNew).toBe(true);
1037
- expect(testDoc.isLoaded).toBe(true);
1038
- expect(testDoc.isPendingWrite).toBe(true);
1039
- await testDoc.write();
1040
- expect(testDoc.isPendingWrite).toBe(false);
1041
- const result1 = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
1042
- expect(result1).toEqual({
1043
- email: "bob@example.com",
1044
- recordOfPrimitives: {},
1045
- nestedRecords: {},
1046
- recordOfObjects: { x: { rating: 5, tags: ["hi"] } },
1047
- arrayOfObjects: [],
1048
- });
1049
- testDoc.w.recordOfPrimitives.a = "bye";
1050
- expect(testDoc.isPendingWrite).toBe(true);
1051
- await testDoc.write();
1052
- expect(testDoc.isPendingWrite).toBe(false);
1053
- const result2 = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
1054
- expect(result2).toEqual({
1055
- email: "bob@example.com",
1056
- recordOfPrimitives: { a: "bye" },
1057
- nestedRecords: {},
1058
- recordOfObjects: { x: { rating: 5, tags: ["hi"] } },
1059
- arrayOfObjects: [],
1060
- });
1061
- });
1062
- });
1063
- describe("copy", () => {
1064
- const initialState = {
1065
- email: "bob@example.com",
1066
- recordOfObjects: {
1067
- "ice cream": {
1068
- rating: 10,
1069
- },
1070
- spinach: {
1071
- rating: 5,
1072
- tags: ["green", "healthy"],
1073
- },
1074
- },
1075
- };
1076
- it("performs a deep copy of a document.", async () => {
1077
- const testDoc1 = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, initialState);
1078
- const testDoc2 = testDoc1.copy();
1079
- const iceCream = testDoc2.w.recordOfObjects["ice cream"];
1080
- iceCream.rating = 9;
1081
- iceCream.tags.push("melting");
1082
- await Promise.all([testDoc1.write(), testDoc2.write()]);
1083
- const result1 = (await (0, firestore_deps_1.getDoc)(testDoc1.docRef)).data();
1084
- expect(result1).toEqual({
1085
- email: "bob@example.com",
1086
- recordOfObjects: {
1087
- "ice cream": {
1088
- rating: 10,
1089
- tags: [],
1090
- },
1091
- spinach: {
1092
- rating: 5,
1093
- tags: ["green", "healthy"],
1094
- },
1095
- },
1096
- recordOfPrimitives: {},
1097
- nestedRecords: {},
1098
- arrayOfObjects: [],
1099
- });
1100
- const result2 = (await (0, firestore_deps_1.getDoc)(testDoc2.docRef)).data();
1101
- expect(result2).toEqual({
1102
- email: "bob@example.com",
1103
- recordOfObjects: {
1104
- "ice cream": {
1105
- rating: 9,
1106
- tags: ["melting"],
1107
- },
1108
- spinach: {
1109
- rating: 5,
1110
- tags: ["green", "healthy"],
1111
- },
1112
- },
1113
- recordOfPrimitives: {},
1114
- nestedRecords: {},
1115
- arrayOfObjects: [],
1116
- });
1117
- });
1118
- it("throws if the copied doc exists but was not loaded.", async () => {
1119
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, initialState);
1120
- const testDoc = new FiretenderDoc_1.FiretenderDoc(testDataSchema, docRef);
1121
- expect(() => testDoc.copy()).toThrowError();
1122
- });
1123
- it("can copy into a specified collection.", async () => {
1124
- const testDoc1 = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, initialState);
1125
- const testDoc2 = testDoc1.copy(testCollection);
1126
- await Promise.all([testDoc1.write(), testDoc2.write()]);
1127
- const result2 = (await (0, firestore_deps_1.getDoc)(testDoc2.docRef)).data();
1128
- expect(result2).toEqual({
1129
- email: "bob@example.com",
1130
- recordOfObjects: {
1131
- "ice cream": {
1132
- rating: 10,
1133
- tags: [],
1134
- },
1135
- spinach: {
1136
- rating: 5,
1137
- tags: ["green", "healthy"],
1138
- },
1139
- },
1140
- recordOfPrimitives: {},
1141
- nestedRecords: {},
1142
- arrayOfObjects: [],
1143
- });
1144
- });
1145
- it("can copy into a specified doc reference.", async () => {
1146
- const testDoc1 = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, initialState);
1147
- const testDoc2 = testDoc1.copy((0, firestore_deps_1.doc)(testCollection, "copy-with-doc-ref"));
1148
- await Promise.all([testDoc1.write(), testDoc2.write()]);
1149
- expect(testDoc2.id).toBe("copy-with-doc-ref");
1150
- const result2 = (await (0, firestore_deps_1.getDoc)(testDoc2.docRef)).data();
1151
- expect(result2).toEqual({
1152
- email: "bob@example.com",
1153
- recordOfObjects: {
1154
- "ice cream": {
1155
- rating: 10,
1156
- tags: [],
1157
- },
1158
- spinach: {
1159
- rating: 5,
1160
- tags: ["green", "healthy"],
1161
- },
1162
- },
1163
- recordOfPrimitives: {},
1164
- nestedRecords: {},
1165
- arrayOfObjects: [],
1166
- });
1167
- });
1168
- it("can copy into a specified ID.", async () => {
1169
- const testDoc1 = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, (0, firestore_deps_1.doc)(testCollection, "copy-original"), initialState);
1170
- const testDoc2 = testDoc1.copy("copy-with-id-string");
1171
- await Promise.all([testDoc1.write(), testDoc2.write()]);
1172
- expect(testDoc2.id).toBe("copy-with-id-string");
1173
- const result2 = (await (0, firestore_deps_1.getDoc)(testDoc2.docRef)).data();
1174
- expect(result2).toEqual({
1175
- email: "bob@example.com",
1176
- recordOfObjects: {
1177
- "ice cream": {
1178
- rating: 10,
1179
- tags: [],
1180
- },
1181
- spinach: {
1182
- rating: 5,
1183
- tags: ["green", "healthy"],
1184
- },
1185
- },
1186
- recordOfPrimitives: {},
1187
- nestedRecords: {},
1188
- arrayOfObjects: [],
1189
- });
1190
- });
1191
- it("can copy into a doc given by string[].", async () => {
1192
- const testDoc1 = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, (0, firestore_deps_1.collection)(testCollection, "id-A", "subcol-1"), initialState);
1193
- const testDoc2 = testDoc1.copy(["id-B", "copy-with-doc-ids"]);
1194
- expect(testDoc2.docRef.path).toBe("doctests/id-B/subcol-1/copy-with-doc-ids");
1195
- });
1196
- it("can copy into a collection using string[] (for new doc).", async () => {
1197
- const testDoc1 = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, (0, firestore_deps_1.collection)(testCollection, "id-A", "subcol-1"), initialState);
1198
- const testDoc2 = await testDoc1.copy(["id-B"]).write();
1199
- expect(testDoc2.docRef.path).toMatch(/^doctests\/id-B\/subcol-1\/[^/]+$/);
1200
- });
1201
- it("can copy into a collection using string[] (existing doc).", async () => {
1202
- const testDoc1 = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, (0, firestore_deps_1.collection)(testCollection, "id-A", "subcol-1"), initialState);
1203
- await testDoc1.write();
1204
- const testDoc2 = await testDoc1.copy(["id-B"]).write();
1205
- expect(testDoc2.docRef.path).toMatch(/^doctests\/id-B\/subcol-1\/[^/]+$/);
1206
- });
1207
- it("throws when given string[] with the wrong number of IDs.", async () => {
1208
- const testDoc1 = FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, (0, firestore_deps_1.collection)(testCollection, "id-A", "subcol-1", "id-B", "subcol-2"), initialState);
1209
- expect(() => testDoc1.copy(["A"])).toThrowError();
1210
- expect(() => testDoc1.copy(["A", "B", "C", "D"])).toThrowError();
1211
- });
1212
- });
1213
- describe("other zod types", () => {
1214
- it("handles z.any", async () => {
1215
- const schema = zod_1.z.object({
1216
- anything: zod_1.z.any(),
1217
- arrayOfAnythings: zod_1.z.array(zod_1.z.any()),
1218
- });
1219
- const data = {
1220
- anything: {
1221
- foo: "hello",
1222
- bar: 123,
1223
- },
1224
- arrayOfAnythings: [{ a: { x: 123, y: 234 }, b: 222 }, { c: 333 }],
1225
- };
1226
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, data);
1227
- const testDoc = await new FiretenderDoc_1.FiretenderDoc(schema, docRef).load();
1228
- expect(testDoc.r).toEqual(data);
1229
- await testDoc.update((data) => {
1230
- data.anything = "I can haz primitive?";
1231
- data.arrayOfAnythings[1] = data.arrayOfAnythings[0];
1232
- const a = data.arrayOfAnythings[1].a;
1233
- data.arrayOfAnythings.push(a);
1234
- });
1235
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
1236
- expect(result).toEqual({
1237
- anything: "I can haz primitive?",
1238
- arrayOfAnythings: [
1239
- { a: { x: 123, y: 234 }, b: 222 },
1240
- { a: { x: 123, y: 234 }, b: 222 },
1241
- { x: 123, y: 234 },
1242
- ],
1243
- });
1244
- });
1245
- it("handles enums", async () => {
1246
- const schema = zod_1.z.object({
1247
- x: zod_1.z.enum(["a", "b", "c"]),
1248
- });
1249
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, { x: "b" });
1250
- const testDoc = await new FiretenderDoc_1.FiretenderDoc(schema, docRef).load();
1251
- expect(testDoc.r).toEqual({ x: "b" });
1252
- await testDoc.update((data) => {
1253
- data.x = "c";
1254
- });
1255
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
1256
- expect(result).toEqual({ x: "c" });
1257
- });
1258
- it("handles nullable fields", async () => {
1259
- const schema = zod_1.z.object({
1260
- x: zod_1.z.number().nullable(),
1261
- y: zod_1.z.number().nullable(),
1262
- z: zod_1.z.object({ a: zod_1.z.string(), b: zod_1.z.string() }).nullable(),
1263
- });
1264
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, {
1265
- x: 111,
1266
- y: null,
1267
- z: { a: "foo", b: "bar" },
1268
- });
1269
- const testDoc = await new FiretenderDoc_1.FiretenderDoc(schema, docRef).load();
1270
- expect(testDoc.r).toEqual({ x: 111, y: null, z: { a: "foo", b: "bar" } });
1271
- await testDoc.update((data) => {
1272
- data.x = null;
1273
- data.y = 222;
1274
- if (data.z) {
1275
- data.z.b = "baz";
1276
- }
1277
- });
1278
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
1279
- expect(result).toEqual({ x: null, y: 222, z: { a: "foo", b: "baz" } });
1280
- });
1281
- it("handles discriminating unions", async () => {
1282
- const schema = zod_1.z.object({
1283
- someUnion: zod_1.z.discriminatedUnion("type", [
1284
- zod_1.z.object({
1285
- type: zod_1.z.literal("x"),
1286
- x: zod_1.z.object({
1287
- x2: zod_1.z.string(),
1288
- }),
1289
- }),
1290
- zod_1.z.object({
1291
- type: zod_1.z.literal("y"),
1292
- y: zod_1.z.object({
1293
- y2: zod_1.z.number(),
1294
- }),
1295
- }),
1296
- ]),
1297
- });
1298
- const docRef = await (0, firestore_deps_1.addDoc)(testCollection, {
1299
- someUnion: {
1300
- type: "x",
1301
- x: { x2: "123" },
1302
- },
1303
- });
1304
- const testDoc = await new FiretenderDoc_1.FiretenderDoc(schema, docRef).load();
1305
- expect(testDoc.r).toEqual({ someUnion: { type: "x", x: { x2: "123" } } });
1306
- await testDoc.update((data) => {
1307
- let x2 = "";
1308
- if (data.someUnion.type === "x") {
1309
- x2 = data.someUnion.x.x2;
1310
- }
1311
- data.someUnion = { type: "y", y: { y2: Number(x2) } };
1312
- });
1313
- const result = (await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data();
1314
- expect(result).toEqual({ someUnion: { type: "y", y: { y2: 123 } } });
1315
- });
1316
- });
1317
- describe("timestamps", () => {
1318
- it("writes Firestore's Timestamp type", async () => {
1319
- const now = new Date();
1320
- const testDoc = await FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, {
1321
- email: "bob@example.com",
1322
- ttl: firestore_deps_1.Timestamp.fromDate(now),
1323
- }).write();
1324
- const doc = await (0, firestore_deps_1.getDoc)(testDoc.docRef);
1325
- expect(doc.data()?.ttl.toDate()).toEqual(now);
1326
- });
1327
- it("reads Firestore's Timestamp type.", async () => {
1328
- const now = new Date();
1329
- const testDoc = await createAndLoadDoc({
1330
- email: "bob@example.com",
1331
- ttl: firestore_deps_1.Timestamp.fromDate(now),
1332
- });
1333
- expect(testDoc.r.ttl?.toDate()).toEqual(now);
1334
- });
1335
- it("generates server timestamps in new doc", async () => {
1336
- const testDoc = await FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, {
1337
- email: "bob@example.com",
1338
- ttl: (0, timestamps_1.serverTimestamp)(),
1339
- }).write();
1340
- const doc = await (0, firestore_deps_1.getDoc)(testDoc.docRef);
1341
- const millisDiff = Math.abs(doc.data()?.ttl.toMillis() - Date.now());
1342
- expect(millisDiff).toBeLessThan(10000); // Less than 10 seconds apart.
1343
- });
1344
- it("generates server timestamps in existing doc", async () => {
1345
- const pastDate = new Date(2001, 2, 3);
1346
- const testDoc = await FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, {
1347
- email: "bob@example.com",
1348
- ttl: firestore_deps_1.Timestamp.fromDate(pastDate),
1349
- }).write();
1350
- expect((await (0, firestore_deps_1.getDoc)(testDoc.docRef)).data()?.ttl.toDate()).toEqual(pastDate);
1351
- await testDoc.update((doc) => {
1352
- doc.ttl = (0, timestamps_1.serverTimestamp)();
1353
- });
1354
- const doc = await (0, firestore_deps_1.getDoc)(testDoc.docRef);
1355
- const millisDiff = Math.abs(doc.data()?.ttl.toMillis() - Date.now());
1356
- expect(millisDiff).toBeLessThan(10000); // Less than 10 seconds apart.
1357
- });
1358
- it("provides temporary client-generated timestamps", async () => {
1359
- const now = new Date();
1360
- const testDoc = await FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, {
1361
- email: "bob@example.com",
1362
- ttl: (0, timestamps_1.serverTimestampWithClientTime)(),
1363
- });
1364
- // Wait 100 ms before writing to avoid coincident timestamps. The test
1365
- // server runs locally, so this should always work....
1366
- await new Promise((resolve) => setTimeout(resolve, 100));
1367
- await testDoc.write();
1368
- // Was a temp timestamp defined, and is it approximately now?
1369
- const tempTimestamp = testDoc.r.ttl;
1370
- expect(tempTimestamp).toBeDefined();
1371
- let millisDiff = Math.abs(tempTimestamp.toMillis() - now.valueOf());
1372
- expect(millisDiff).toBeLessThan(10000); // Less than 10 seconds apart.
1373
- // Check that the temp timestamp's polyfills are working. Note that the
1374
- // firestore-admin version of Timestamp has underscores preceding its
1375
- // seconds and nanoseconds properties.
1376
- expect(tempTimestamp.isEqual(new firestore_deps_1.Timestamp(tempTimestamp.seconds ?? tempTimestamp._seconds, tempTimestamp.nanoseconds ?? tempTimestamp._nanoseconds)));
1377
- expect(tempTimestamp.toDate().getTime() === tempTimestamp.toMillis());
1378
- expect(tempTimestamp.toString().length > 0);
1379
- expect(tempTimestamp.valueOf().length > 0);
1380
- // On reading, is there a server timestamp that differs from the temp one?
1381
- await testDoc.load({ force: true });
1382
- const assignedTimestamp = testDoc.r.ttl;
1383
- expect(assignedTimestamp).toBeDefined();
1384
- expect(assignedTimestamp.toMillis() !== tempTimestamp.toMillis());
1385
- // Is the server timestamp approximately now?
1386
- millisDiff = Math.abs(assignedTimestamp.toMillis() - now.valueOf());
1387
- expect(millisDiff).toBeLessThan(10000); // Less than 10 seconds apart.
1388
- });
1389
- it("generates future timestamps", async () => {
1390
- const testDoc = await FiretenderDoc_1.FiretenderDoc.createNewDoc(testDataSchema, testCollection, {
1391
- email: "bob@example.com",
1392
- ttl: (0, timestamps_1.futureTimestamp)({ days: 30 }),
1393
- }).write();
1394
- const doc = await (0, firestore_deps_1.getDoc)(testDoc.docRef);
1395
- const futureMillis = Date.now() + 30 * 86400e3;
1396
- const millisDiff = Math.abs(doc.data()?.ttl.toMillis() - futureMillis);
1397
- expect(millisDiff).toBeLessThan(10000); // Less than 10 seconds apart.
1398
- });
1399
- });
1400
- //# sourceMappingURL=FiretenderDoc.spec.js.map