Skip to content

Commit

Permalink
chore(tests): Absolute urls
Browse files Browse the repository at this point in the history
  • Loading branch information
davideast committed Mar 12, 2017
1 parent 59b06a9 commit 72dab0d
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 47 deletions.
36 changes: 13 additions & 23 deletions src/database/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,26 @@ import 'firebase/database';
import { Inject, Injectable } from '@angular/core';
import { FirebaseAppConfigToken, FirebaseAppConfig, FirebaseApp } from '../angularfire2';
import { FirebaseListFactory } from './index';
import { FirebaseListFactoryOpts, FirebaseObjectFactoryOpts } from '../interfaces';
import { FirebaseListFactoryOpts, FirebaseObjectFactoryOpts, PathReference } from '../interfaces';
import * as utils from '../utils';
import {
FirebaseListObservable,
FirebaseObjectObservable,
FirebaseObjectFactory,
} from './index';
import { FirebaseListObservable, FirebaseObjectObservable, FirebaseObjectFactory } from './index';

@Injectable()
export class AngularFireDatabase {

constructor(private app: FirebaseApp) {}
list (urlOrRef:string | firebase.database.Reference, opts?:FirebaseListFactoryOpts):FirebaseListObservable<any[]> {
return utils.checkForUrlOrFirebaseRef(urlOrRef, {
isUrl: () => FirebaseListFactory(this.app.database().ref(<string>urlOrRef), opts),
isRef: () => FirebaseListFactory(<firebase.database.Reference>urlOrRef)
});

list(pathOrRef: PathReference, opts?:FirebaseListFactoryOpts):FirebaseListObservable<any[]> {
const ref = utils.getRef(this.app, pathOrRef);
return FirebaseListFactory(utils.getRef(this.app, ref), opts);
}
object(urlOrRef: string | firebase.database.Reference, opts?:FirebaseObjectFactoryOpts):FirebaseObjectObservable<any> {
return utils.checkForUrlOrFirebaseRef(urlOrRef, {
isUrl: () => FirebaseObjectFactory(this.app.database().ref(<string>urlOrRef), opts),
isRef: () => FirebaseObjectFactory(urlOrRef)

object(pathOrRef: PathReference, opts?:FirebaseObjectFactoryOpts):FirebaseObjectObservable<any> {
return utils.checkForUrlOrFirebaseRef(pathOrRef, {
isUrl: () => FirebaseObjectFactory(this.app.database().ref(<string>pathOrRef), opts),
isRef: () => FirebaseObjectFactory(pathOrRef)
});
}
}

function getAbsUrl (root:FirebaseAppConfig, url:string) {
if (!(/^[a-z]+:\/\/.*/.test(url))) {
// Provided url is relative.
// Strip any leading slash
url = root.databaseURL + '/' + utils.stripLeadingSlash(url);
}
return url;
}

6 changes: 6 additions & 0 deletions src/database/firebase_list_factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ describe('FirebaseListFactory', () => {
expect(list instanceof FirebaseListObservable).toBe(true);
});

it('should take an absolute url in the constructor', () => {
const absoluteUrl = COMMON_CONFIG.databaseURL + '/questions';
const list = FirebaseListFactory(absoluteUrl);
expect(list instanceof FirebaseListObservable).toBe(true);
});

});

describe('query', () => {
Expand Down
41 changes: 24 additions & 17 deletions src/database/firebase_list_factory.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
import * as firebase from 'firebase/app';
import * as utils from '../utils';
import 'firebase/database';
import { AFUnwrappedDataSnapshot } from '../interfaces';
import { FirebaseListObservable } from './firebase_list_observable';
import { Observer } from 'rxjs/Observer';
import { observeOn } from 'rxjs/operator/observeOn';
import { observeQuery } from './query_observable';
import { Query, FirebaseListFactoryOpts, PathReference } from '../interfaces';
import * as utils from '../utils';
import { Query, FirebaseListFactoryOpts, PathReference, QueryReference, DatabaseQuery, DatabaseReference } from '../interfaces';
import { switchMap } from 'rxjs/operator/switchMap';
import { map } from 'rxjs/operator/map';

export function FirebaseListFactory (
pathOrReference: PathReference,
pathRef: PathReference,
{ preserveSnapshot, query = {} } :FirebaseListFactoryOpts = {}): FirebaseListObservable<any> {

let ref: firebase.database.Reference | firebase.database.Query;
let ref: QueryReference;

utils.checkForUrlOrFirebaseRef(pathOrReference, {
isUrl: () => ref = firebase.database().ref(<string>pathOrReference),
isRef: () => ref = <firebase.database.Reference>pathOrReference,
isQuery: () => ref = <firebase.database.Query>pathOrReference,
utils.checkForUrlOrFirebaseRef(pathRef, {
isUrl: () => {
const path = pathRef as string;
if(utils.isAbsoluteUrl(path)) {
ref = firebase.database().refFromURL(path)
} else {
ref = firebase.database().ref(path);
}
},
isRef: () => ref = <DatabaseReference>pathRef,
isQuery: () => ref = <DatabaseQuery>pathRef,
});

// if it's just a reference or string, create a regular list observable
if ((utils.isFirebaseRef(pathOrReference) ||
utils.isString(pathOrReference)) &&
if ((utils.isFirebaseRef(pathRef) ||
utils.isString(pathRef)) &&
utils.isEmptyObject(query)) {
return firebaseListObservable(ref, { preserveSnapshot });
}

const queryObs = observeQuery(query);
return new FirebaseListObservable(ref, subscriber => {
let sub = switchMap.call(map.call(queryObs, query => {
let queried: firebase.database.Query = ref;
let queried: DatabaseQuery = ref;
// Only apply the populated keys
// apply ordering and available querying options
// eg: ref.orderByChild('height').startAt(3)
Expand Down Expand Up @@ -112,13 +119,13 @@ export function FirebaseListFactory (
}

/**
* Creates a FirebaseListObservable from a reference or query. Options can be provided as a second parameter.
* This function understands the nuances of the Firebase SDK event ordering and other quirks. This function
* takes into account that not all .on() callbacks are guaranteed to be asynchonous. It creates a initial array
* from a promise of ref.once('value'), and then starts listening to child events. When the initial array
* is loaded, the observable starts emitting values.
* Creates a FirebaseListObservable from a reference or query. Options can be provided as a second
* parameter. This function understands the nuances of the Firebase SDK event ordering and other
* quirks. This function takes into account that not all .on() callbacks are guaranteed to be
* asynchonous. It creates a initial array from a promise of ref.once('value'), and then starts
* listening to child events. When the initial array is loaded, the observable starts emitting values.
*/
function firebaseListObservable(ref: firebase.database.Reference | firebase.database.Query, {preserveSnapshot}: FirebaseListFactoryOpts = {}): FirebaseListObservable<any> {
function firebaseListObservable(ref: firebase.database.Reference | DatabaseQuery, {preserveSnapshot}: FirebaseListFactoryOpts = {}): FirebaseListObservable<any> {

const toValue = preserveSnapshot ? (snapshot => snapshot) : utils.unwrapMapFn;
const toKey = preserveSnapshot ? (value => value.key) : (value => value.$key);
Expand Down
6 changes: 6 additions & 0 deletions src/database/firebase_object_factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ describe('FirebaseObjectFactory', () => {
expect(object instanceof FirebaseObjectObservable).toBe(true);
});

it('should take an absolute url in the constructor', () => {
const absoluteUrl = COMMON_CONFIG.databaseURL + '/questions/one';
const list = FirebaseObjectFactory(absoluteUrl);
expect(list instanceof FirebaseObjectObservable).toBe(true);
});

});

describe('methods', () => {
Expand Down
19 changes: 13 additions & 6 deletions src/database/firebase_object_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@ import { observeOn } from 'rxjs/operator/observeOn';
import * as firebase from 'firebase/app';
import 'firebase/database';
import * as utils from '../utils';
import { FirebaseObjectFactoryOpts, PathReference } from '../interfaces';
import { FirebaseObjectFactoryOpts, PathReference, DatabaseReference } from '../interfaces';

export function FirebaseObjectFactory (
pathReference: PathReference,
pathRef: PathReference,
{ preserveSnapshot }: FirebaseObjectFactoryOpts = {}): FirebaseObjectObservable<any> {

let ref: firebase.database.Reference;
let ref: DatabaseReference;

utils.checkForUrlOrFirebaseRef(pathReference, {
isUrl: () => ref = firebase.database().ref(<string>pathReference),
isRef: () => ref = <firebase.database.Reference>pathReference
utils.checkForUrlOrFirebaseRef(pathRef, {
isUrl: () => {
const path = pathRef as string;
if(utils.isAbsoluteUrl(path)) {
ref = firebase.database().refFromURL(path)
} else {
ref = firebase.database().ref(path);
}
},
isRef: () => ref = <DatabaseReference>pathRef
});

const objectObservable = new FirebaseObjectObservable((obs: Observer<any>) => {
Expand Down
37 changes: 36 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import * as firebase from 'firebase/app';
import { Subscription } from 'rxjs/Subscription';
import { Scheduler } from 'rxjs/Scheduler';
import { queue } from 'rxjs/scheduler/queue';
import { AFUnwrappedDataSnapshot } from './interfaces';
import { AFUnwrappedDataSnapshot, PathReference, DatabaseReference } from './interfaces';
import { FirebaseApp } from './app/index';

const REGEX_ABSOLUTE_URL = /^[a-z]+:\/\/.*/;

export function isNil(obj: any): boolean {
return obj === undefined || obj === null;
Expand Down Expand Up @@ -92,6 +95,15 @@ export function stripTrailingSlash(value: string): string {
}
}

function getAbsUrl(root: string, url:string) {
if (!(/^[a-z]+:\/\/.*/.test(url))) {
// Provided url is relative.
// Strip any leading slash
url = root + '/' + stripLeadingSlash(url);
}
return url;
}

export function stripLeadingSlash(value: string): string {
// Is the last char a /
if (value.substring(0, 1) === '/') {
Expand All @@ -101,6 +113,29 @@ export function stripLeadingSlash(value: string): string {
}
}

export function isAbsoluteUrl(url: string) {
return REGEX_ABSOLUTE_URL.test(url);
}

/**
* Returns a database reference given a Firebase App and an
* absolute or relative path.
* @param app - Firebase App
* @param path - Database path, relative or absolute
*/
export function getRef(app: FirebaseApp, pathRef: PathReference): DatabaseReference {
// if a db ref was passed in, just return it
if(isFirebaseRef(pathRef)) {
return pathRef as DatabaseReference;
}

const path = pathRef as string;
if(isAbsoluteUrl(<string>pathRef)) {
return app.database().refFromURL(path);
}
return app.database().ref(path);
}

/**
* TODO: remove this scheduler once Rx has a more robust story for working
* with zones.
Expand Down

0 comments on commit 72dab0d

Please sign in to comment.