Skip to content

Commit

Permalink
fix(afs): allow stateChanges and auditLog to emit blank arrays at first
Browse files Browse the repository at this point in the history
This allows a developer to detirmine that the collection being observed is empty
and keeps the subscription from holding up isStable.
  • Loading branch information
jamesdaniels committed Nov 30, 2020
1 parent 829df50 commit 21442f0
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 17 deletions.
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "@angular/fire",
"version": "6.1.2",
"version": "6.1.3",
"description": "The official Angular library for Firebase.",
"private": true,
"scripts": {
Expand Down
9 changes: 5 additions & 4 deletions src/auth/auth.spec.ts
Expand Up @@ -118,20 +118,21 @@ describe('AngularFireAuth', () => {

});

const FIREBASE_APP_NAME_TOO = (Math.random() + 1).toString(36).substring(7);

describe('AngularFireAuth with different app', () => {
let app: FirebaseApp;
let afAuth: AngularFireAuth;
let firebaseAppName: string;

beforeEach(() => {
firebaseAppName = rando();

TestBed.configureTestingModule({
imports: [
AngularFireModule.initializeApp(COMMON_CONFIG, rando()),
AngularFireAuthModule
],
providers: [
{ provide: FIREBASE_APP_NAME, useValue: FIREBASE_APP_NAME_TOO },
{ provide: FIREBASE_APP_NAME, useValue: firebaseAppName },
{ provide: FIREBASE_OPTIONS, useValue: COMMON_CONFIG }
]
});
Expand All @@ -156,7 +157,7 @@ describe('AngularFireAuth with different app', () => {

it('should have an initialized Firebase app instance member', async () => {
const app = await afAuth.app;
expect(app.name).toEqual(FIREBASE_APP_NAME_TOO);
expect(app.name).toEqual(firebaseAppName);
});
});

Expand Down
21 changes: 18 additions & 3 deletions src/firestore/collection/collection.spec.ts
Expand Up @@ -399,7 +399,7 @@ describe('AngularFirestoreCollection', () => {
const ITEMS = 10;
const { ref, stocks, names } = await collectionHarness(afs, ITEMS);

const sub = stocks.stateChanges(['modified']).subscribe(data => {
const sub = stocks.stateChanges(['modified']).pipe(skip(1), take(1)).subscribe(data => {
sub.unsubscribe();
expect(data.length).toEqual(1);
expect(data[0].payload.doc.data().price).toEqual(2);
Expand Down Expand Up @@ -436,7 +436,7 @@ describe('AngularFirestoreCollection', () => {
const ITEMS = 10;
const { ref, stocks, names } = await collectionHarness(afs, ITEMS);

const sub = stocks.stateChanges(['removed']).subscribe(data => {
const sub = stocks.stateChanges(['removed']).pipe(skip(1), take(1)).subscribe(data => {
sub.unsubscribe();
expect(data.length).toEqual(1);
expect(data[0].type).toEqual('removed');
Expand All @@ -446,6 +446,21 @@ describe('AngularFirestoreCollection', () => {

delayDelete(stocks, names[0], 400);
});

it('stateChanges() should emit on empty collection', async (done) => {
afs.collection('EMPTY_COLLECTION').stateChanges().pipe(take(1)).subscribe(data => {
expect(data.length).toEqual(0);
done();
});
});

it('stateChanges() w/filter should emit on empty collection', async (done) => {
afs.collection('EMPTY_COLLECTION').stateChanges(['added']).pipe(take(1)).subscribe(data => {
expect(data.length).toEqual(0);
done();
});
});

});

describe('auditTrail()', () => {
Expand All @@ -471,7 +486,7 @@ describe('AngularFirestoreCollection', () => {
const ITEMS = 10;
const { ref, stocks, names } = await collectionHarness(afs, ITEMS);

const sub = stocks.auditTrail(['removed']).subscribe(data => {
const sub = stocks.auditTrail(['removed']).pipe(skip(1), take(1)).subscribe(data => {
sub.unsubscribe();
expect(data.length).toEqual(1);
expect(data[0].type).toEqual('removed');
Expand Down
20 changes: 12 additions & 8 deletions src/firestore/collection/collection.ts
@@ -1,6 +1,6 @@
import { from, Observable } from 'rxjs';
import { fromCollectionRef } from '../observable/fromRef';
import { filter, map, observeOn, scan } from 'rxjs/operators';
import { filter, map, observeOn, pairwise, scan, startWith } from 'rxjs/operators';
import firebase from 'firebase/app';

import { CollectionReference, DocumentChangeAction, DocumentChangeType, DocumentData, DocumentReference, Query } from '../interfaces';
Expand Down Expand Up @@ -59,15 +59,19 @@ export class AngularFirestoreCollection<T = DocumentData> {
* your own data structure.
*/
stateChanges(events?: DocumentChangeType[]): Observable<DocumentChangeAction<T>[]> {
if (!events || events.length === 0) {
return docChanges<T>(this.query, this.afs.schedulers.outsideAngular).pipe(
filter(changes => changes.length > 0),
this.afs.keepUnstableUntilFirst
let source = docChanges<T>(this.query, this.afs.schedulers.outsideAngular);
if (events && events.length > 0) {
source = source.pipe(
map(actions => actions.filter(change => events.indexOf(change.type) > -1))
);
}
return docChanges<T>(this.query, this.afs.schedulers.outsideAngular).pipe(
map(actions => actions.filter(change => events.indexOf(change.type) > -1)),
filter(changes => changes.length > 0),
return source.pipe(
// We want to filter out empty arrays, but always emit at first, so the developer knows
// that the collection has been resolve; even if it's empty
startWith(undefined),
pairwise(),
filter(([prior, current]) => current.length > 0 || !prior),
map(([prior, current]) => current),
this.afs.keepUnstableUntilFirst
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/firestore/utils.spec.ts
Expand Up @@ -54,4 +54,4 @@ export function delayDelete<T>(collection: AngularFirestoreCollection<T>|firebas
}, delay);
}

export const rando = () => (Math.random() + 1).toString(36).substring(7);
export const rando = () => (Math.random() + 1).toString(36).split('.')[1];

0 comments on commit 21442f0

Please sign in to comment.