Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 60 additions & 5 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { size, map } from 'lodash'

const fixPath = path => ((path.substring(0, 1) === '/') ? '' : '/') + path

/**
* @description Fix path by adding "/" to path if needed
* @param {String} path - Path string to fix
* @return {String} path - Fixed path
*/
export const fixPath = path =>
((path.substring(0, 1) === '/') ? '' : '/') + path

/**
* @description Convert Immutable Map to a Javascript object
* @param {Object} data - Immutable Map to be converted to JS object
* @return {Object} data - Javascript version of Immutable Map
*/
export const toJS = data => {
if (data && data.toJS) {
return data.toJS()
Expand All @@ -10,6 +21,11 @@ export const toJS = data => {
return data
}

/**
* @description Convert parameter from Immutable Map to a Javascript object
* @param {Object} data - Immutable Map to be converted to JS object
* @return {Object} data - Javascript version of Immutable Map
*/
export const pathToJS = (data, path, notSetValue) => {
if (!data) {
return notSetValue
Expand All @@ -24,8 +40,15 @@ export const pathToJS = (data, path, notSetValue) => {
return data
}

/**
* @description Convert parameter under "data" path of Immutable Map to a Javascript object
* @param {Object} data - Immutable Map to be converted to JS object
* @param {String} path - Path of parameter to load
* @param {Object|String|Boolean} notSetValue - Value to return if value is not found
* @return {Object} data - Javascript version of Immutable Map
*/
export const dataToJS = (data, path, notSetValue) => {
if (!(data && data.getIn)) {
if (!data) {
return notSetValue
}

Expand All @@ -40,6 +63,14 @@ export const dataToJS = (data, path, notSetValue) => {
return data
}

/**
* @description Load custom object from within store
* @param {Object} data - Immutable Map from store to be converted to JS object
* @param {String} path - Path of parameter to load
* @param {String} customPath - Part of store from which to load
* @param {Object|String|Boolean} notSetValue - Value to return if value is not found
* @return {Object} data - Javascript version of custom path within Immutable Map
*/
export const customToJS = (data, path, custom, notSetValue) => {
if (!(data && data.getIn)) {
return notSetValue
Expand All @@ -56,8 +87,14 @@ export const customToJS = (data, path, custom, notSetValue) => {
return data
}

/**
* @description Convert Immutable Map to a Javascript object
* @param {Object} snapshot - Snapshot from store
* @param {String} path - Path of snapshot to load
* @return {Object} notSetValue - Value to return if snapshot is not found
*/
export const snapshotToJS = (snapshot, path, notSetValue) => {
if (!(snapshot && snapshot.getIn)) {
if (!snapshot) {
return notSetValue
}

Expand All @@ -72,6 +109,11 @@ export const snapshotToJS = (snapshot, path, notSetValue) => {
return snapshot
}

/**
* @description Detect whether items are loaded yet or not
* @param {Object} item - Item to check loaded status of. A comma seperated list is also acceptable.
* @return {Boolean} isLoaded - Whether or not item is loaded
*/
export const isLoaded = function () {
if (!arguments || !arguments.length) {
return true
Expand All @@ -80,8 +122,21 @@ export const isLoaded = function () {
return map(arguments, a => a !== undefined).reduce((a, b) => a && b)
}

/**
* @description Detect whether items are empty or not
* @param {Object} item - Item to check loaded status of. A comma seperated list is also acceptable.
* @return {Boolean} isEmpty - Whether or not item is empty
*/
export const isEmpty = data => {
return !(data && size(data))
}

export default { toJS, pathToJS, dataToJS, snapshotToJS, customToJS, isLoaded, isEmpty }
export default {
toJS,
pathToJS,
dataToJS,
snapshotToJS,
customToJS,
isLoaded,
isEmpty
}
1 change: 1 addition & 0 deletions src/utils/auth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { capitalize, isArray, isString } from 'lodash'
import { supportedAuthProviders } from '../constants'

/**
* @description Get correct login method and params order based on provided credentials
* @param {Object} firebase - Internal firebase object
Expand Down
25 changes: 18 additions & 7 deletions test/setup.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
/* eslint-disable no-unused-vars */
process.env.NODE_ENV = 'test'

var chai = global.chai = require('chai')
var chai = require('chai')
var sinon = require('sinon')
var chaiAsPromised = require('chai-as-promised')
var sinonChai = require('sinon-chai')
var jsdom = require('jsdom').jsdom

// Chai Plugins
chai.use(chaiAsPromised)
chai.use(sinonChai)
var expect = global.expect = chai.expect
global.sinon = require('sinon')
var jsdom = require('jsdom').jsdom

// globals
global.expect = chai.expect
global.sinon = sinon
global.chai = chai
global.document = jsdom('<!doctype html><html><body></body></html>')
global.window = document.defaultView
global.navigator = global.window.navigator
Expand All @@ -22,13 +28,18 @@ var fbConfig = global.fbConfig = {
storageBucket: 'redux-firebasev3.appspot.com',
messagingSenderId: '823357791673'
}
Firebase.initializeApp(fbConfig)

// Swallow firebase reinitialize error (useful when using watch)
try {
Firebase.initializeApp(fbConfig)
} catch (err) {}

global.firebase = Object.defineProperty(Firebase, '_', {
value: {
watchers: {},
authUid: null
authUid: null,
config: Object.assign({}, fbConfig, { userProfile: 'users' })
},
config: fbConfig,
writable: true,
enumerable: true,
configurable: true
Expand Down
147 changes: 125 additions & 22 deletions test/unit/actions/auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import {
createUser,
resetPassword
} from '../../../src/actions/auth'

let functionSpy
let dispatchSpy
const dispatch = () => {
console.log('dispatch')
}

const firebase = {
const fakeFirebase = {
_: {
authUid: '123',
config: {
Expand All @@ -34,12 +35,23 @@ const firebase = {
})
}),
auth: () => ({
onAuthStateChanged: () => {

onAuthStateChanged: (f) => {
f({uid: 'asdfasdf'})
},
signOut: () => {},
createUserWithEmailAndPassword: () => Promise.resolve(),
sendPasswordResetEmail: () => Promise.resolve()
signOut: () =>
Promise.resolve({}),
createUserWithEmailAndPassword: () =>
Promise.resolve({ uid: '123', email: 'test@test.com', providerData: [{}] }),
signInWithCustomToken: () => {
return Promise.resolve({
toJSON: () => ({
stsTokenManager: {
accessToken: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjbGFpbXMiOnsiZGlzcGxheU5hbWUiOiJFZHdhcmQgV3UiLCJlbWFpbCI6ImVkZGlld3U4MEBnbWFpbC5jb20iLCJvcmlnaW5hbElkIjoiSlFYQ2dRTkxEU1lSMFEzdjlwY3FjTDZJeGRUMiIsInByb3ZpZGVyRGF0YSI6W3siZGlzcGxheU5hbWUiOiJFZHdhcmQgV3UiLCJlbWFpbCI6ImVkZGlld3U4MEBnbWFpbC5jb20ifV19LCJ1aWQiOiJqaTh3c1BDVW5PYUhvUGZBalNCS2ZReU1pTmkxIiwiaWF0IjoxNDc1NDM3MDMyLCJleHAiOjE0NzU0NDA2MzIsImF1ZCI6Imh0dHBzOi8vaWRlbnRpdHl0b29sa2l0Lmdvb2dsZWFwaXMuY29tL2dvb2dsZS5pZGVudGl0eS5pZGVudGl0eXRvb2xraXQudjEuSWRlbnRpdHlUb29sa2l0IiwiaXNzIjoicmVzaWRlLXByb2RAcmVzaWRlLXByb2QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiJyZXNpZGUtcHJvZEByZXNpZGUtcHJvZC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSJ9.aOXOCCAL-lI5AVnd8MVlc86exvCGNySq8X7DM4Gr7PG0ek5mh_8qnFfLuzw2gfv6mHNVgY2UngUmG0qETaBdox7l3wBo1GdP9hB1bM8NltCYffXwxyw7sN36BFWD3l-cz4rlxfmfzosCLj3XtDK8ARDQ76pAXxsK-rRBvMG6N-wgE_ZLf17FVvwB95e1DAmL39fp6dRVxoPflG--m4QEKVk8xIeDx4ol9HJw512gMGtTkRDMEPWVJEdaEAp6L6Lo2-Bk-TxBCHs8gpb7b7eidWMUEXObk0UPQIz2DRh-3olbruimzL_SgPNg4Pz0uUYSn11-Mx_HxxiVtyQj1ufoLA'
},
uid: 'asdfasdfsdf'
})
})
}
})
}

Expand All @@ -49,51 +61,142 @@ describe('Actions: Auth', () => {
expect(dispatchLoginError(dispatch, { some: 'error' }))
})
})

describe('dispatchUnauthorizedError', () => {
it('calls dispatch with error', () => {
expect(dispatchUnauthorizedError(dispatch, { some: 'error' }))
})
})

describe('dispatchLogin', () => {
it('calls dispatch', () => {
expect(dispatchLogin(dispatch, { some: 'error' }))
})
})

describe('init', () => {
it.skip('calls firebases onAuthStateChanged', () => {
// TODO: Check that mock onAuthStateChanged function is called using spy
expect(init(dispatch, firebase))
it('calls firebases onAuthStateChanged', () => {
init(dispatch, fakeFirebase)
})
})

describe('unWatchUserProfile', () => {
it('calls profile unwatch', () => {
expect(unWatchUserProfile(firebase))
})
})

describe('watchUserProfile', () => {
it('calls profile unwatch', () => {
expect(watchUserProfile(dispatch, firebase))
watchUserProfile(dispatch, fakeFirebase)
expect(firebase._.profileWatch).to.be.a.function
})
it('sets profile watch function', () => {
watchUserProfile(dispatch, firebase)
expect(firebase._.profileWatch).to.be.a.function
})
})

describe('createUserProfile', () => {
it('creates profile if config is enabled', () => {
return createUserProfile(dispatch, firebase, { uid: '123', email: 'test@test.com', providerData: [{}] }, { some: 'asdf' })
.then((profile) => {
expect(profile).to.be.an.object
})
}, 4000)
})

describe('login', () => {
// skipped due to TypeError: Cannot read property 'apply' of undefined
it.skip('logs user into Firebase', () => {
expect(login(dispatch, firebase, { email: 'test@tst.com', password: 'asdfasdf' }))
})
it('handles invalid email login', () => {
return login(dispatch, firebase, { email: 'test@tst.com', password: 'asdfasdf' })
.catch((err) => {
expect(err.code).to.equal('auth/user-not-found')
})
}, 4000)
it('handles invalid token login', () => {
return login(dispatch, firebase, { token: 'test@tst.com' })
.catch((err) => {
expect(err.code).to.equal('auth/invalid-custom-token')
})
}, 4000)
it('handles token login', () => {
const token = 'asdfasdf'
return login(dispatch, fakeFirebase, { token }, { uid: 'asdfasdf' })
.then((authData) => {
expect(authData).to.be.an.object
})
}, 4000)
})

describe('logout', () => {
it('logs user out of Firebase', () => {
expect(logout(dispatch, firebase)).to.eventually.be.fullfilled
beforeEach(() => {
functionSpy = sinon.spy(firebase.auth(), 'signOut')
})
afterEach(() => {
firebase.auth().signOut.restore()
})
it('calls firebase.auth().signOut()', () => {
return logout(dispatch, firebase)
.then(() => {
expect(functionSpy).to.have.been.calledOnce
})
})
it('calls firebase.auth().signOut()', () => {
logout(dispatch, firebase)
expect(functionSpy).to.have.been.calledOnce
})
it('sets authUid to null', () => {
fakeFirebase._.authUid = 'asdfasdf'
return logout(dispatch, fakeFirebase)
.then(() => {
expect(fakeFirebase._.authUid).to.be.null
})
})
it.skip('calls dispatch', () => {
dispatchSpy = sinon.spy(dispatch)
return logout(dispatch, firebase)
.then(() => {
expect(dispatchSpy).to.have.been.calledOnce
})
})
})

describe('createUser', () => {
it('creates user', () => {
expect(createUser(dispatch, firebase, { email: 'test@test.com', password: 'asdf' }))
// Skipped because of TypeError: Cannot read property 'apply' of undefined
it.skip('creates user', () => {
return createUser(dispatch, fakeFirebase, { email: 'test@test.com', password: 'asdf' }, { email: 'test@test.com', password: 'asdf' })
.then(userData => {
expect(userData).to.be.an.object
})
})
})
it('handles no email', () => {
return createUser(dispatch, fakeFirebase, { password: 'asdf' })
.catch((err) => {
expect(err).to.be.a.string
})
})
it('handles no password', () => {
return createUser(dispatch, fakeFirebase, { email: 'asdf@asdf.com' })
.catch((err) => {
expect(err).to.be.a.string
})
})
}, 4000)

describe('resetPassword', () => {
it('calls to reset user password', () => {
expect(resetPassword(dispatch, firebase, 'test@test.com')).to.eventually.be.fulfilled
beforeEach(() => {
functionSpy = sinon.spy(firebase.auth(), 'sendPasswordResetEmail')
})
afterEach(() => {
firebase.auth().sendPasswordResetEmail.restore()
})
it('dispatches error for invalid user', () => {
return resetPassword(dispatch, firebase, 'test@test.com')
.catch((err) => {
console.log('error', err)
expect(err.code).to.equal('auth/user-not-found')
expect(functionSpy).to.have.been.calledOnce
})
}, 4000)
})
})
2 changes: 1 addition & 1 deletion test/unit/actions/query.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Actions: Query', () => {
.then((snap) => {
expect(snap).to.be.an.object
})
})
}, 4000)
it('runs given first_child', () => {
return watchEvent(firebase, dispatch, { type: 'first_child', path: 'projects' }, 'projects')
.then((snap) => {
Expand Down
Loading