Skip to content

Commit 134e497

Browse files
authored
Don't splat in snapToData (#33)
* Don't splat in `snapToData` so classes returned by a converter aren't thrown away * `snapToData` should also handle null and non-objects that might be return by converters * Bump versions * Add tests back into canary/release script * Addressed some flaky tests * yarn.lock was showing in the github diff, mark as generated
1 parent 494dfc0 commit 134e497

File tree

9 files changed

+191
-91
lines changed

9 files changed

+191
-91
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
yarn.lock linguist-generated=true

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ jobs:
125125
publish:
126126
runs-on: ubuntu-latest
127127
name: Publish (NPM)
128-
needs: ['build']
128+
needs: ['build', 'test']
129129
if: ${{ github.ref == 'refs/heads/main' || github.event_name == 'release' }}
130130
steps:
131131
- name: Setup node

firestore/document/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@ export function snapToData<T=DocumentData>(
4444
idField?: string,
4545
}={}
4646
): {} | undefined {
47+
// TODO clean up the typings
48+
const data = snapshot.data() as any;
4749
// match the behavior of the JS SDK when the snapshot doesn't exist
48-
if (!snapshot.exists()) {
49-
return snapshot.data();
50+
// it's possible with data converters too that the user didn't return an object
51+
if (!snapshot.exists() || typeof data !== 'object' || data === null) {
52+
return data;
5053
}
51-
return {
52-
...snapshot.data(),
53-
...(options.idField ? { [options.idField]: snapshot.id } : null)
54-
};
54+
if (options.idField) { data[options.idField] = snapshot.id; }
55+
return data;
5556
}

firestore/lite/document/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@ export function snapToData<T=DocumentData>(
4444
idField?: string,
4545
}={}
4646
): {} | undefined {
47+
// TODO clean up the typings
48+
const data = snapshot.data() as any;
4749
// match the behavior of the JS SDK when the snapshot doesn't exist
48-
if (!snapshot.exists()) {
49-
return snapshot.data();
50+
// it's possible with data converters too that the user didn't return an object
51+
if (!snapshot.exists() || typeof data !== 'object' || data === null) {
52+
return data;
5053
}
51-
return {
52-
...snapshot.data(),
53-
...(options.idField ? { [options.idField]: snapshot.id } : null)
54-
};
54+
if (options.idField) { data[options.idField] = snapshot.id; }
55+
return data;
5556
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rxfire",
3-
"version": "6.0.0",
3+
"version": "6.0.1",
44
"private": true,
55
"description": "Firebase JavaScript library RxJS",
66
"author": "Firebase <[email protected]> (https://firebase.google.com/)",
@@ -73,7 +73,7 @@
7373
"build:docs": "cp README.md ./dist/ && cp -r ./docs ./dist/",
7474
"dev": "rollup -c -w",
7575
"echo:chrome": "echo 'Open Chrome DevTools: \nchrome://inspect/#devices'",
76-
"test": "FIREBASE_CLI_PREVIEWS=storageemulator firebase emulators:exec jest --project=rxfire-test-c497c",
76+
"test": "FIREBASE_CLI_PREVIEWS=storageemulator firebase emulators:exec \"jest --detectOpenHandles\" --project=rxfire-test-c497c ",
7777
"test:debug": "yarn echo:chrome && FIREBASE_CLI_PREVIEWS=storageemulator firebase emulators:exec ./test-debug.sh --project=rxfire-test-c497c"
7878
},
7979
"dependencies": {

test/database.test.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -316,15 +316,19 @@ describe('RxFire Database', () => {
316316
it('should process a new child_added event', done => {
317317
const aref = builtRef(rando());
318318
const obs = list(aref, { events: [ListenEvent.added] });
319-
obs
320-
.pipe(skip(2), take(1))
321-
.subscribe(changes => {
322-
const data = changes.map(change => change.snapshot.val());
323-
expect(data).toContainEqual({ name: 'anotha one' });
324-
})
325-
.add(done);
326319
set(aref, itemsObj).then(() => {
327-
push(aref, { name: 'anotha one' });
320+
let count = 0;
321+
obs
322+
.pipe(take(2))
323+
.subscribe(changes => {
324+
if (count++ === 0) {
325+
push(aref, { name: 'anotha one' });
326+
} else {
327+
const data = changes.map(change => change.snapshot.val());
328+
expect(data).toContainEqual({ name: 'anotha one' });
329+
done();
330+
}
331+
});
328332
});
329333
});
330334

test/firestore-lite.test.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ import {
2828
docData,
2929
collectionData,
3030
} from '../dist/firestore/lite';
31-
import {map, take, skip} from 'rxjs/operators';
31+
import { map } from 'rxjs/operators';
3232
import { default as TEST_PROJECT, firestoreEmulatorPort } from './config';
33-
import { doc as firestoreDoc, getDocs, collection as firestoreCollection, getDoc, Firestore as FirebaseFirestore, CollectionReference, getFirestore, DocumentReference, connectFirestoreEmulator, doc, setDoc, collection as baseCollection } from 'firebase/firestore/lite';
33+
import { doc as firestoreDoc, getDocs, collection as firestoreCollection, getDoc, Firestore as FirebaseFirestore, CollectionReference, getFirestore, DocumentReference, connectFirestoreEmulator, doc, setDoc, collection as baseCollection, QueryDocumentSnapshot } from 'firebase/firestore/lite';
3434
import { initializeApp, deleteApp, FirebaseApp } from 'firebase/app';
3535

3636
const createId = (): string => Math.random().toString(36).substring(5);
@@ -105,6 +105,46 @@ describe('RxFire firestore/lite', () => {
105105
});
106106
});
107107

108+
109+
describe('collection w/converter', () => {
110+
/**
111+
* This is a simple test to see if the collection() method
112+
* correctly emits snapshots.
113+
*
114+
* The test seeds two "people" into the collection. RxFire
115+
* creats an observable with the `collection()` method and
116+
* asserts that the two "people" are in the array.
117+
*/
118+
it('should emit snapshots', async (done: jest.DoneCallback) => {
119+
const {colRef, expectedNames} = await seedTest(firestore);
120+
121+
class Folk {
122+
constructor(public name: string) { }
123+
static fromFirestore(snap: QueryDocumentSnapshot) {
124+
const name = snap.data().name;
125+
if (name !== 'Shannon') {
126+
return new Folk(`${snap.data().name}!`);
127+
} else {
128+
return undefined;
129+
}
130+
}
131+
static toFirestore() {
132+
return {};
133+
}
134+
static collection = colRef.withConverter(Folk);
135+
}
136+
137+
collection(Folk.collection)
138+
.subscribe(docs => {
139+
const names = docs.map(doc => doc.data()?.name);
140+
const classes = docs.map(doc => doc.data()?.constructor?.name);
141+
expect(names).toEqual(['David!', undefined]);
142+
expect(classes).toEqual(['Folk', undefined]);
143+
done();
144+
});
145+
});
146+
});
147+
108148
describe('Data Mapping Functions', () => {
109149
/**
110150
* The `unwrap(id)` method will map a collection to its data payload and map the doc ID to a the specificed key.

test/firestore.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
} from '../dist/firestore';
3434
import {map, take, skip} from 'rxjs/operators';
3535
import { default as TEST_PROJECT, firestoreEmulatorPort } from './config';
36-
import { getDocs, collection as firestoreCollection, getDoc, DocumentReference, doc as firestoreDoc, Firestore as FirebaseFirestore, CollectionReference, getFirestore, updateDoc, connectFirestoreEmulator, doc, setDoc, DocumentChange, collection as baseCollection } from 'firebase/firestore';
36+
import { getDocs, collection as firestoreCollection, getDoc, DocumentReference, doc as firestoreDoc, Firestore as FirebaseFirestore, CollectionReference, getFirestore, updateDoc, connectFirestoreEmulator, doc, setDoc, DocumentChange, collection as baseCollection, QueryDocumentSnapshot } from 'firebase/firestore';
3737
import { initializeApp, deleteApp, FirebaseApp } from 'firebase/app';
3838

3939
const createId = (): string => Math.random().toString(36).substring(5);
@@ -115,6 +115,44 @@ describe('RxFire Firestore', () => {
115115
});
116116
});
117117

118+
describe('collection w/converter', () => {
119+
/**
120+
* This is a simple test to see if the collection() method
121+
* correctly emits snapshots.
122+
*
123+
* The test seeds two "people" into the collection. RxFire
124+
* creats an observable with the `collection()` method and
125+
* asserts that the two "people" are in the array.
126+
*/
127+
it('should emit snapshots', (done: jest.DoneCallback) => {
128+
const {colRef, expectedNames} = seedTest(firestore);
129+
130+
class Folk {
131+
constructor(public name: string) { }
132+
static fromFirestore(snap: QueryDocumentSnapshot) {
133+
const name = snap.data().name;
134+
if (name !== 'Shannon') {
135+
return new Folk(`${snap.data().name}!`);
136+
} else {
137+
return undefined;
138+
}
139+
}
140+
static toFirestore() {
141+
return {};
142+
}
143+
}
144+
145+
collection(colRef.withConverter(Folk))
146+
.subscribe(docs => {
147+
const names = docs.map(doc => doc.data()?.name);
148+
const classes = docs.map(doc => doc.data()?.constructor?.name);
149+
expect(names).toEqual(['David!', undefined]);
150+
expect(classes).toEqual(['Folk', undefined]);
151+
done();
152+
});
153+
});
154+
});
155+
118156
describe('collectionChanges', () => {
119157
/**
120158
* The `stateChanges()` method emits a stream of events as they

0 commit comments

Comments
 (0)