diff --git a/gulpfile.js b/gulpfile.js index ff073d7..b638099 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -8,7 +8,6 @@ var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var fs = require('fs'); var argv = require('yargs').argv; -var streamToPromise = require('stream-to-promise'); var path = require('path'); var os = require('os'); @@ -36,6 +35,16 @@ function bundle () { .pipe(plugins.footer(fs.readFileSync('./helpers/globals.js'))); } +var bundlePath; +gulp.task('bundle-smoke', function () { + var name = Date.now() + '-mockfirebase.js'; + var dir = os.tmpdir(); + bundlePath = path.join(dir, name); + return bundle() + .pipe(plugins.rename(name)) + .pipe(gulp.dest(dir)); +}); + gulp.task('bundle', function () { return bundle().pipe(gulp.dest('./browser')); }); @@ -76,28 +85,20 @@ gulp.task('karma', function () { }); }); -gulp.task('smoke', function () { - var name = Date.now() + '-mockfirebase.js'; - var dir = os.tmpdir(); - var bundlePath = path.join(dir, name); - return streamToPromise(bundle() - .pipe(plugins.rename(name)) - .pipe(gulp.dest(dir))) - .then(function () { - return require('karma-as-promised').server.start({ - frameworks: ['mocha', 'chai'], - browsers: ['PhantomJS'], - client: { - args: ['--grep', argv.grep] - }, - files: [ - bundlePath, - 'test/smoke/globals.js' - ], - autoWatch: false, - singleRun: true - }); - }); +gulp.task('smoke', ['bundle-smoke'], function () { + return require('karma-as-promised').server.start({ + frameworks: ['mocha', 'chai'], + browsers: ['PhantomJS'], + client: { + args: ['--grep', argv.grep] + }, + files: [ + bundlePath, + 'test/smoke/globals.js' + ], + autoWatch: false, + singleRun: true + }); }); gulp.task('lint', function () { diff --git a/helpers/globals.js b/helpers/globals.js index d537bb0..420b6f4 100644 --- a/helpers/globals.js +++ b/helpers/globals.js @@ -4,17 +4,50 @@ window.MockFirebase = window.mockfirebase.MockFirebase; window.MockFirebaseSimpleLogin = window.mockfirebase.MockFirebaseSimpleLogin; + window.mockfirebase.MockFirebaseSdk = { + database: function() { + return { + ref: function(path) { + return new window.mockfirebase.MockFirebase(path); + }, + refFromURL: function(url) { + return new window.mockfirebase.MockFirebase(url); + } + }; + }, + auth: function() { + var auth = new window.mockfirebase.MockFirebase(); + delete auth.ref; + return auth; + } + }; + window.mockfirebase.MockFirebaseSdk.auth.GoogleAuthProvider = function() { + this.providerId = "google.com"; + }; + window.mockfirebase.MockFirebaseSdk.auth.TwitterAuthProvider = function() { + this.providerId = "twitter.com"; + }; + window.mockfirebase.MockFirebaseSdk.auth.FacebookAuthProvider = function() { + this.providerId = "facebook.com"; + }; + window.mockfirebase.MockFirebaseSdk.auth.GithubAuthProvider = function() { + this.providerId = "github.com"; + }; + var originals = false; window.MockFirebase.override = function () { originals = { + firebasesdk: window.firebase, firebase: window.Firebase, login: window.FirebaseSimpleLogin }; + window.firebase = window.mockfirebase.MockFirebaseSdk; window.Firebase = window.mockfirebase.MockFirebase; window.FirebaseSimpleLogin = window.mockfirebase.MockFirebaseSimpleLogin; }; window.MockFirebase.restore = function () { if (!originals) return; + window.firebase = originals.firebasesdk; window.Firebase = originals.firebase; window.FirebaseSimpleLogin = originals.login; }; diff --git a/package.json b/package.json index 783aaff..a2ac032 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "browserify": "^6.3.2", "browserify-shim": "^3.8.0", "firebase-auto-ids": "~1.1.0", - "lodash": "~2.4.1" + "lodash": "~2.4.1", + "rsvp": "^3.2.1" }, "devDependencies": { "chai": "~1.9.1", @@ -55,7 +56,6 @@ "semver": "~3.0.1", "sinon": "~1.10.3", "sinon-chai": "~2.5.0", - "stream-to-promise": "~1.0.4", "vinyl-buffer": "1.0.0", "vinyl-source-stream": "^1.0.0", "yargs": "~1.3.1" diff --git a/src/auth.js b/src/auth.js index c9379d9..68bce9c 100644 --- a/src/auth.js +++ b/src/auth.js @@ -2,10 +2,11 @@ var _ = require('lodash'); var format = require('util').format; +var rsvp = require('rsvp'); function FirebaseAuth () { + this.currentUser = null; this._auth = { - userData: null, listeners: [], completionListeners: [], users: [], @@ -15,13 +16,34 @@ function FirebaseAuth () { FirebaseAuth.prototype.changeAuthState = function (userData) { this._defer('changeAuthState', _.toArray(arguments), function() { - if (!_.isEqual(this._auth.userData, userData)) { - this._auth.userData = _.isObject(userData) ? userData : null; + if (!_.isEqual(this.currentUser, userData)) { + this.currentUser = _.isObject(userData) ? userData : null; this._triggerAuthEvent(); } }); }; +FirebaseAuth.prototype.onAuthStateChanged = function (callback) { + var self = this; + var currentUser = this.currentUser; + this._auth.listeners.push({fn: callback}); + + defer(); + return destroy; + + function destroy() { + self.offAuth(callback); + } + + function defer() { + self._defer('onAuthStateChanged', _.toArray(arguments), function() { + if (!_.isEqual(self.currentUser, currentUser)) { + self._triggerAuthEvent(); + } + }); + } +}; + FirebaseAuth.prototype.getEmailUser = function (email) { var users = this._auth.users; return users.hasOwnProperty(email) ? _.clone(users[email]) : null; @@ -46,12 +68,66 @@ Object.keys(authMethods) }; }); +var signinMethods = { + signInWithCustomToken: function(authToken) { + return { + isAnonymous: false + }; + }, + signInAnonymously: function() { + return { + isAnonymous: true + }; + }, + signInWithEmailAndPassword: function(email, password) { + return { + isAnonymous: false, + email: email + }; + }, + signInWithPopup: function(provider) { + return { + isAnonymous: false, + providerData: [provider] + }; + }, + signInWithRedirect: function(provider) { + return { + isAnonymous: false, + providerData: [provider] + }; + }, + signInWithCredential: function(credential) { + return { + isAnonymous: false + }; + } +}; + +Object.keys(signinMethods) + .forEach(function (method) { + var getUser = signinMethods[method]; + FirebaseAuth.prototype[method] = function () { + var self = this; + var user = getUser.apply(this, arguments); + var promise = new rsvp.Promise(function(resolve, reject) { + self._authEvent(method, function(err) { + if (err) reject(err); + self.currentUser = user; + resolve(user); + self._triggerAuthEvent(); + }, true); + }); + return promise; + }; + }); + FirebaseAuth.prototype.auth = function (token, callback) { console.warn('FIREBASE WARNING: FirebaseRef.auth() being deprecated. Please use FirebaseRef.authWithCustomToken() instead.'); this._authEvent('auth', callback); }; -FirebaseAuth.prototype._authEvent = function (method, callback) { +FirebaseAuth.prototype._authEvent = function (method, callback, defercallback) { var err = this._nextErr(method); if (!callback) return; if (err) { @@ -62,26 +138,33 @@ FirebaseAuth.prototype._authEvent = function (method, callback) { }); } else { - // if there is no error, then we just add our callback to the listener - // stack and wait for the next changeAuthState() call. - this._auth.completionListeners.push({fn: callback}); + if (defercallback) { + this._defer(method, _.toArray(arguments), function() { + callback(); + }); + } else { + // if there is no error, then we just add our callback to the listener + // stack and wait for the next changeAuthState() call. + this._auth.completionListeners.push({fn: callback}); + } } }; FirebaseAuth.prototype._triggerAuthEvent = function () { var completionListeners = this._auth.completionListeners; this._auth.completionListeners = []; - var user = this._auth.userData; + var user = this.currentUser; completionListeners.forEach(function (parts) { parts.fn.call(parts.context, null, _.cloneDeep(user)); }); - this._auth.listeners.forEach(function (parts) { + var listeners = _.cloneDeep(this._auth.listeners); + listeners.forEach(function (parts) { parts.fn.call(parts.context, _.cloneDeep(user)); }); }; FirebaseAuth.prototype.getAuth = function () { - return this._auth.userData; + return this.currentUser; }; FirebaseAuth.prototype.onAuth = function (onComplete, context) { @@ -102,12 +185,28 @@ FirebaseAuth.prototype.offAuth = function (onComplete, context) { }; FirebaseAuth.prototype.unauth = function () { - if (this._auth.userData !== null) { - this._auth.userData = null; + if (this.currentUser !== null) { + this.currentUser = null; this._triggerAuthEvent(); } }; +FirebaseAuth.prototype.signOut = function () { + var self = this, updateuser = this.currentUser !== null; + var promise = new rsvp.Promise(function(resolve, reject) { + self._authEvent('signOut', function(err) { + if (err) reject(err); + self.currentUser = null; + resolve(); + + if (updateuser) { + self._triggerAuthEvent(); + } + }, true); + }); + return promise; +}; + FirebaseAuth.prototype.createUser = function (credentials, onComplete) { validateCredentials('createUser', credentials, [ 'email', diff --git a/src/firebase.js b/src/firebase.js index 8d5720b..b4dc8f4 100644 --- a/src/firebase.js +++ b/src/firebase.js @@ -11,6 +11,7 @@ var Auth = require('./auth'); var validate = require('./validators'); function MockFirebase (path, data, parent, name) { + this.ref = this; this.path = path || 'Mock://'; this.errs = {}; this.priority = null; @@ -198,10 +199,6 @@ MockFirebase.prototype.name = function () { return this.key.apply(this, arguments); }; -MockFirebase.prototype.ref = function () { - return this; -}; - MockFirebase.prototype.parent = function () { return this.parentRef; }; @@ -299,7 +296,7 @@ MockFirebase.prototype.off = function (event, callback, context) { }); } else { - this._events[event] = []; + this._events[event] = []; } } }; @@ -376,7 +373,7 @@ MockFirebase.prototype._dataChanged = function (unparsedData) { keysToChange.forEach(function(key) { var childData = unparsedData[key]; if (utils.isServerTimestamp(childData)) { - childData = getServerTime(); + childData = getServerTime(); } this._updateOrAdd(key, childData, events); }, this); diff --git a/src/query.js b/src/query.js index ad08124..5c2b392 100644 --- a/src/query.js +++ b/src/query.js @@ -6,22 +6,20 @@ var utils = require('./utils'); var validate = require('./validators'); function MockQuery (ref) { - this.ref = function () { - return ref; - }; + this.ref = ref; this._events = []; // startPri, endPri, startKey, endKey, and limit this._q = {}; } MockQuery.prototype.flush = function () { - var ref = this.ref(); + var ref = this.ref; ref.flush.apply(ref, arguments); return this; }; MockQuery.prototype.autoFlush = function () { - var ref = this.ref(); + var ref = this.ref; ref.autoFlush.apply(ref, arguments); return this; }; @@ -57,7 +55,7 @@ MockQuery.prototype.on = function (event, callback, cancelCallback, context) { var lastSlice = this.slice(); var map; function handleRefEvent (snap, prevChild) { - var slice = new Slice(self, event === 'value' ? snap : utils.makeRefSnap(snap.ref().parent())); + var slice = new Slice(self, event === 'value' ? snap : utils.makeRefSnap(snap.ref.parent())); switch (event) { case 'value': if (isFirst || !lastSlice.equals(slice)) { @@ -104,11 +102,11 @@ MockQuery.prototype.on = function (event, callback, cancelCallback, context) { lastSlice = slice; } this._events.push([event, callback, context, handleRefEvent]); - this.ref().on(event, handleRefEvent, _.bind(cancelCallback, context)); + this.ref.on(event, handleRefEvent, _.bind(cancelCallback, context)); }; MockQuery.prototype.off = function (event, callback, context) { - var ref = this.ref(); + var ref = this.ref; _.each(this._events, function (parts) { if( parts[0] === event && parts[1] === callback && parts[2] === context ) { ref.off(event, parts[3]); @@ -133,21 +131,21 @@ MockQuery.prototype.limit = function (intVal) { if( typeof intVal !== 'number' ) { throw new Error('Query.limit: First argument must be a positive integer.'); } - var q = new MockQuery(this.ref()); + var q = new MockQuery(this.ref); _.extend(q._q, this._q, {limit: intVal}); return q; }; MockQuery.prototype.startAt = function (priority, key) { assertQuery('Query.startAt', priority, key); - var q = new MockQuery(this.ref()); + var q = new MockQuery(this.ref); _.extend(q._q, this._q, {startKey: key, startPri: priority}); return q; }; MockQuery.prototype.endAt = function (priority, key) { assertQuery('Query.endAt', priority, key); - var q = new MockQuery(this.ref()); + var q = new MockQuery(this.ref); _.extend(q._q, this._q, {endKey: key, endPri: priority}); return q; }; diff --git a/src/slice.js b/src/slice.js index 9402b47..644452a 100644 --- a/src/slice.js +++ b/src/slice.js @@ -5,8 +5,8 @@ var Snapshot = require('./snapshot'); var utils = require('./utils'); function Slice (queue, snap) { - var data = snap? snap.val() : queue.ref().getData(); - this.ref = snap? snap.ref() : queue.ref(); + var data = snap? snap.val() : queue.ref.getData(); + this.ref = snap? snap.ref : queue.ref; this.priority = snap? snap.getPriority() : this.ref.priority; this.pris = {}; this.data = {}; diff --git a/src/snapshot.js b/src/snapshot.js index e6edf47..77e3a6a 100644 --- a/src/snapshot.js +++ b/src/snapshot.js @@ -3,9 +3,7 @@ var _ = require('lodash'); function MockDataSnapshot (ref, data, priority) { - this.ref = function () { - return ref; - }; + this.ref = ref; data = _.cloneDeep(data); if (_.isObject(data) && _.isEmpty(data)) { data = null; @@ -19,9 +17,9 @@ function MockDataSnapshot (ref, data, priority) { } MockDataSnapshot.prototype.child = function (key) { - var ref = this.ref().child(key); + var ref = this.ref.child(key); var data = this.hasChild(key) ? this.val()[key] : null; - var priority = this.ref().child(key).priority; + var priority = this.ref.child(key).priority; return new MockDataSnapshot(ref, data, priority); }; @@ -44,7 +42,7 @@ MockDataSnapshot.prototype.hasChildren = function () { }; MockDataSnapshot.prototype.key = function () { - return this.ref().key(); + return this.ref.key(); }; MockDataSnapshot.prototype.name = function () { diff --git a/test/unit/firebase.js b/test/unit/firebase.js index d3f95e5..1e149a8 100644 --- a/test/unit/firebase.js +++ b/test/unit/firebase.js @@ -199,7 +199,7 @@ describe('MockFirebase', function () { ref.flush(); expect(spy.callCount).to.equal(1); var snapshot = spy.firstCall.args[0]; - expect(snapshot.ref()).to.equal(ref); + expect(snapshot.ref).to.equal(ref); expect(snapshot.val()).to.deep.equal(data); expect(snapshot.getPriority()).to.equal(null); }); @@ -215,7 +215,7 @@ describe('MockFirebase', function () { ref.flush(); expect(spy.callCount).to.equal(1); var snapshot = spy.firstCall.args[0]; - expect(snapshot.ref()).to.equal(ref.child('theKey')); + expect(snapshot.ref).to.equal(ref.child('theKey')); expect(spy.firstCall.args[1]).to.equal('prevChild'); expect(snapshot.getPriority()).to.equal(1); }); @@ -270,7 +270,7 @@ describe('MockFirebase', function () { describe('#ref', function () { it('returns itself', function () { - expect(ref.ref()).to.equal(ref); + expect(ref.ref).to.equal(ref); }); }); diff --git a/test/unit/query.js b/test/unit/query.js index fa8b150..19296ea 100644 --- a/test/unit/query.js +++ b/test/unit/query.js @@ -19,7 +19,7 @@ describe('MockQuery', function () { describe('#ref', function() { it('returns the ref used to create the query', function() { - expect(ref.limit(2).startAt('a').ref()).to.equal(ref); + expect(ref.limit(2).startAt('a').ref).to.equal(ref); }); }); @@ -201,7 +201,7 @@ describe('MockQuery', function () { ref.child('fruit').once('value', function(list_snapshot) { list_snapshot.forEach(function(snapshot){ model[snapshot.key()] = snapshot.val(); - snapshot.ref().on('value', function(snapshot) { + snapshot.ref.on('value', function(snapshot) { model[snapshot.key()] = snapshot.val(); }); last_key = snapshot.key(); @@ -211,7 +211,7 @@ describe('MockQuery', function () { if(model[snapshot.key()] === undefined) { model[snapshot.key()] = snapshot.val(); - snapshot.ref().on('value', function(snapshot) { + snapshot.ref.on('value', function(snapshot) { model[snapshot.key()] = snapshot.val(); }); } diff --git a/test/unit/snapshot.js b/test/unit/snapshot.js index 2b73031..562fb73 100644 --- a/test/unit/snapshot.js +++ b/test/unit/snapshot.js @@ -15,7 +15,7 @@ describe('DataSnapshot', function () { describe('#ref', function () { it('returns the reference', function () { - expect(new Snapshot(ref).ref()).to.equal(ref); + expect(new Snapshot(ref).ref).to.equal(ref); }); }); @@ -53,7 +53,7 @@ describe('DataSnapshot', function () { it('generates a snapshot for a child ref', function () { var parent = new Snapshot(ref); var child = parent.child('key'); - expect(parent.ref().child('key')).to.equal(child.ref()); + expect(parent.ref.child('key')).to.equal(child.ref); }); it('uses child data', function () {