Skip to content

Commit c5727ce

Browse files
authored
Merge pull request prescottprue#13 from prescottprue/coverage
* Tests added for reducer * Helpers tests improved * Compose function has tests
2 parents f137824 + cb55a99 commit c5727ce

File tree

9 files changed

+460
-69
lines changed

9 files changed

+460
-69
lines changed

src/helpers.js

+60-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import { size, map } from 'lodash'
22

3-
const fixPath = path => ((path.substring(0, 1) === '/') ? '' : '/') + path
4-
3+
/**
4+
* @description Fix path by adding "/" to path if needed
5+
* @param {String} path - Path string to fix
6+
* @return {String} path - Fixed path
7+
*/
8+
export const fixPath = path =>
9+
((path.substring(0, 1) === '/') ? '' : '/') + path
10+
11+
/**
12+
* @description Convert Immutable Map to a Javascript object
13+
* @param {Object} data - Immutable Map to be converted to JS object
14+
* @return {Object} data - Javascript version of Immutable Map
15+
*/
516
export const toJS = data => {
617
if (data && data.toJS) {
718
return data.toJS()
@@ -10,6 +21,11 @@ export const toJS = data => {
1021
return data
1122
}
1223

24+
/**
25+
* @description Convert parameter from Immutable Map to a Javascript object
26+
* @param {Object} data - Immutable Map to be converted to JS object
27+
* @return {Object} data - Javascript version of Immutable Map
28+
*/
1329
export const pathToJS = (data, path, notSetValue) => {
1430
if (!data) {
1531
return notSetValue
@@ -24,8 +40,15 @@ export const pathToJS = (data, path, notSetValue) => {
2440
return data
2541
}
2642

43+
/**
44+
* @description Convert parameter under "data" path of Immutable Map to a Javascript object
45+
* @param {Object} data - Immutable Map to be converted to JS object
46+
* @param {String} path - Path of parameter to load
47+
* @param {Object|String|Boolean} notSetValue - Value to return if value is not found
48+
* @return {Object} data - Javascript version of Immutable Map
49+
*/
2750
export const dataToJS = (data, path, notSetValue) => {
28-
if (!(data && data.getIn)) {
51+
if (!data) {
2952
return notSetValue
3053
}
3154

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

66+
/**
67+
* @description Load custom object from within store
68+
* @param {Object} data - Immutable Map from store to be converted to JS object
69+
* @param {String} path - Path of parameter to load
70+
* @param {String} customPath - Part of store from which to load
71+
* @param {Object|String|Boolean} notSetValue - Value to return if value is not found
72+
* @return {Object} data - Javascript version of custom path within Immutable Map
73+
*/
4374
export const customToJS = (data, path, custom, notSetValue) => {
4475
if (!(data && data.getIn)) {
4576
return notSetValue
@@ -56,8 +87,14 @@ export const customToJS = (data, path, custom, notSetValue) => {
5687
return data
5788
}
5889

90+
/**
91+
* @description Convert Immutable Map to a Javascript object
92+
* @param {Object} snapshot - Snapshot from store
93+
* @param {String} path - Path of snapshot to load
94+
* @return {Object} notSetValue - Value to return if snapshot is not found
95+
*/
5996
export const snapshotToJS = (snapshot, path, notSetValue) => {
60-
if (!(snapshot && snapshot.getIn)) {
97+
if (!snapshot) {
6198
return notSetValue
6299
}
63100

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

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

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

87-
export default { toJS, pathToJS, dataToJS, snapshotToJS, customToJS, isLoaded, isEmpty }
134+
export default {
135+
toJS,
136+
pathToJS,
137+
dataToJS,
138+
snapshotToJS,
139+
customToJS,
140+
isLoaded,
141+
isEmpty
142+
}

src/utils/auth.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { capitalize, isArray, isString } from 'lodash'
22
import { supportedAuthProviders } from '../constants'
3+
34
/**
45
* @description Get correct login method and params order based on provided credentials
56
* @param {Object} firebase - Internal firebase object

test/setup.js

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
/* eslint-disable no-unused-vars */
22
process.env.NODE_ENV = 'test'
33

4-
var chai = global.chai = require('chai')
4+
var chai = require('chai')
5+
var sinon = require('sinon')
56
var chaiAsPromised = require('chai-as-promised')
67
var sinonChai = require('sinon-chai')
8+
var jsdom = require('jsdom').jsdom
9+
10+
// Chai Plugins
711
chai.use(chaiAsPromised)
812
chai.use(sinonChai)
9-
var expect = global.expect = chai.expect
10-
global.sinon = require('sinon')
11-
var jsdom = require('jsdom').jsdom
13+
14+
// globals
15+
global.expect = chai.expect
16+
global.sinon = sinon
17+
global.chai = chai
1218
global.document = jsdom('<!doctype html><html><body></body></html>')
1319
global.window = document.defaultView
1420
global.navigator = global.window.navigator
@@ -22,13 +28,18 @@ var fbConfig = global.fbConfig = {
2228
storageBucket: 'redux-firebasev3.appspot.com',
2329
messagingSenderId: '823357791673'
2430
}
25-
Firebase.initializeApp(fbConfig)
31+
32+
// Swallow firebase reinitialize error (useful when using watch)
33+
try {
34+
Firebase.initializeApp(fbConfig)
35+
} catch (err) {}
36+
2637
global.firebase = Object.defineProperty(Firebase, '_', {
2738
value: {
2839
watchers: {},
29-
authUid: null
40+
authUid: null,
41+
config: Object.assign({}, fbConfig, { userProfile: 'users' })
3042
},
31-
config: fbConfig,
3243
writable: true,
3344
enumerable: true,
3445
configurable: true

test/unit/actions/auth.spec.js

+125-22
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import {
1212
createUser,
1313
resetPassword
1414
} from '../../../src/actions/auth'
15-
15+
let functionSpy
16+
let dispatchSpy
1617
const dispatch = () => {
1718
console.log('dispatch')
1819
}
1920

20-
const firebase = {
21+
const fakeFirebase = {
2122
_: {
2223
authUid: '123',
2324
config: {
@@ -34,12 +35,23 @@ const firebase = {
3435
})
3536
}),
3637
auth: () => ({
37-
onAuthStateChanged: () => {
38-
38+
onAuthStateChanged: (f) => {
39+
f({uid: 'asdfasdf'})
3940
},
40-
signOut: () => {},
41-
createUserWithEmailAndPassword: () => Promise.resolve(),
42-
sendPasswordResetEmail: () => Promise.resolve()
41+
signOut: () =>
42+
Promise.resolve({}),
43+
createUserWithEmailAndPassword: () =>
44+
Promise.resolve({ uid: '123', email: 'test@test.com', providerData: [{}] }),
45+
signInWithCustomToken: () => {
46+
return Promise.resolve({
47+
toJSON: () => ({
48+
stsTokenManager: {
49+
accessToken: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjbGFpbXMiOnsiZGlzcGxheU5hbWUiOiJFZHdhcmQgV3UiLCJlbWFpbCI6ImVkZGlld3U4MEBnbWFpbC5jb20iLCJvcmlnaW5hbElkIjoiSlFYQ2dRTkxEU1lSMFEzdjlwY3FjTDZJeGRUMiIsInByb3ZpZGVyRGF0YSI6W3siZGlzcGxheU5hbWUiOiJFZHdhcmQgV3UiLCJlbWFpbCI6ImVkZGlld3U4MEBnbWFpbC5jb20ifV19LCJ1aWQiOiJqaTh3c1BDVW5PYUhvUGZBalNCS2ZReU1pTmkxIiwiaWF0IjoxNDc1NDM3MDMyLCJleHAiOjE0NzU0NDA2MzIsImF1ZCI6Imh0dHBzOi8vaWRlbnRpdHl0b29sa2l0Lmdvb2dsZWFwaXMuY29tL2dvb2dsZS5pZGVudGl0eS5pZGVudGl0eXRvb2xraXQudjEuSWRlbnRpdHlUb29sa2l0IiwiaXNzIjoicmVzaWRlLXByb2RAcmVzaWRlLXByb2QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiJyZXNpZGUtcHJvZEByZXNpZGUtcHJvZC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSJ9.aOXOCCAL-lI5AVnd8MVlc86exvCGNySq8X7DM4Gr7PG0ek5mh_8qnFfLuzw2gfv6mHNVgY2UngUmG0qETaBdox7l3wBo1GdP9hB1bM8NltCYffXwxyw7sN36BFWD3l-cz4rlxfmfzosCLj3XtDK8ARDQ76pAXxsK-rRBvMG6N-wgE_ZLf17FVvwB95e1DAmL39fp6dRVxoPflG--m4QEKVk8xIeDx4ol9HJw512gMGtTkRDMEPWVJEdaEAp6L6Lo2-Bk-TxBCHs8gpb7b7eidWMUEXObk0UPQIz2DRh-3olbruimzL_SgPNg4Pz0uUYSn11-Mx_HxxiVtyQj1ufoLA'
50+
},
51+
uid: 'asdfasdfsdf'
52+
})
53+
})
54+
}
4355
})
4456
}
4557

@@ -49,51 +61,142 @@ describe('Actions: Auth', () => {
4961
expect(dispatchLoginError(dispatch, { some: 'error' }))
5062
})
5163
})
64+
5265
describe('dispatchUnauthorizedError', () => {
5366
it('calls dispatch with error', () => {
5467
expect(dispatchUnauthorizedError(dispatch, { some: 'error' }))
5568
})
5669
})
70+
5771
describe('dispatchLogin', () => {
5872
it('calls dispatch', () => {
5973
expect(dispatchLogin(dispatch, { some: 'error' }))
6074
})
6175
})
76+
6277
describe('init', () => {
63-
it.skip('calls firebases onAuthStateChanged', () => {
64-
// TODO: Check that mock onAuthStateChanged function is called using spy
65-
expect(init(dispatch, firebase))
78+
it('calls firebases onAuthStateChanged', () => {
79+
init(dispatch, fakeFirebase)
6680
})
6781
})
82+
6883
describe('unWatchUserProfile', () => {
6984
it('calls profile unwatch', () => {
7085
expect(unWatchUserProfile(firebase))
7186
})
7287
})
88+
7389
describe('watchUserProfile', () => {
7490
it('calls profile unwatch', () => {
75-
expect(watchUserProfile(dispatch, firebase))
91+
watchUserProfile(dispatch, fakeFirebase)
92+
expect(firebase._.profileWatch).to.be.a.function
93+
})
94+
it('sets profile watch function', () => {
95+
watchUserProfile(dispatch, firebase)
96+
expect(firebase._.profileWatch).to.be.a.function
7697
})
7798
})
99+
100+
describe('createUserProfile', () => {
101+
it('creates profile if config is enabled', () => {
102+
return createUserProfile(dispatch, firebase, { uid: '123', email: 'test@test.com', providerData: [{}] }, { some: 'asdf' })
103+
.then((profile) => {
104+
expect(profile).to.be.an.object
105+
})
106+
}, 4000)
107+
})
108+
78109
describe('login', () => {
79-
// skipped due to TypeError: Cannot read property 'apply' of undefined
80-
it.skip('logs user into Firebase', () => {
81-
expect(login(dispatch, firebase, { email: 'test@tst.com', password: 'asdfasdf' }))
82-
})
110+
it('handles invalid email login', () => {
111+
return login(dispatch, firebase, { email: 'test@tst.com', password: 'asdfasdf' })
112+
.catch((err) => {
113+
expect(err.code).to.equal('auth/user-not-found')
114+
})
115+
}, 4000)
116+
it('handles invalid token login', () => {
117+
return login(dispatch, firebase, { token: 'test@tst.com' })
118+
.catch((err) => {
119+
expect(err.code).to.equal('auth/invalid-custom-token')
120+
})
121+
}, 4000)
122+
it('handles token login', () => {
123+
const token = 'asdfasdf'
124+
return login(dispatch, fakeFirebase, { token }, { uid: 'asdfasdf' })
125+
.then((authData) => {
126+
expect(authData).to.be.an.object
127+
})
128+
}, 4000)
83129
})
130+
84131
describe('logout', () => {
85-
it('logs user out of Firebase', () => {
86-
expect(logout(dispatch, firebase)).to.eventually.be.fullfilled
132+
beforeEach(() => {
133+
functionSpy = sinon.spy(firebase.auth(), 'signOut')
134+
})
135+
afterEach(() => {
136+
firebase.auth().signOut.restore()
137+
})
138+
it('calls firebase.auth().signOut()', () => {
139+
return logout(dispatch, firebase)
140+
.then(() => {
141+
expect(functionSpy).to.have.been.calledOnce
142+
})
143+
})
144+
it('calls firebase.auth().signOut()', () => {
145+
logout(dispatch, firebase)
146+
expect(functionSpy).to.have.been.calledOnce
147+
})
148+
it('sets authUid to null', () => {
149+
fakeFirebase._.authUid = 'asdfasdf'
150+
return logout(dispatch, fakeFirebase)
151+
.then(() => {
152+
expect(fakeFirebase._.authUid).to.be.null
153+
})
154+
})
155+
it.skip('calls dispatch', () => {
156+
dispatchSpy = sinon.spy(dispatch)
157+
return logout(dispatch, firebase)
158+
.then(() => {
159+
expect(dispatchSpy).to.have.been.calledOnce
160+
})
87161
})
88162
})
163+
89164
describe('createUser', () => {
90-
it('creates user', () => {
91-
expect(createUser(dispatch, firebase, { email: 'test@test.com', password: 'asdf' }))
165+
// Skipped because of TypeError: Cannot read property 'apply' of undefined
166+
it.skip('creates user', () => {
167+
return createUser(dispatch, fakeFirebase, { email: 'test@test.com', password: 'asdf' }, { email: 'test@test.com', password: 'asdf' })
168+
.then(userData => {
169+
expect(userData).to.be.an.object
170+
})
92171
})
93-
})
172+
it('handles no email', () => {
173+
return createUser(dispatch, fakeFirebase, { password: 'asdf' })
174+
.catch((err) => {
175+
expect(err).to.be.a.string
176+
})
177+
})
178+
it('handles no password', () => {
179+
return createUser(dispatch, fakeFirebase, { email: 'asdf@asdf.com' })
180+
.catch((err) => {
181+
expect(err).to.be.a.string
182+
})
183+
})
184+
}, 4000)
185+
94186
describe('resetPassword', () => {
95-
it('calls to reset user password', () => {
96-
expect(resetPassword(dispatch, firebase, 'test@test.com')).to.eventually.be.fulfilled
187+
beforeEach(() => {
188+
functionSpy = sinon.spy(firebase.auth(), 'sendPasswordResetEmail')
189+
})
190+
afterEach(() => {
191+
firebase.auth().sendPasswordResetEmail.restore()
97192
})
193+
it('dispatches error for invalid user', () => {
194+
return resetPassword(dispatch, firebase, 'test@test.com')
195+
.catch((err) => {
196+
console.log('error', err)
197+
expect(err.code).to.equal('auth/user-not-found')
198+
expect(functionSpy).to.have.been.calledOnce
199+
})
200+
}, 4000)
98201
})
99202
})

test/unit/actions/query.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('Actions: Query', () => {
2626
.then((snap) => {
2727
expect(snap).to.be.an.object
2828
})
29-
})
29+
}, 4000)
3030
it('runs given first_child', () => {
3131
return watchEvent(firebase, dispatch, { type: 'first_child', path: 'projects' }, 'projects')
3232
.then((snap) => {

0 commit comments

Comments
 (0)