Skip to content

Commit 83a6e55

Browse files
committed
fix(firestore): compare #docChanges() to previous listener's snapshot
Actual Firebase behaviour is that adding another #onSnapshot() doesn't change the outcome of #docChanges() for each of the listeners. This means that they each compare only to themselves, and every #get() results in the snapshot comparing to nothing. Fixes #18
1 parent 22d450a commit 83a6e55

4 files changed

Lines changed: 32 additions & 24 deletions

File tree

firestore/document-reference.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ it("exposes #path", () => {
2222
expect(doc.path).toBe("/foo/bar");
2323
});
2424

25+
it("exposes #parent", () => {
26+
const doc = firestore.doc("foo/bar");
27+
expect(doc.parent.path).toBe("/foo");
28+
});
29+
2530
it("shares same data as other instances", async () => {
2631
const doc1 = firestore.doc("foo/bar");
2732
await doc1.set({ foo: "bar" });
@@ -286,4 +291,16 @@ describe("#delete()", () => {
286291

287292
expect(listener).toHaveBeenCalledTimes(1);
288293
});
294+
295+
it("emits change events on the parent collection", async () => {
296+
const coll = firestore.collection("foo");
297+
const doc = coll.doc("bar");
298+
await doc.set({ bla: "blabla" });
299+
300+
const listener = jest.fn();
301+
coll.onSnapshot(listener);
302+
await doc.delete();
303+
304+
expect(listener).toHaveBeenCalledTimes(2);
305+
});
289306
});

firestore/query-snapshot.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { MockFirestore, MockQuerySnapshot } from "./";
44
import { MockQueryDocumentSnapshot } from "./document-snapshot";
55
import { MockQuery } from "./query";
66

7+
let snapshot: MockQuerySnapshot | undefined;
78
let query: MockQuery;
89
beforeEach(() => {
10+
snapshot = undefined;
911
query = new MockFirestore(createMockInstance(MockApp)).collection("foo");
1012
});
1113

1214
const createSnapshot = (docs: MockQueryDocumentSnapshot<any>[]) => {
13-
const snapshot = new MockQuerySnapshot(query, docs);
14-
query.snapshotVersions.push(snapshot);
15+
snapshot = new MockQuerySnapshot(query, docs, snapshot);
1516
return snapshot;
1617
};
1718

@@ -35,7 +36,7 @@ it("exposes #size as the length of #docs", () => {
3536
expect(query.size).toBe(docs.length);
3637
});
3738

38-
it("exposes #empty as the true when #docs.length = 0", () => {
39+
it("exposes #empty as true when #docs.length = 0", () => {
3940
const docs = [createMockInstance(MockQueryDocumentSnapshot)];
4041

4142
const query1 = createSnapshot(docs);

firestore/query-snapshot.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ import { MockQueryDocumentSnapshot } from "./document-snapshot";
44

55
export class MockQuerySnapshot<T = firebase.firestore.DocumentData>
66
implements firebase.firestore.QuerySnapshot<T> {
7-
private get previousSnapshot(): MockQuerySnapshot<T> | undefined {
8-
return this.query.snapshotVersions[this.version - 1];
9-
}
10-
117
readonly metadata: firebase.firestore.SnapshotMetadata = {
128
fromCache: true,
139
hasPendingWrites: false,
@@ -25,7 +21,7 @@ export class MockQuerySnapshot<T = firebase.firestore.DocumentData>
2521
constructor(
2622
public readonly query: MockQuery<T>,
2723
public readonly docs: MockQueryDocumentSnapshot<T>[],
28-
private readonly version: number = query.snapshotVersions.length
24+
private readonly previousSnapshot?: MockQuerySnapshot<T> | undefined
2925
) {}
3026

3127
docChanges(

firestore/query.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export class MockQuery<T = firebase.firestore.DocumentData> implements firebase.
2424
protected filters: Record<string, QueryFilter> = {};
2525
protected ordering?: Ordering;
2626
private noInitialSnapshot = false;
27-
public snapshotVersions: MockQuerySnapshot<T>[] = [];
2827

2928
protected get emitter() {
3029
const emitter = this.firestore.collectionEvents.get(this.path) || new EventEmitter();
@@ -45,7 +44,6 @@ export class MockQuery<T = firebase.firestore.DocumentData> implements firebase.
4544

4645
// TODO: this emits even if there wasn't an actual change with the current filters
4746
const snapshot = await this.get();
48-
this.snapshotVersions.push(snapshot);
4947
this.emitter.emit(QUERY_SNAPSHOT_NEXT_EVENT, [snapshot]);
5048
}
5149

@@ -58,8 +56,6 @@ export class MockQuery<T = firebase.firestore.DocumentData> implements firebase.
5856
const query = new MockQuery(this.firestore, this.path, this.converter);
5957
Object.assign(query, this);
6058
query.filters = Object.assign({}, this.filters);
61-
// Extending the original query with new filters/ordering voids the snapshot version cache.
62-
query.snapshotVersions = [];
6359
return query;
6460
}
6561

@@ -299,7 +295,7 @@ export class MockQuery<T = firebase.firestore.DocumentData> implements firebase.
299295
onCompletion?: (() => void) | undefined
300296
): () => void;
301297
onSnapshot(options: any, onNext?: any, onError?: any, onCompletion?: any): () => void {
302-
let actualListeners: Observer<firebase.firestore.QuerySnapshot<T>>;
298+
let actualListeners: Observer<MockQuerySnapshot<T>>;
303299

304300
if (typeof options === "object") {
305301
if (typeof onNext === "object") {
@@ -319,31 +315,29 @@ export class MockQuery<T = firebase.firestore.DocumentData> implements firebase.
319315
};
320316
}
321317

318+
let prevSnapshot: MockQuerySnapshot<T>;
322319
const { next, error } = actualListeners;
323-
this.emitter.on(QUERY_SNAPSHOT_NEXT_EVENT, next);
320+
const actualNext = (snapshot: MockQuerySnapshot<T>) => {
321+
const surrogate = new MockQuerySnapshot(this, snapshot.docs, prevSnapshot);
322+
next(surrogate);
323+
prevSnapshot = surrogate;
324+
};
325+
this.emitter.on(QUERY_SNAPSHOT_NEXT_EVENT, actualNext);
324326
error && this.emitter.on(QUERY_SNAPSHOT_ERROR_EVENT, error);
325327

326328
if (!this.noInitialSnapshot) {
327-
this.get().then((snapshot) => next(snapshot));
329+
this.get().then((snapshot) => actualNext(snapshot));
328330
}
329331

330332
return () => {
331-
this.emitter.off(QUERY_SNAPSHOT_NEXT_EVENT, next);
333+
this.emitter.off(QUERY_SNAPSHOT_NEXT_EVENT, actualNext);
332334
error && this.emitter.off(QUERY_SNAPSHOT_ERROR_EVENT, error);
333335
};
334336
}
335337

336338
withConverter<U>(converter: firebase.firestore.FirestoreDataConverter<U>): MockQuery<U> {
337339
const query = (this.clone() as unknown) as MockQuery<U>;
338340
query.converter = converter;
339-
query.snapshotVersions = this.snapshotVersions.map(
340-
(snapshot, version) =>
341-
new MockQuerySnapshot(
342-
query,
343-
(snapshot.docs as unknown) as MockQueryDocumentSnapshot<U>[],
344-
version
345-
)
346-
);
347341
return query;
348342
}
349343
}

0 commit comments

Comments
 (0)