@sneat/api 0.1.2 → 0.1.4
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/esm2022/index.js +6 -0
- package/esm2022/index.js.map +1 -0
- package/esm2022/lib/sneat-api-service-factory.js +60 -0
- package/esm2022/lib/sneat-api-service-factory.js.map +1 -0
- package/esm2022/lib/sneat-api-service.interface.js +2 -0
- package/esm2022/lib/sneat-api-service.interface.js.map +1 -0
- package/esm2022/lib/sneat-api-service.js +107 -0
- package/esm2022/lib/sneat-api-service.js.map +1 -0
- package/esm2022/lib/sneat-api.module.js +18 -0
- package/esm2022/lib/sneat-api.module.js.map +1 -0
- package/esm2022/lib/sneat-firestore.service.js +75 -0
- package/esm2022/lib/sneat-firestore.service.js.map +1 -0
- package/esm2022/sneat-api.js +5 -0
- package/esm2022/sneat-api.js.map +1 -0
- package/lib/sneat-api-service-factory.d.ts +9 -0
- package/lib/sneat-api-service.d.ts +29 -0
- package/lib/sneat-api-service.interface.d.ts +20 -0
- package/lib/sneat-api.module.d.ts +7 -0
- package/lib/sneat-firestore.service.d.ts +30 -0
- package/package.json +14 -2
- package/sneat-api.d.ts +5 -0
- package/eslint.config.js +0 -7
- package/ng-package.json +0 -7
- package/project.json +0 -38
- package/src/lib/sneat-api-service-factory.spec.ts +0 -132
- package/src/lib/sneat-api-service-factory.ts +0 -59
- package/src/lib/sneat-api-service.interface.ts +0 -50
- package/src/lib/sneat-api-service.spec.ts +0 -298
- package/src/lib/sneat-api-service.ts +0 -138
- package/src/lib/sneat-api.module.spec.ts +0 -18
- package/src/lib/sneat-api.module.ts +0 -10
- package/src/lib/sneat-firestore.service.spec.ts +0 -465
- package/src/lib/sneat-firestore.service.ts +0 -157
- package/src/test-setup.ts +0 -3
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -19
- package/tsconfig.lib.prod.json +0 -7
- package/tsconfig.spec.json +0 -31
- package/vite.config.mts +0 -10
- /package/{src/index.ts → index.d.ts} +0 -0
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
|
|
2
|
-
import { Injector } from '@angular/core';
|
|
3
|
-
import {
|
|
4
|
-
CollectionReference,
|
|
5
|
-
DocumentReference,
|
|
6
|
-
DocumentSnapshot,
|
|
7
|
-
QuerySnapshot,
|
|
8
|
-
} from '@angular/fire/firestore';
|
|
9
|
-
import { Subject } from 'rxjs';
|
|
10
|
-
import {
|
|
11
|
-
SneatFirestoreService,
|
|
12
|
-
docSnapshotToDto,
|
|
13
|
-
IQueryArgs,
|
|
14
|
-
} from './sneat-firestore.service';
|
|
15
|
-
|
|
16
|
-
// Mock Firebase functions
|
|
17
|
-
const mockDoc = vi.fn();
|
|
18
|
-
const mockGetDoc = vi.fn();
|
|
19
|
-
const mockOnSnapshot = vi.fn();
|
|
20
|
-
const mockQuery = vi.fn();
|
|
21
|
-
const mockWhere = vi.fn();
|
|
22
|
-
const mockLimit = vi.fn();
|
|
23
|
-
|
|
24
|
-
vi.mock('@angular/fire/firestore', () => ({
|
|
25
|
-
doc: (...args: unknown[]) => mockDoc(...args),
|
|
26
|
-
getDoc: (...args: unknown[]) => mockGetDoc(...args),
|
|
27
|
-
onSnapshot: (...args: unknown[]) => mockOnSnapshot(...args),
|
|
28
|
-
query: (...args: unknown[]) => mockQuery(...args),
|
|
29
|
-
where: (...args: unknown[]) => mockWhere(...args),
|
|
30
|
-
limit: (...args: unknown[]) => mockLimit(...args),
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
interface TestBrief {
|
|
34
|
-
id: string;
|
|
35
|
-
name: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface TestDbo extends TestBrief {
|
|
39
|
-
email: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
describe('SneatFirestoreService', () => {
|
|
43
|
-
let service: SneatFirestoreService<TestBrief, TestDbo>;
|
|
44
|
-
let injector: Injector;
|
|
45
|
-
let dto2brief: (id: string, dto: TestDbo) => TestBrief;
|
|
46
|
-
|
|
47
|
-
beforeEach(() => {
|
|
48
|
-
TestBed.configureTestingModule({
|
|
49
|
-
providers: [],
|
|
50
|
-
});
|
|
51
|
-
injector = TestBed.inject(Injector);
|
|
52
|
-
dto2brief = (id: string, dto: TestDbo) => ({ id, name: dto.name });
|
|
53
|
-
|
|
54
|
-
// Clear all mocks
|
|
55
|
-
mockDoc.mockClear();
|
|
56
|
-
mockGetDoc.mockClear();
|
|
57
|
-
mockOnSnapshot.mockClear();
|
|
58
|
-
mockQuery.mockClear();
|
|
59
|
-
mockWhere.mockClear();
|
|
60
|
-
mockLimit.mockClear();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should be created', () => {
|
|
64
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
65
|
-
expect(service).toBeTruthy();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should use default dto2brief if not provided', () => {
|
|
69
|
-
// The constructor has a default value for dto2brief, so it will never throw
|
|
70
|
-
// when undefined is passed - it will just use the default
|
|
71
|
-
service = new SneatFirestoreService(injector);
|
|
72
|
-
expect(service).toBeTruthy();
|
|
73
|
-
|
|
74
|
-
// Test that the default dto2brief works correctly
|
|
75
|
-
const mockSnapshot = {
|
|
76
|
-
id: 'doc1',
|
|
77
|
-
exists: () => true,
|
|
78
|
-
data: () =>
|
|
79
|
-
({ id: 'doc1', name: 'Test', email: 'test1@example.com' }) as TestDbo,
|
|
80
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
81
|
-
|
|
82
|
-
const result = service.docSnapshotToContext(mockSnapshot);
|
|
83
|
-
expect(result.id).toBe('doc1');
|
|
84
|
-
expect(result.dto).toBeDefined();
|
|
85
|
-
expect((result.brief as TestBrief).id).toBe('doc1');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should throw error if dto2brief is explicitly null', () => {
|
|
89
|
-
// Use type assertion to bypass TypeScript and test runtime behavior
|
|
90
|
-
expect(() => {
|
|
91
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
-
new SneatFirestoreService(injector, null as any);
|
|
93
|
-
}).toThrow('dto2brief is required');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('watchByID', () => {
|
|
97
|
-
it('should watch document by ID', fakeAsync(() => {
|
|
98
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
99
|
-
const mockCollection = {
|
|
100
|
-
path: 'test-collection',
|
|
101
|
-
} as CollectionReference<TestDbo>;
|
|
102
|
-
const mockDocRef = {
|
|
103
|
-
id: 'doc1',
|
|
104
|
-
path: 'test-collection/doc1',
|
|
105
|
-
} as DocumentReference<TestDbo>;
|
|
106
|
-
const mockSnapshot = {
|
|
107
|
-
exists: () => true,
|
|
108
|
-
data: () => ({ id: 'doc1', name: 'Test', email: 'test1@example.com' }),
|
|
109
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
110
|
-
|
|
111
|
-
mockDoc.mockReturnValue(mockDocRef);
|
|
112
|
-
mockOnSnapshot.mockImplementation((docRef, next) => {
|
|
113
|
-
// Simulate snapshot
|
|
114
|
-
setTimeout(() => next(mockSnapshot), 0);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const result$ = service.watchByID(mockCollection, 'doc1');
|
|
118
|
-
let result: unknown;
|
|
119
|
-
result$.subscribe((data) => {
|
|
120
|
-
result = data;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
tick();
|
|
124
|
-
|
|
125
|
-
expect(mockDoc).toHaveBeenCalledWith(mockCollection, 'doc1');
|
|
126
|
-
expect(result).toEqual({
|
|
127
|
-
id: 'doc1',
|
|
128
|
-
dbo: { id: 'doc1', name: 'Test', email: 'test1@example.com' },
|
|
129
|
-
brief: { id: 'doc1', name: 'Test' },
|
|
130
|
-
});
|
|
131
|
-
}));
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe('watchByDocRef', () => {
|
|
135
|
-
it('should watch document by reference', fakeAsync(() => {
|
|
136
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
137
|
-
const mockDocRef = {
|
|
138
|
-
id: 'doc2',
|
|
139
|
-
path: 'test-collection/doc2',
|
|
140
|
-
} as DocumentReference<TestDbo>;
|
|
141
|
-
const mockSnapshot = {
|
|
142
|
-
exists: () => true,
|
|
143
|
-
data: () => ({ id: 'doc2', name: 'Test2', email: 'test2@example.com' }),
|
|
144
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
145
|
-
|
|
146
|
-
mockOnSnapshot.mockImplementation((docRef, next) => {
|
|
147
|
-
setTimeout(() => next(mockSnapshot), 0);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const result$ = service.watchByDocRef(mockDocRef);
|
|
151
|
-
let result: unknown;
|
|
152
|
-
result$.subscribe((data) => {
|
|
153
|
-
result = data;
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
tick();
|
|
157
|
-
|
|
158
|
-
expect(mockOnSnapshot).toHaveBeenCalled();
|
|
159
|
-
expect(result).toEqual({
|
|
160
|
-
id: 'doc2',
|
|
161
|
-
dbo: { id: 'doc2', name: 'Test2', email: 'test2@example.com' },
|
|
162
|
-
brief: { id: 'doc2', name: 'Test2' },
|
|
163
|
-
});
|
|
164
|
-
}));
|
|
165
|
-
|
|
166
|
-
it('should handle errors', fakeAsync(() => {
|
|
167
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
168
|
-
const mockDocRef = {
|
|
169
|
-
id: 'doc3',
|
|
170
|
-
path: 'test-collection/doc3',
|
|
171
|
-
} as DocumentReference<TestDbo>;
|
|
172
|
-
const testError = new Error('Firestore error');
|
|
173
|
-
|
|
174
|
-
mockOnSnapshot.mockImplementation((docRef, next, error) => {
|
|
175
|
-
setTimeout(() => error(testError), 0);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const result$ = service.watchByDocRef(mockDocRef);
|
|
179
|
-
let caughtError: unknown;
|
|
180
|
-
result$.subscribe({
|
|
181
|
-
error: (err) => {
|
|
182
|
-
caughtError = err;
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
tick();
|
|
187
|
-
|
|
188
|
-
expect(caughtError).toBe(testError);
|
|
189
|
-
}));
|
|
190
|
-
|
|
191
|
-
it('should handle complete', fakeAsync(() => {
|
|
192
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
193
|
-
const mockDocRef = {
|
|
194
|
-
id: 'doc5',
|
|
195
|
-
path: 'test-collection/doc5',
|
|
196
|
-
} as DocumentReference<TestDbo>;
|
|
197
|
-
|
|
198
|
-
mockOnSnapshot.mockImplementation((docRef, next, error, complete) => {
|
|
199
|
-
setTimeout(() => complete(), 0);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const result$ = service.watchByDocRef(mockDocRef);
|
|
203
|
-
let completed = false;
|
|
204
|
-
result$.subscribe({
|
|
205
|
-
complete: () => {
|
|
206
|
-
completed = true;
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
tick();
|
|
211
|
-
|
|
212
|
-
expect(completed).toBe(true);
|
|
213
|
-
}));
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe('getByDocRef', () => {
|
|
217
|
-
it('should get document by reference', fakeAsync(() => {
|
|
218
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
219
|
-
const mockDocRef = {
|
|
220
|
-
id: 'doc4',
|
|
221
|
-
path: 'test-collection/doc4',
|
|
222
|
-
} as DocumentReference<TestDbo>;
|
|
223
|
-
const mockSnapshot = {
|
|
224
|
-
exists: () => true,
|
|
225
|
-
data: () => ({ id: 'doc4', name: 'Test4', email: 'test4@example.com' }),
|
|
226
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
227
|
-
|
|
228
|
-
mockGetDoc.mockResolvedValue(mockSnapshot);
|
|
229
|
-
|
|
230
|
-
const result$ = service.getByDocRef(mockDocRef);
|
|
231
|
-
let result: unknown;
|
|
232
|
-
result$.subscribe((data) => {
|
|
233
|
-
result = data;
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
tick();
|
|
237
|
-
|
|
238
|
-
expect(mockGetDoc).toHaveBeenCalledWith(mockDocRef);
|
|
239
|
-
expect(result).toEqual({
|
|
240
|
-
id: 'doc4',
|
|
241
|
-
dbo: { id: 'doc4', name: 'Test4', email: 'test4@example.com' },
|
|
242
|
-
brief: { id: 'doc4', name: 'Test4' },
|
|
243
|
-
});
|
|
244
|
-
}));
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
describe('watchSnapshotsByFilter', () => {
|
|
248
|
-
it('should watch documents by filter', () => {
|
|
249
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
250
|
-
const mockCollection = {
|
|
251
|
-
path: 'test-collection',
|
|
252
|
-
} as CollectionReference<TestDbo>;
|
|
253
|
-
const queryArgs: IQueryArgs = {
|
|
254
|
-
filter: [{ field: 'name', operator: '==', value: 'Test' }],
|
|
255
|
-
};
|
|
256
|
-
const mockQueryObj = {};
|
|
257
|
-
|
|
258
|
-
mockWhere.mockReturnValue({});
|
|
259
|
-
mockQuery.mockReturnValue(mockQueryObj);
|
|
260
|
-
mockOnSnapshot.mockImplementation(() => {
|
|
261
|
-
// Just return to simulate subscription
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
const result$ = service.watchSnapshotsByFilter(mockCollection, queryArgs);
|
|
265
|
-
|
|
266
|
-
expect(mockQuery).toHaveBeenCalled();
|
|
267
|
-
expect(mockWhere).toHaveBeenCalledWith('name', '==', 'Test');
|
|
268
|
-
expect(result$).toBeTruthy();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('should use array-contains for fields ending with IDs', () => {
|
|
272
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
273
|
-
const mockCollection = {
|
|
274
|
-
path: 'test-collection',
|
|
275
|
-
} as CollectionReference<TestDbo>;
|
|
276
|
-
const queryArgs: IQueryArgs = {
|
|
277
|
-
filter: [{ field: 'userIDs', operator: '==', value: 'user1' }],
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
mockWhere.mockReturnValue({});
|
|
281
|
-
mockQuery.mockReturnValue({});
|
|
282
|
-
mockOnSnapshot.mockImplementation(() => {
|
|
283
|
-
// Just return
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
service.watchSnapshotsByFilter(mockCollection, queryArgs);
|
|
287
|
-
|
|
288
|
-
expect(mockWhere).toHaveBeenCalledWith(
|
|
289
|
-
'userIDs',
|
|
290
|
-
'array-contains',
|
|
291
|
-
'user1',
|
|
292
|
-
);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it('should apply limit when specified', () => {
|
|
296
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
297
|
-
const mockCollection = {
|
|
298
|
-
path: 'test-collection',
|
|
299
|
-
} as CollectionReference<TestDbo>;
|
|
300
|
-
const queryArgs: IQueryArgs = {
|
|
301
|
-
limit: 10,
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
mockLimit.mockReturnValue({});
|
|
305
|
-
mockQuery.mockReturnValue({});
|
|
306
|
-
mockOnSnapshot.mockImplementation(() => {
|
|
307
|
-
// Just return
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
service.watchSnapshotsByFilter(mockCollection, queryArgs);
|
|
311
|
-
|
|
312
|
-
expect(mockLimit).toHaveBeenCalledWith(10);
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
describe('watchByFilter', () => {
|
|
317
|
-
it('should watch and transform documents by filter', fakeAsync(() => {
|
|
318
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
319
|
-
const mockCollection = {
|
|
320
|
-
path: 'test-collection',
|
|
321
|
-
} as CollectionReference<TestDbo>;
|
|
322
|
-
const mockSnapshot1 = {
|
|
323
|
-
id: 'doc1',
|
|
324
|
-
exists: () => true,
|
|
325
|
-
data: () => ({ id: 'doc1', name: 'Test1', email: 'test1@example.com' }),
|
|
326
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
327
|
-
const mockQuerySnapshot = {
|
|
328
|
-
docs: [mockSnapshot1],
|
|
329
|
-
} as QuerySnapshot<TestDbo>;
|
|
330
|
-
|
|
331
|
-
mockQuery.mockReturnValue({});
|
|
332
|
-
mockOnSnapshot.mockImplementation(
|
|
333
|
-
(q, subj: Subject<QuerySnapshot<TestDbo>>) => {
|
|
334
|
-
setTimeout(() => subj.next(mockQuerySnapshot), 0);
|
|
335
|
-
},
|
|
336
|
-
);
|
|
337
|
-
|
|
338
|
-
const result$ = service.watchByFilter(mockCollection);
|
|
339
|
-
let result: unknown;
|
|
340
|
-
result$.subscribe((data) => {
|
|
341
|
-
result = data;
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
tick();
|
|
345
|
-
|
|
346
|
-
expect(result).toEqual([
|
|
347
|
-
{
|
|
348
|
-
id: 'doc1',
|
|
349
|
-
dto: { id: 'doc1', name: 'Test1', email: 'test1@example.com' },
|
|
350
|
-
brief: { id: 'doc1', name: 'Test1' },
|
|
351
|
-
},
|
|
352
|
-
]);
|
|
353
|
-
}));
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
describe('docSnapshotsToContext', () => {
|
|
357
|
-
it('should transform array of snapshots to contexts', () => {
|
|
358
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
359
|
-
const mockSnapshot1 = {
|
|
360
|
-
id: 'doc1',
|
|
361
|
-
exists: () => true,
|
|
362
|
-
data: () => ({ id: 'doc1', name: 'Test1', email: 'test1@example.com' }),
|
|
363
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
364
|
-
const mockSnapshot2 = {
|
|
365
|
-
id: 'doc2',
|
|
366
|
-
exists: () => true,
|
|
367
|
-
data: () => ({ id: 'doc2', name: 'Test2', email: 'test2@example.com' }),
|
|
368
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
369
|
-
|
|
370
|
-
const result = service.docSnapshotsToContext([
|
|
371
|
-
mockSnapshot1,
|
|
372
|
-
mockSnapshot2,
|
|
373
|
-
]);
|
|
374
|
-
|
|
375
|
-
expect(result).toHaveLength(2);
|
|
376
|
-
expect(result[0]).toEqual({
|
|
377
|
-
id: 'doc1',
|
|
378
|
-
dto: { id: 'doc1', name: 'Test1', email: 'test1@example.com' },
|
|
379
|
-
brief: { id: 'doc1', name: 'Test1' },
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
describe('docSnapshotToContext', () => {
|
|
385
|
-
it('should transform snapshot to context', () => {
|
|
386
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
387
|
-
const mockSnapshot = {
|
|
388
|
-
id: 'doc1',
|
|
389
|
-
exists: () => true,
|
|
390
|
-
data: () => ({ id: 'doc1', name: 'Test1', email: 'test1@example.com' }),
|
|
391
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
392
|
-
|
|
393
|
-
const result = service.docSnapshotToContext(mockSnapshot);
|
|
394
|
-
|
|
395
|
-
expect(result).toEqual({
|
|
396
|
-
id: 'doc1',
|
|
397
|
-
dto: { id: 'doc1', name: 'Test1', email: 'test1@example.com' },
|
|
398
|
-
brief: { id: 'doc1', name: 'Test1' },
|
|
399
|
-
});
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
it('should handle snapshot with no data', () => {
|
|
403
|
-
service = new SneatFirestoreService(injector, dto2brief);
|
|
404
|
-
const mockSnapshot = {
|
|
405
|
-
id: 'doc1',
|
|
406
|
-
exists: () => true,
|
|
407
|
-
data: () => undefined,
|
|
408
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
409
|
-
|
|
410
|
-
const result = service.docSnapshotToContext(mockSnapshot);
|
|
411
|
-
|
|
412
|
-
expect(result).toEqual({
|
|
413
|
-
id: 'doc1',
|
|
414
|
-
dto: undefined,
|
|
415
|
-
brief: undefined,
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
describe('docSnapshotToDto', () => {
|
|
421
|
-
it('should convert existing snapshot to dto', () => {
|
|
422
|
-
const mockSnapshot = {
|
|
423
|
-
exists: true,
|
|
424
|
-
data: () => ({ id: 'doc1', name: 'Test1', email: 'test1@example.com' }),
|
|
425
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
426
|
-
|
|
427
|
-
const result = docSnapshotToDto('doc1', dto2brief, mockSnapshot);
|
|
428
|
-
|
|
429
|
-
expect(result).toEqual({
|
|
430
|
-
id: 'doc1',
|
|
431
|
-
dbo: { id: 'doc1', name: 'Test1', email: 'test1@example.com' },
|
|
432
|
-
brief: { id: 'doc1', name: 'Test1' },
|
|
433
|
-
});
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it('should handle non-existing snapshot', () => {
|
|
437
|
-
const mockSnapshot = {
|
|
438
|
-
exists: false,
|
|
439
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
440
|
-
|
|
441
|
-
const result = docSnapshotToDto('doc1', dto2brief, mockSnapshot);
|
|
442
|
-
|
|
443
|
-
expect(result).toEqual({
|
|
444
|
-
id: 'doc1',
|
|
445
|
-
brief: null,
|
|
446
|
-
dbo: null,
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
it('should handle snapshot with undefined data', () => {
|
|
451
|
-
const mockSnapshot = {
|
|
452
|
-
exists: true,
|
|
453
|
-
data: () => undefined,
|
|
454
|
-
} as unknown as DocumentSnapshot<TestDbo>;
|
|
455
|
-
|
|
456
|
-
const result = docSnapshotToDto('doc1', dto2brief, mockSnapshot);
|
|
457
|
-
|
|
458
|
-
expect(result).toEqual({
|
|
459
|
-
id: 'doc1',
|
|
460
|
-
dbo: undefined,
|
|
461
|
-
brief: undefined,
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
});
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { Injector, runInInjectionContext } from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
doc,
|
|
4
|
-
getDoc,
|
|
5
|
-
CollectionReference,
|
|
6
|
-
DocumentReference,
|
|
7
|
-
DocumentSnapshot,
|
|
8
|
-
query,
|
|
9
|
-
where,
|
|
10
|
-
onSnapshot,
|
|
11
|
-
limit,
|
|
12
|
-
} from '@angular/fire/firestore';
|
|
13
|
-
import { IIdAndOptionalBriefAndOptionalDbo } from '@sneat/core';
|
|
14
|
-
import { QuerySnapshot, QueryOrderByConstraint } from 'firebase/firestore';
|
|
15
|
-
import { WhereFilterOp } from '@firebase/firestore-types';
|
|
16
|
-
import { INavContext } from '@sneat/core';
|
|
17
|
-
import { from, map, Observable, Subject } from 'rxjs';
|
|
18
|
-
|
|
19
|
-
export interface IFilter {
|
|
20
|
-
readonly field: string;
|
|
21
|
-
readonly operator: WhereFilterOp;
|
|
22
|
-
readonly value: unknown;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface IQueryArgs {
|
|
26
|
-
readonly limit?: number;
|
|
27
|
-
readonly filter?: readonly IFilter[];
|
|
28
|
-
readonly orderBy?: readonly QueryOrderByConstraint[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class SneatFirestoreService<Brief, Dbo extends Brief> {
|
|
32
|
-
constructor(
|
|
33
|
-
private readonly injector: Injector,
|
|
34
|
-
// private readonly afs: AngularFirestore,
|
|
35
|
-
private readonly dto2brief: (id: string, dto: Dbo) => Brief = (
|
|
36
|
-
id: string,
|
|
37
|
-
dto: Dbo,
|
|
38
|
-
) => ({
|
|
39
|
-
...(dto as unknown as Brief),
|
|
40
|
-
id,
|
|
41
|
-
}),
|
|
42
|
-
) {
|
|
43
|
-
if (!dto2brief) {
|
|
44
|
-
throw new Error('dto2brief is required');
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
watchByID<Dbo2 extends Dbo>(
|
|
49
|
-
collection: CollectionReference<Dbo2>,
|
|
50
|
-
id: string,
|
|
51
|
-
): Observable<IIdAndOptionalBriefAndOptionalDbo<Brief, Dbo2>> {
|
|
52
|
-
const docRef = runInInjectionContext(this.injector, () =>
|
|
53
|
-
doc(collection, id),
|
|
54
|
-
);
|
|
55
|
-
return this.watchByDocRef(docRef);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
watchByDocRef<Dbo2 extends Dbo>(
|
|
59
|
-
docRef: DocumentReference<Dbo2>,
|
|
60
|
-
): Observable<IIdAndOptionalBriefAndOptionalDbo<Brief, Dbo2>> {
|
|
61
|
-
return runInInjectionContext(this.injector, () => {
|
|
62
|
-
const subj = new Subject<DocumentSnapshot<Dbo2>>();
|
|
63
|
-
// const snapshots = docSnapshots<Dbo2>(docRef);
|
|
64
|
-
onSnapshot(
|
|
65
|
-
docRef,
|
|
66
|
-
(snapshot) => subj.next(snapshot),
|
|
67
|
-
(err) => subj.error(err),
|
|
68
|
-
() => subj.complete(),
|
|
69
|
-
);
|
|
70
|
-
// const snapshots = from(getDoc<Dbo2>(docRef));
|
|
71
|
-
return subj.asObservable().pipe(
|
|
72
|
-
// tap((snapshot) =>
|
|
73
|
-
// console.log(
|
|
74
|
-
// `SneatFirestoreService.watchByDocRef(${docRef.path}): snapshot:`,
|
|
75
|
-
// snapshot,
|
|
76
|
-
// ),
|
|
77
|
-
// ),
|
|
78
|
-
map((changes) =>
|
|
79
|
-
docSnapshotToDto<Brief, Dbo2>(docRef.id, this.dto2brief, changes),
|
|
80
|
-
),
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
getByDocRef<Dbo2 extends Dbo>(
|
|
86
|
-
docRef: DocumentReference<Dbo2>,
|
|
87
|
-
): Observable<INavContext<Brief, Dbo2>> {
|
|
88
|
-
return from(getDoc(docRef)).pipe(
|
|
89
|
-
map((changes) =>
|
|
90
|
-
docSnapshotToDto<Brief, Dbo2>(docRef.id, this.dto2brief, changes),
|
|
91
|
-
),
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
watchSnapshotsByFilter<Dbo2 extends Dbo>(
|
|
96
|
-
collectionRef: CollectionReference<Dbo2>,
|
|
97
|
-
queryArgs?: IQueryArgs,
|
|
98
|
-
): Observable<QuerySnapshot<Dbo2>> {
|
|
99
|
-
const operator = (f: IFilter) =>
|
|
100
|
-
f.field.endsWith('IDs') ? 'array-contains' : f.operator;
|
|
101
|
-
const q = query(
|
|
102
|
-
collectionRef,
|
|
103
|
-
...(queryArgs?.filter || []).map((f) =>
|
|
104
|
-
where(f.field, operator(f), f.value),
|
|
105
|
-
),
|
|
106
|
-
...(queryArgs?.orderBy || []),
|
|
107
|
-
...(queryArgs?.limit ? [limit(queryArgs.limit)] : []),
|
|
108
|
-
);
|
|
109
|
-
const subj = new Subject<QuerySnapshot<Dbo2>>();
|
|
110
|
-
onSnapshot(q, subj);
|
|
111
|
-
return subj;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
watchByFilter<Dbo2 extends Dbo>(
|
|
115
|
-
collectionRef: CollectionReference<Dbo2>,
|
|
116
|
-
queryArgs?: IQueryArgs,
|
|
117
|
-
): Observable<INavContext<Brief, Dbo2>[]> {
|
|
118
|
-
return this.watchSnapshotsByFilter(collectionRef, queryArgs).pipe(
|
|
119
|
-
map((querySnapshot) => {
|
|
120
|
-
return querySnapshot.docs.map(this.docSnapshotToContext.bind(this));
|
|
121
|
-
}),
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
docSnapshotsToContext<Dbo2 extends Dbo>(
|
|
126
|
-
docSnapshots: DocumentSnapshot<Dbo2>[],
|
|
127
|
-
): INavContext<Brief, Dbo2>[] {
|
|
128
|
-
return docSnapshots.map((doc) => {
|
|
129
|
-
return this.docSnapshotToContext(doc);
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
docSnapshotToContext<Dbo2 extends Dbo>(
|
|
134
|
-
doc: DocumentSnapshot<Dbo2>,
|
|
135
|
-
): INavContext<Brief, Dbo2> {
|
|
136
|
-
const { id } = doc;
|
|
137
|
-
const dto: Dbo2 | undefined = doc.data();
|
|
138
|
-
const brief = dto && this.dto2brief(id, dto);
|
|
139
|
-
return {
|
|
140
|
-
id,
|
|
141
|
-
dto,
|
|
142
|
-
brief,
|
|
143
|
-
} as unknown as INavContext<Brief, Dbo2>; // TODO: try to remove this cast
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function docSnapshotToDto<Brief, Dbo extends Brief>(
|
|
148
|
-
id: string,
|
|
149
|
-
dto2brief: (id: string, dto: Dbo) => Brief,
|
|
150
|
-
docSnapshot: DocumentSnapshot<Dbo>,
|
|
151
|
-
): INavContext<Brief, Dbo> {
|
|
152
|
-
if (!docSnapshot.exists) {
|
|
153
|
-
return { id, brief: null, dbo: null };
|
|
154
|
-
}
|
|
155
|
-
const dto: Dbo | undefined = docSnapshot.data();
|
|
156
|
-
return { id, dbo: dto, brief: dto ? dto2brief(id, dto) : undefined };
|
|
157
|
-
}
|
package/src/test-setup.ts
DELETED
package/tsconfig.json
DELETED
package/tsconfig.lib.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.lib.base.json",
|
|
3
|
-
"exclude": [
|
|
4
|
-
"vite.config.ts",
|
|
5
|
-
"vite.config.mts",
|
|
6
|
-
"vitest.config.ts",
|
|
7
|
-
"vitest.config.mts",
|
|
8
|
-
"src/**/*.test.ts",
|
|
9
|
-
"src/**/*.spec.ts",
|
|
10
|
-
"src/**/*.test.tsx",
|
|
11
|
-
"src/**/*.spec.tsx",
|
|
12
|
-
"src/**/*.test.js",
|
|
13
|
-
"src/**/*.spec.js",
|
|
14
|
-
"src/**/*.test.jsx",
|
|
15
|
-
"src/**/*.spec.jsx",
|
|
16
|
-
"src/test-setup.ts",
|
|
17
|
-
"src/lib/testing/**/*"
|
|
18
|
-
]
|
|
19
|
-
}
|