From ae9aefcdce0fafc8387954cc8711e99af61653cd Mon Sep 17 00:00:00 2001 From: prescottprue Date: Tue, 16 Jan 2018 23:50:20 -0800 Subject: [PATCH 1/4] fix(firestore): logging in deletes any custom fields on user object - #366 --- package-lock.json | 10 +++++----- package.json | 8 +------- src/actions/auth.js | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca01afe90..98e637ec8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-redux-firebase", - "version": "2.0.1", + "version": "2.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3047,7 +3047,7 @@ "delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "integrity": "sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=", "optional": true }, "depd": { @@ -6295,7 +6295,7 @@ "gitbook-plugin-prism": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/gitbook-plugin-prism/-/gitbook-plugin-prism-2.3.0.tgz", - "integrity": "sha512-d1Q6MlGNTs/SWA7AmGeHTtXHLozRtvrIX8UH4iS9XrhPuC4Iboty81Ak4GfBKaWsvkgEFjY5Vrz+UyFpKTA5iQ==", + "integrity": "sha1-aGN9VP/lZQAt0QwwuqG0M7NewvI=", "requires": { "cheerio": "0.22.0", "mkdirp": "0.5.1", @@ -6305,7 +6305,7 @@ "gitbook-plugin-versions-select": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/gitbook-plugin-versions-select/-/gitbook-plugin-versions-select-0.1.1.tgz", - "integrity": "sha512-yo4yqgXnNwMuR8jwKdMrlnq2g6ma0FTHL5tGtrdGJEaDi893j6hQUxM3QncanZgoFX0H10vMrxGUPE4GvAsHiQ==" + "integrity": "sha1-JyOtRpIkTdvHlUCg5EX0XuJJeQk=" }, "github-slugger": { "version": "1.2.0", @@ -17319,7 +17319,7 @@ "tiny-emitter": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", - "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==", + "integrity": "sha1-gtJ0aKylrejl/R5tIrV91D69+3w=", "optional": true }, "tiny-lr": { diff --git a/package.json b/package.json index 25a04efdc..6a64c0af2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-redux-firebase", - "version": "2.0.1", + "version": "2.0.2", "description": "Redux integration for Firebase. Comes with a Higher Order Components for use with React.", "main": "lib/index.js", "module": "es/index.js", @@ -56,12 +56,6 @@ "redux-react-firebase" ], "dependencies": { - "gitbook-plugin-anchorjs": "^1.1.1", - "gitbook-plugin-edit-link": "^2.0.2", - "gitbook-plugin-ga": "^1.0.1", - "gitbook-plugin-github": "^2.0.0", - "gitbook-plugin-prism": "^2.3.0", - "gitbook-plugin-versions-select": "^0.1.1", "hoist-non-react-statics": "^2.3.1", "lodash": "^4.17.4", "prop-types": "^15.6.0" diff --git a/src/actions/auth.js b/src/actions/auth.js index e60186d95..f14a29697 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -214,7 +214,7 @@ export const createUserProfile = (dispatch, firebase, userData, profile) => { // update profile only if doesn't exist or if set by config !config.updateProfileOnLogin && profileSnap.exists ? profileSnap.data() - : profileSnap.ref.set(omit(profile, ['providerData'])) // fixes issue with bad write + : profileSnap.ref.update(omit(profile, ['providerData'])) // fixes issue with bad write .then(() => profile) // Update the profile ) .catch((err) => { From 0f64cb6db91d9989fca9955d47ed74c5f75fb1bd Mon Sep 17 00:00:00 2001 From: prescottprue Date: Sun, 21 Jan 2018 02:02:11 -0800 Subject: [PATCH 2/4] feat(auth): firestore profile's now contain timestamps for dates instead of strings --- src/actions/auth.js | 28 ++++++++++++++++++++-------- src/constants.js | 18 +++++++++++++----- src/utils/index.js | 11 +++++++++-- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/actions/auth.js b/src/actions/auth.js index f14a29697..7b019780e 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -7,6 +7,7 @@ import { } from 'lodash' import { actionTypes } from '../constants' import { populate } from '../helpers' +import { stringToDate } from '../utils' import { getLoginMethodAndParams, updateProfileOnRTDB, @@ -209,14 +210,25 @@ export const createUserProfile = (dispatch, firebase, userData, profile) => { .collection(config.userProfile) .doc(userData.uid) .get() - .then( - profileSnap => - // update profile only if doesn't exist or if set by config - !config.updateProfileOnLogin && profileSnap.exists - ? profileSnap.data() - : profileSnap.ref.update(omit(profile, ['providerData'])) // fixes issue with bad write - .then(() => profile) // Update the profile - ) + .then(profileSnap => { + // Convert to JSON format (to prevent issue of writing invalid type to Firestore) + const userDataObject = userData.toJSON ? userData.toJSON() : userData + // Remove unnessesary auth params (configurable) and preserve types of timestamps + const newProfile = { + ...omit(userDataObject, config.keysToRemoveFromAuth), + avatarUrl: userDataObject.photoURL, // match profile pattern used for RTDB + createdAt: stringToDate(userDataObject.createdAt), + lastLoginAt: stringToDate(userDataObject.lastLoginAt) + } + // Return if config for updating profile is not enabled and profile exists + if (!config.updateProfileOnLogin && profileSnap.exists) { + return profileSnap.data() + } + // Create/Update the profile + return profileSnap.ref + .set(newProfile, { merge: true }) + .then(() => newProfile) + }) .catch((err) => { // Error reading user profile dispatch({ type: actionTypes.UNAUTHORIZED_ERROR, authError: err }) diff --git a/src/constants.js b/src/constants.js index bd2db143d..feb66a50d 100644 --- a/src/constants.js +++ b/src/constants.js @@ -5,7 +5,7 @@ * @example * import { constants } from 'react-redux-firebase' * constants.actionsPrefix === '@@reactReduxFirebase' // true -*/ + */ export const actionsPrefix = '@@reactReduxFirebase' /** @@ -55,7 +55,7 @@ export const actionsPrefix = '@@reactReduxFirebase' * @example * import { actionTypes } from 'react-redux-firebase' * actionTypes.SET === '@@reactReduxFirebase/SET' // true -*/ + */ export const actionTypes = { START: `${actionsPrefix}/START`, SET: `${actionsPrefix}/SET`, @@ -166,7 +166,7 @@ export const actionTypes = { * firestore helpers (**WARNING** Changing this will break firestoreConnect HOC. * Do **NOT** change to `'firestore'`) * @type {Object} -*/ + */ export const defaultConfig = { userProfile: null, presence: null, @@ -183,7 +183,15 @@ export const defaultConfig = { dispatchRemoveAction: false, enableEmptyAuthChanges: true, firebaseStateName: 'firebase', - attachAuthIsReady: false + attachAuthIsReady: false, + keysToRemoveFromAuth: [ + 'appName', + 'apiKey', + 'authDomain', + 'redirectEventId', + 'stsTokenManager', + 'uid' + ] } /** @@ -192,7 +200,7 @@ export const defaultConfig = { * @description List of all external auth providers that are supported * (firebase's email/anonymous included by default). * @private -*/ + */ export const supportedAuthProviders = [ 'google', 'github', diff --git a/src/utils/index.js b/src/utils/index.js index 25f72c8eb..f24b13efd 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -20,7 +20,14 @@ export const getDisplayName = Component => { return Component.displayName || Component.name || 'Component' } -export default getDisplayName - export const wrapDisplayName = (BaseComponent, hocName) => `${hocName}(${getDisplayName(BaseComponent)})` + +export const stringToDate = strInput => { + try { + return new Date(JSON.parse(strInput)) + } catch (err) { + console.error('Error parsing string to date:', err.message || err) // eslint-disable-line no-console + return strInput + } +} From d7d355a2353bcf430d3b8842dddf5ca76fa06bc3 Mon Sep 17 00:00:00 2001 From: prescottprue Date: Sun, 21 Jan 2018 02:18:41 -0800 Subject: [PATCH 3/4] feat(core): prettier added --- .eslintrc | 14 +- package.json | 4 + src/actions/auth.js | 318 +++++---- src/actions/query.js | 94 ++- src/actions/storage.js | 95 +-- src/compose.js | 1042 ++++++++++++++-------------- src/createFirebaseInstance.js | 88 ++- src/enhancer.js | 45 +- src/firebaseConnect.js | 106 +-- src/firestoreConnect.js | 8 +- src/helpers.js | 31 +- src/reducers.js | 42 +- src/utils/actions.js | 45 +- src/utils/auth.js | 42 +- src/utils/events.js | 33 +- src/utils/index.js | 2 +- src/utils/populate.js | 87 +-- src/utils/query.js | 70 +- src/utils/reducers.js | 25 +- src/utils/storage.js | 20 +- src/withFirebase.js | 2 +- src/withFirestore.js | 2 +- test/setup.js | 16 +- test/unit/actions/auth.spec.js | 273 ++++++-- test/unit/actions/query.spec.js | 65 +- test/unit/actions/storage.spec.js | 92 +-- test/unit/enhancer.spec.js | 104 +-- test/unit/firebaseConnect.spec.js | 25 +- test/unit/firestoreConnect.spec.js | 17 +- test/unit/helpers.spec.js | 317 ++++----- test/unit/reducer.spec.js | 488 +++++++------ test/unit/utils/actions.spec.js | 14 +- test/unit/utils/auth.spec.js | 44 +- test/unit/utils/events.spec.js | 18 +- test/unit/utils/populate.spec.js | 64 +- test/unit/utils/query.spec.js | 122 ++-- test/unit/utils/storage.spec.js | 12 +- test/unit/withFirebase.spec.js | 8 +- test/unit/withFirestore.spec.js | 8 +- test/utils.js | 88 ++- 40 files changed, 2275 insertions(+), 1715 deletions(-) diff --git a/.eslintrc b/.eslintrc index 764c8e70d..1d1614ee8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,8 +2,8 @@ root: true parser: babel-eslint -extends: [standard, standard-react] -plugins: [babel, react] +extends: [standard, standard-react, prettier, prettier/react] +plugins: [babel, react, prettier] env: browser: true @@ -14,3 +14,13 @@ env: rules: semi: [2, 'never'] no-console: 'error' + prettier/prettier: ['error', { + singleQuote: true, + trailingComma: 'es6', + semi: false, + bracketSpacing: true, + jsxBracketSameLine: true, + printWidth: 80, + tabWidth: 2, + useTabs: false + }] diff --git a/package.json b/package.json index 6a64c0af2..fe307dc4a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "clean": "rimraf es lib dist coverage", "lint": "eslint src/** test/unit/** test/setup.js", "lint:fix": "npm run lint -- --fix", + "format": "prettier --single-quote --no-semi --trailing-comma none --write \"src/**/*.js\" \"test/**/*.js\"", "test": "mocha -R spec ./test/unit/**", "test:cov": "istanbul cover ./node_modules/mocha/bin/_mocha ./test/unit/**", "codecov": "cat coverage/lcov.info | codecov", @@ -90,11 +91,13 @@ "documentation-markdown-api-theme": "^1.0.2", "enzyme": "^2.3.0", "eslint": "^4.8.0", + "eslint-config-prettier": "^2.3.0", "eslint-config-standard": "^10.2.1", "eslint-config-standard-react": "^5.0.0", "eslint-plugin-babel": "^4.1.2", "eslint-plugin-import": "^2.7.0", "eslint-plugin-node": "^5.2.0", + "eslint-plugin-prettier": "^2.1.2", "eslint-plugin-promise": "^3.5.0", "eslint-plugin-react": "^7.4.0", "eslint-plugin-standard": "^3.0.1", @@ -104,6 +107,7 @@ "istanbul": "1.1.0-alpha.1", "jsdom": "^11.3.0", "mocha": "^3.5.3", + "prettier": "^1.10.2", "react": "^15.4.2", "react-addons-test-utils": "^15.4.2", "react-dom": "^15.4.2", diff --git a/src/actions/auth.js b/src/actions/auth.js index 7b019780e..b8bef432a 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -1,10 +1,4 @@ -import { - isArray, - isString, - isFunction, - forEach, - omit -} from 'lodash' +import { isArray, isString, isFunction, forEach, omit } from 'lodash' import { actionTypes } from '../constants' import { populate } from '../helpers' import { stringToDate } from '../utils' @@ -13,10 +7,7 @@ import { updateProfileOnRTDB, updateProfileOnFirestore } from '../utils/auth' -import { - promisesForPopulate, - getPopulateObjs -} from '../utils/populate' +import { promisesForPopulate, getPopulateObjs } from '../utils/populate' /** * @description Dispatch login error action @@ -35,7 +26,7 @@ const dispatchLoginError = (dispatch, authError) => * @param {Object} firebase - Internal firebase object * @private */ -export const unWatchUserProfile = (firebase) => { +export const unWatchUserProfile = firebase => { const { authUid, config: { userProfile, useFirestoreForProfile } @@ -45,7 +36,8 @@ export const unWatchUserProfile = (firebase) => { // Call profile onSnapshot unsubscribe stored on profileWatch firebase._.profileWatch() } else { - firebase.database() + firebase + .database() .ref() .child(`${userProfile}/${authUid}`) .off('value', firebase._.profileWatch) @@ -54,7 +46,7 @@ export const unWatchUserProfile = (firebase) => { } } -const getProfileFromSnap = (snap) => { +const getProfileFromSnap = snap => { // Real Time Database if (snap && snap.val) { return snap.val() @@ -75,7 +67,11 @@ const getProfileFromSnap = (snap) => { * Snapshot from profile watcher * @private */ -export const handleProfileWatchResponse = (dispatch, firebase, userProfileSnap) => { +export const handleProfileWatchResponse = ( + dispatch, + firebase, + userProfileSnap +) => { const { profileParamsToPopulate, autoPopulateProfile, @@ -85,8 +81,7 @@ export const handleProfileWatchResponse = (dispatch, firebase, userProfileSnap) if ( !profileParamsToPopulate || useFirestoreForProfile || // populating profile through firestore not yet supported - (!isArray(profileParamsToPopulate) && - !isString(profileParamsToPopulate)) + (!isArray(profileParamsToPopulate) && !isString(profileParamsToPopulate)) ) { if (useFirestoreForProfile && profileParamsToPopulate) { console.warn('Profile population is not yet supported for Firestore') // eslint-disable-line no-console @@ -99,34 +94,38 @@ export const handleProfileWatchResponse = (dispatch, firebase, userProfileSnap) userProfileSnap.key, profile, profileParamsToPopulate - ).then((data) => { - // Fire actions for placement of data gathered in populate into redux - forEach(data, (result, path) => { - dispatch({ - type: actionTypes.SET, - path, - data: result, - timestamp: Date.now(), - requesting: false, - requested: true + ) + .then(data => { + // Fire actions for placement of data gathered in populate into redux + forEach(data, (result, path) => { + dispatch({ + type: actionTypes.SET, + path, + data: result, + timestamp: Date.now(), + requesting: false, + requested: true + }) }) + if (!autoPopulateProfile) { + // Dispatch action with profile combined with populated parameters + dispatch({ type: actionTypes.SET_PROFILE, profile }) + } else { + // Auto Populate profile + const populates = getPopulateObjs(profileParamsToPopulate) + const profile = userProfileSnap.val() + dispatch({ + type: actionTypes.SET_PROFILE, + profile: populate({ profile, data }, 'profile', populates) + }) + } }) - if (!autoPopulateProfile) { - // Dispatch action with profile combined with populated parameters - dispatch({ type: actionTypes.SET_PROFILE, profile }) - } else { - // Auto Populate profile - const populates = getPopulateObjs(profileParamsToPopulate) - const profile = userProfileSnap.val() + .catch(err => { + // Error retrieving data for population onto profile. dispatch({ - type: actionTypes.SET_PROFILE, - profile: populate({ profile, data }, 'profile', populates) + type: actionTypes.UNAUTHORIZED_ERROR, + authError: `Error during profile population: ${err.message}` }) - } - }) - .catch((err) => { - // Error retrieving data for population onto profile. - dispatch({ type: actionTypes.UNAUTHORIZED_ERROR, authError: `Error during profile population: ${err.message}` }) // Update profile with un-populated version dispatch({ type: actionTypes.SET_PROFILE, profile }) }) @@ -194,10 +193,12 @@ export const createUserProfile = (dispatch, firebase, userData, profile) => { try { profile = config.profileFactory(userData, profile) // eslint-disable-line no-param-reassign } catch (err) { - console.error( // eslint-disable-line no-console + /* eslint-disable no-console */ + console.error( 'Error occured within profileFactory function:', err.message || err ) + /* eslint-enable no-console */ return Promise.reject(err) } } @@ -229,7 +230,7 @@ export const createUserProfile = (dispatch, firebase, userData, profile) => { .set(newProfile, { merge: true }) .then(() => newProfile) }) - .catch((err) => { + .catch(err => { // Error reading user profile dispatch({ type: actionTypes.UNAUTHORIZED_ERROR, authError: err }) return Promise.reject(err) @@ -249,7 +250,7 @@ export const createUserProfile = (dispatch, firebase, userData, profile) => { ? profileSnap.val() : profileSnap.ref.update(profile).then(() => profile) // Update the profile ) - .catch((err) => { + .catch(err => { // Error reading user profile dispatch({ type: actionTypes.UNAUTHORIZED_ERROR, authError: err }) return Promise.reject(err) @@ -287,7 +288,7 @@ const setupPresence = (dispatch, firebase) => { if (sessionsRef) { sessionsRef = ref.child(sessions) } - amOnline.on('value', (snapShot) => { + amOnline.on('value', snapShot => { if (!snapShot.val()) return // user is online if (sessionsRef) { @@ -304,7 +305,8 @@ const setupPresence = (dispatch, firebase) => { // set authUid as priority for easy sorting session.setPriority(authUid) } - session.child('endedAt') + session + .child('endedAt') .onDisconnect() .set(firebase.database.ServerValue.TIMESTAMP, () => { dispatch({ type: actionTypes.SESSION_END }) @@ -405,7 +407,8 @@ export const init = (dispatch, firebase) => { } dispatch({ type: actionTypes.AUTHENTICATION_INIT_STARTED }) // Set Auth State listener - firebase.auth() + firebase + .auth() .onAuthStateChanged(authData => handleAuthStateChange(dispatch, firebase, authData) ) @@ -422,10 +425,8 @@ export const init = (dispatch, firebase) => { firebase .auth() .getRedirectResult() - .then(authData => - handleRedirectResult(dispatch, firebase, authData) - ) - .catch((error) => { + .then(authData => handleRedirectResult(dispatch, firebase, authData)) + .catch(error => { dispatchLoginError(dispatch, error) return Promise.reject(error) }) @@ -458,8 +459,10 @@ export const login = (dispatch, firebase, credentials) => { const { method, params } = getLoginMethodAndParams(firebase, credentials) - return firebase.auth()[method](...params) - .then((userData) => { + return firebase + .auth() + [method](...params) + .then(userData => { // Handle null response from getRedirectResult before redirect has happened if (!userData) return Promise.resolve(null) @@ -486,39 +489,26 @@ export const login = (dispatch, firebase, credentials) => { // Modify confirm method to include profile creation return { ...userData, - confirm: (code) => + confirm: code => // Call original confirm - userData.confirm(code) - .then(({ user, additionalUserInfo }) => - createUserProfile( - dispatch, - firebase, - user, - { - phoneNumber: user.providerData[0].phoneNumber, - providerData: user.providerData - } - ) - .then((profile) => ({ profile, user, additionalUserInfo })) - ) + userData.confirm(code).then(({ user, additionalUserInfo }) => + createUserProfile(dispatch, firebase, user, { + phoneNumber: user.providerData[0].phoneNumber, + providerData: user.providerData + }).then(profile => ({ profile, user, additionalUserInfo })) + ) } } // Create profile when logging in with external provider const user = userData.user || userData - return createUserProfile( - dispatch, - firebase, - user, - { - email: user.email, - displayName: user.providerData[0].displayName || user.email, - avatarUrl: user.providerData[0].photoURL, - providerData: user.providerData - } - ) - .then((profile) => ({ profile, ...userData })) + return createUserProfile(dispatch, firebase, user, { + email: user.email, + displayName: user.providerData[0].displayName || user.email, + avatarUrl: user.providerData[0].photoURL, + providerData: user.providerData + }).then(profile => ({ profile, ...userData })) }) .catch(err => { dispatchLoginError(dispatch, err) @@ -533,7 +523,8 @@ export const login = (dispatch, firebase, credentials) => { * @private */ export const logout = (dispatch, firebase) => - firebase.auth() + firebase + .auth() .signOut() .then(() => { dispatch({ @@ -553,7 +544,12 @@ export const logout = (dispatch, firebase) => * @return {Promise} * @private */ -export const createUser = (dispatch, firebase, { email, password, signIn }, profile) => { +export const createUser = ( + dispatch, + firebase, + { email, password, signIn }, + profile +) => { dispatchLoginError(dispatch, null) if (!email || !password) { @@ -562,30 +558,45 @@ export const createUser = (dispatch, firebase, { email, password, signIn }, prof return Promise.reject(error) } - return firebase.auth() + return firebase + .auth() .createUserWithEmailAndPassword(email, password) - .then((userData) => - // Login to newly created account if signIn flag is not set to false - firebase.auth().currentUser || (!!signIn && signIn === false) - ? createUserProfile(dispatch, firebase, userData, profile || { email }) - : login(dispatch, firebase, { email, password }) - .then(() => - createUserProfile(dispatch, firebase, userData, profile || { email }) - ) - .catch(err => { - if (err) { - switch (err.code) { - case 'auth/user-not-found': - dispatchLoginError(dispatch, new Error('The specified user account does not exist.')) - break - default: - dispatchLoginError(dispatch, err) - } - } - return Promise.reject(err) - }) + .then( + userData => + // Login to newly created account if signIn flag is not set to false + firebase.auth().currentUser || (!!signIn && signIn === false) + ? createUserProfile( + dispatch, + firebase, + userData, + profile || { email } + ) + : login(dispatch, firebase, { email, password }) + .then(() => + createUserProfile( + dispatch, + firebase, + userData, + profile || { email } + ) + ) + .catch(err => { + if (err) { + switch (err.code) { + case 'auth/user-not-found': + dispatchLoginError( + dispatch, + new Error('The specified user account does not exist.') + ) + break + default: + dispatchLoginError(dispatch, err) + } + } + return Promise.reject(err) + }) ) - .catch((err) => { + .catch(err => { dispatchLoginError(dispatch, err) return Promise.reject(err) }) @@ -601,13 +612,17 @@ export const createUser = (dispatch, firebase, { email, password, signIn }, prof */ export const resetPassword = (dispatch, firebase, email) => { dispatchLoginError(dispatch, null) - return firebase.auth() + return firebase + .auth() .sendPasswordResetEmail(email) - .catch((err) => { + .catch(err => { if (err) { switch (err.code) { case 'auth/user-not-found': - dispatchLoginError(dispatch, new Error('The specified user account does not exist.')) + dispatchLoginError( + dispatch, + new Error('The specified user account does not exist.') + ) break default: dispatchLoginError(dispatch, err) @@ -628,16 +643,23 @@ export const resetPassword = (dispatch, firebase, email) => { */ export const confirmPasswordReset = (dispatch, firebase, code, password) => { dispatchLoginError(dispatch, null) - return firebase.auth() + return firebase + .auth() .confirmPasswordReset(code, password) - .catch((err) => { + .catch(err => { if (err) { switch (err.code) { case 'auth/expired-action-code': - dispatchLoginError(dispatch, new Error('The action code has expired.')) + dispatchLoginError( + dispatch, + new Error('The action code has expired.') + ) break case 'auth/invalid-action-code': - dispatchLoginError(dispatch, new Error('The action code is invalid.')) + dispatchLoginError( + dispatch, + new Error('The action code is invalid.') + ) break case 'auth/user-disabled': dispatchLoginError(dispatch, new Error('The user is disabled.')) @@ -646,7 +668,10 @@ export const confirmPasswordReset = (dispatch, firebase, code, password) => { dispatchLoginError(dispatch, new Error('The user is not found.')) break case 'auth/weak-password': - dispatchLoginError(dispatch, new Error('The password is not strong enough.')) + dispatchLoginError( + dispatch, + new Error('The password is not strong enough.') + ) break default: dispatchLoginError(dispatch, err) @@ -666,9 +691,10 @@ export const confirmPasswordReset = (dispatch, firebase, code, password) => { */ export const verifyPasswordResetCode = (dispatch, firebase, code) => { dispatchLoginError(dispatch, null) - return firebase.auth() + return firebase + .auth() .verifyPasswordResetCode(code) - .catch((err) => { + .catch(err => { if (err) { dispatchLoginError(dispatch, err) } @@ -696,28 +722,28 @@ export const updateProfile = (dispatch, firebase, profileUpdate) => { ? updateProfileOnFirestore : updateProfileOnRTDB return updatePromise(firebase, profileUpdate) - .then((snap) => { + .then(snap => { dispatch({ type: actionTypes.PROFILE_UPDATE_SUCCESS, payload: config.useFirestoreForProfile ? snap.data() : snap.val() }) return snap }) - .catch((error) => { + .catch(error => { dispatch({ type: actionTypes.PROFILE_UPDATE_ERROR, error }) return Promise.reject(error) }) } /** - * @description Update Auth Object. Internally calls - * `firebase.auth().currentUser.updateProfile` as seen [in the firebase docs](https://firebase.google.com/docs/auth/web/manage-users#update_a_users_profile). - * @param {Function} dispatch - Action dispatch function - * @param {Object} firebase - Internal firebase object - * @param {Object} profileUpdate - Update to be auth object - * @return {Promise} - * @private - */ + * @description Update Auth Object. Internally calls + * `firebase.auth().currentUser.updateProfile` as seen [in the firebase docs](https://firebase.google.com/docs/auth/web/manage-users#update_a_users_profile). + * @param {Function} dispatch - Action dispatch function + * @param {Object} firebase - Internal firebase object + * @param {Object} profileUpdate - Update to be auth object + * @return {Promise} + * @private + */ export const updateAuth = (dispatch, firebase, authUpdate, updateInProfile) => { dispatch({ type: actionTypes.AUTH_UPDATE_START, payload: authUpdate }) @@ -727,9 +753,10 @@ export const updateAuth = (dispatch, firebase, authUpdate, updateInProfile) => { return Promise.reject(error) } - return firebase.auth().currentUser - .updateProfile(authUpdate) - .then((payload) => { + return firebase + .auth() + .currentUser.updateProfile(authUpdate) + .then(payload => { dispatch({ type: actionTypes.AUTH_UPDATE_SUCCESS, payload: firebase.auth().currentUser @@ -739,7 +766,7 @@ export const updateAuth = (dispatch, firebase, authUpdate, updateInProfile) => { } return payload }) - .catch((error) => { + .catch(error => { dispatch({ type: actionTypes.AUTH_UPDATE_ERROR, error }) return Promise.reject(error) }) @@ -765,16 +792,17 @@ export const updateEmail = (dispatch, firebase, newEmail, updateInProfile) => { return Promise.reject(error) } - return firebase.auth().currentUser - .updateEmail(newEmail) - .then((payload) => { + return firebase + .auth() + .currentUser.updateEmail(newEmail) + .then(payload => { dispatch({ type: actionTypes.EMAIL_UPDATE_SUCCESS, payload: newEmail }) if (updateInProfile) { return updateProfile(dispatch, firebase, { email: newEmail }) } return payload }) - .catch((error) => { + .catch(error => { dispatch({ type: actionTypes.EMAIL_UPDATE_ERROR, error }) return Promise.reject(error) }) @@ -797,13 +825,15 @@ export const reloadAuth = (dispatch, firebase) => { return Promise.reject(error) } - return firebase.auth().currentUser.reload() + return firebase + .auth() + .currentUser.reload() .then(() => { const auth = firebase.auth().currentUser dispatch({ type: actionTypes.AUTH_RELOAD_SUCCESS, payload: auth }) return auth }) - .catch((error) => { + .catch(error => { dispatch({ type: actionTypes.AUTH_RELOAD_ERROR, error }) return Promise.reject(error) }) @@ -827,12 +857,14 @@ export const linkWithCredential = (dispatch, firebase, credential) => { return Promise.reject(error) } - return firebase.auth().currentUser.linkWithCredential(credential) - .then((auth) => { + return firebase + .auth() + .currentUser.linkWithCredential(credential) + .then(auth => { dispatch({ type: actionTypes.AUTH_LINK_SUCCESS, payload: auth }) return auth }) - .catch((error) => { + .catch(error => { dispatch({ type: actionTypes.AUTH_LINK_ERROR, error }) return Promise.reject(error) }) @@ -849,8 +881,18 @@ export const linkWithCredential = (dispatch, firebase, credential) => { * @param {Object} applicationVerifier - Phone number * @return {Promise} Resolves with auth */ -export const signInWithPhoneNumber = (firebase, dispatch, phoneNumber, applicationVerifier, options = {}) => { - return login(dispatch, firebase, { phoneNumber, applicationVerifier, ...options }) +export const signInWithPhoneNumber = ( + firebase, + dispatch, + phoneNumber, + applicationVerifier, + options = {} +) => { + return login(dispatch, firebase, { + phoneNumber, + applicationVerifier, + ...options + }) } export default { diff --git a/src/actions/query.js b/src/actions/query.js index bc9d8fc67..43e82255f 100644 --- a/src/actions/query.js +++ b/src/actions/query.js @@ -26,7 +26,15 @@ export const watchEvent = (firebase, dispatch, options) => { if (!firebase.database || typeof firebase.database !== 'function') { throw new Error('Firebase database is required to create watchers') } - const { type, path, populates, queryParams, queryId, isQuery, storeAs } = options + const { + type, + path, + populates, + queryParams, + queryId, + isQuery, + storeAs + } = options const watchPath = !storeAs ? path : `${path}@${storeAs}` const id = queryId || getQueryIdFromPath(path) const counter = getWatcherCount(firebase, type, watchPath, id) @@ -42,7 +50,8 @@ export const watchEvent = (firebase, dispatch, options) => { setWatcher(firebase, dispatch, type, watchPath, id) if (type === 'first_child') { - return firebase.database() + return firebase + .database() .ref() .child(path) .orderByKey() @@ -67,7 +76,10 @@ export const watchEvent = (firebase, dispatch, options) => { }) } - let query = firebase.database().ref().child(path) + let query = firebase + .database() + .ref() + .child(path) if (isQuery) { query = applyParamsToQuery(queryParams, query) @@ -77,7 +89,8 @@ export const watchEvent = (firebase, dispatch, options) => { // Handle once queries if (type === 'once') { - return query.once('value') + return query + .once('value') .then(snapshot => { if (snapshot.val() === null) { return dispatch({ @@ -115,34 +128,40 @@ export const watchEvent = (firebase, dispatch, options) => { // Handle all other queries /* istanbul ignore next: is run by tests but doesn't show in coverage */ - query.on(type, (snapshot) => { - let data = (type === 'child_removed') ? undefined : snapshot.val() - const resultPath = storeAs || (type === 'value') ? path : `${path}/${snapshot.key}` + query.on( + type, + snapshot => { + let data = type === 'child_removed' ? undefined : snapshot.val() + const resultPath = + storeAs || type === 'value' ? path : `${path}/${snapshot.key}` - // Dispatch standard event if no populates exists - if (!populates) { - // create an array for preserving order of children under ordered - const ordered = type === 'child_added' - ? [{ key: snapshot.key, value: snapshot.val() }] - : orderedFromSnapshot(snapshot) - return dispatch({ - type: actionTypes.SET, - path: storeAs || resultPath, - data, - ordered + // Dispatch standard event if no populates exists + if (!populates) { + // create an array for preserving order of children under ordered + const ordered = + type === 'child_added' + ? [{ key: snapshot.key, value: snapshot.val() }] + : orderedFromSnapshot(snapshot) + return dispatch({ + type: actionTypes.SET, + path: storeAs || resultPath, + data, + ordered + }) + } + // populate and dispatch associated actions if populates exist + return populateAndDispatch(firebase, dispatch, { + path, + storeAs, + snapshot, + data: snapshot.val(), + populates }) + }, + err => { + dispatch({ type: actionTypes.ERROR, payload: err }) } - // populate and dispatch associated actions if populates exist - return populateAndDispatch(firebase, dispatch, { - path, - storeAs, - snapshot, - data: snapshot.val(), - populates - }) - }, (err) => { - dispatch({ type: actionTypes.ERROR, payload: err }) - }) + ) } /** @@ -158,7 +177,11 @@ export const watchEvent = (firebase, dispatch, options) => { * @param {String} config.queryId - Id of the query (used for idendifying) * in internal watchers list */ -export const unWatchEvent = (firebase, dispatch, { type, path, storeAs, queryId }) => { +export const unWatchEvent = ( + firebase, + dispatch, + { type, path, storeAs, queryId } +) => { const watchPath = !storeAs ? path : `${path}@${storeAs}` unsetWatcher(firebase, dispatch, type, watchPath, queryId) } @@ -183,9 +206,7 @@ export const watchEvents = (firebase, dispatch, events) => { * @param {Array} events - List of events for which to remove watchers */ export const unWatchEvents = (firebase, dispatch, events) => - events.forEach(event => - unWatchEvent(firebase, dispatch, event) - ) + events.forEach(event => unWatchEvent(firebase, dispatch, event)) /** * @description Add watchers to a list of events @@ -200,14 +221,17 @@ export const unWatchEvents = (firebase, dispatch, events) => export const remove = (firebase, dispatch, path, options = {}) => { const { dispatchAction = true } = options const { dispatchRemoveAction } = firebase._.config - return firebase.database().ref(path).remove() + return firebase + .database() + .ref(path) + .remove() .then(() => { if (dispatchRemoveAction && dispatchAction) { dispatch({ type: actionTypes.REMOVE, path }) } return path }) - .catch((err) => { + .catch(err => { dispatch({ type: actionTypes.ERROR, payload: err }) return Promise.reject(err) }) diff --git a/src/actions/storage.js b/src/actions/storage.js index cce6a685e..addd0eb93 100644 --- a/src/actions/storage.js +++ b/src/actions/storage.js @@ -23,31 +23,33 @@ const { * @private */ const uploadFileWithProgress = (dispatch, firebase, { path, file }) => { - const uploadEvent = firebase.storage().ref(`${path}/${file.name}`).put(file) + const uploadEvent = firebase + .storage() + .ref(`${path}/${file.name}`) + .put(file) // TODO: Allow config to control whether progress it set to state or not - const unListen = uploadEvent.on( - firebase.storage.TaskEvent.STATE_CHANGED, - { - next: (snapshot) => { - dispatch({ - type: FILE_UPLOAD_PROGRESS, - path, - payload: { - snapshot, - percent: Math.floor(snapshot.bytesTransferred / snapshot.totalBytes * 100) - } - }) - }, - error: (err) => { - dispatch({ type: FILE_UPLOAD_ERROR, path, payload: err }) - unListen() - }, - complete: () => { - dispatch({ type: FILE_UPLOAD_COMPLETE, path, payload: file }) - unListen() - } + const unListen = uploadEvent.on(firebase.storage.TaskEvent.STATE_CHANGED, { + next: snapshot => { + dispatch({ + type: FILE_UPLOAD_PROGRESS, + path, + payload: { + snapshot, + percent: Math.floor( + snapshot.bytesTransferred / snapshot.totalBytes * 100 + ) + } + }) + }, + error: err => { + dispatch({ type: FILE_UPLOAD_ERROR, path, payload: err }) + unListen() + }, + complete: () => { + dispatch({ type: FILE_UPLOAD_COMPLETE, path, payload: file }) + unListen() } - ) + }) return uploadEvent } @@ -71,19 +73,24 @@ export const uploadFile = (dispatch, firebase, config) => { throw new Error('Firebase storage is required to upload files') } const { path, file, dbPath, options = { progress: false } } = config - const nameFromOptions = options.name && isFunction(options.name) - ? options.name(file, firebase, config) - : options.name + const nameFromOptions = + options.name && isFunction(options.name) + ? options.name(file, firebase, config) + : options.name const filename = nameFromOptions || file.name dispatch({ type: FILE_UPLOAD_START, payload: { ...config, filename } }) - const uploadPromise = () => options.progress - ? uploadFileWithProgress(dispatch, firebase, { path, file }) - : firebase.storage().ref(`${path}/${filename}`).put(file) + const uploadPromise = () => + options.progress + ? uploadFileWithProgress(dispatch, firebase, { path, file }) + : firebase + .storage() + .ref(`${path}/${filename}`) + .put(file) return uploadPromise() - .then((uploadTaskSnaphot) => { + .then(uploadTaskSnaphot => { if (!dbPath || !firebase.database) { dispatch({ type: FILE_UPLOAD_COMPLETE, @@ -99,17 +106,18 @@ export const uploadFile = (dispatch, firebase, config) => { const fileData = isFunction(fileMetadataFactory) ? fileMetadataFactory(uploadTaskSnaphot, firebase) : { - name, - fullPath, - downloadURL: downloadURLs[0], - createdAt: firebase.database.ServerValue.TIMESTAMP - } + name, + fullPath, + downloadURL: downloadURLs[0], + createdAt: firebase.database.ServerValue.TIMESTAMP + } // TODO: Support uploading metadata to Firestore - return firebase.database() + return firebase + .database() .ref(dbPath) .push(fileData) - .then((metaDataSnapshot) => { + .then(metaDataSnapshot => { const payload = { snapshot: metaDataSnapshot, key: metaDataSnapshot.key, @@ -125,7 +133,7 @@ export const uploadFile = (dispatch, firebase, config) => { return payload }) }) - .catch((err) => { + .catch(err => { dispatch({ type: FILE_UPLOAD_ERROR, path, payload: err }) return Promise.reject(err) }) @@ -159,13 +167,6 @@ export const uploadFiles = (dispatch, firebase, { files, ...other }) => export const deleteFile = (dispatch, firebase, { path, dbPath }) => wrapInDispatch(dispatch, { method: deleteFileFromFb, - args: [ - firebase, - { path, dbPath } - ], - types: [ - FILE_DELETE_START, - FILE_DELETE_COMPLETE, - FILE_DELETE_ERROR - ] + args: [firebase, { path, dbPath }], + types: [FILE_DELETE_START, FILE_DELETE_COMPLETE, FILE_DELETE_ERROR] }) diff --git a/src/compose.js b/src/compose.js index 51082e761..8a6f92775 100644 --- a/src/compose.js +++ b/src/compose.js @@ -86,534 +86,536 @@ let firebaseInstance * } * } */ -export default (fbConfig, otherConfig) => next => - (reducer, initialState, middleware) => { - const store = next(reducer, initialState, middleware) - const { dispatch } = store - - // Combine all configs - const configs = Object.assign({}, defaultConfig, fbConfig, otherConfig) - - validateConfig(configs) - - // Initialize Firebase - try { - firebase.initializeApp(fbConfig) - } catch (err) {} // silence reinitialize warning (hot-reloading) - - // Enable Logging based on config - if (configs.enableLogging) { - firebase.database.enableLogging(configs.enableLogging) - } +export default (fbConfig, otherConfig) => next => ( + reducer, + initialState, + middleware +) => { + const store = next(reducer, initialState, middleware) + const { dispatch } = store + + // Combine all configs + const configs = Object.assign({}, defaultConfig, fbConfig, otherConfig) + + validateConfig(configs) + + // Initialize Firebase + try { + firebase.initializeApp(fbConfig) + } catch (err) {} // silence reinitialize warning (hot-reloading) + + // Enable Logging based on config + if (configs.enableLogging) { + firebase.database.enableLogging(configs.enableLogging) + } - // Handle react-native - if (configs.ReactNative) { - configs.enableRedirectHandling = false - const { AsyncStorage } = configs.ReactNative - // Stub firebase's internal's with react-native (based on firebase's react-native index file) - firebase.INTERNAL.extendNamespace({ - INTERNAL: { - reactNative: { - AsyncStorage - } + // Handle react-native + if (configs.ReactNative) { + configs.enableRedirectHandling = false + const { AsyncStorage } = configs.ReactNative + // Stub firebase's internal's with react-native (based on firebase's react-native index file) + firebase.INTERNAL.extendNamespace({ + INTERNAL: { + reactNative: { + AsyncStorage } - }) - } - - const rootRef = firebase.database().ref() - - const instance = Object.defineProperty(firebase, '_', { - value: { - watchers: {}, - config: configs, - authUid: null - }, - writable: true, - enumerable: true, - configurable: true + } }) + } - /** - * @private - * @description Calls a method and attaches meta to value object - * @param {String} method - Method to run with meta attached - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete - * @return {Promise} Containing reference snapshot - */ - const withMeta = (method, path, value, onComplete) => { - if (isObject(value)) { - const prefix = method === 'update' ? 'updated' : 'created' - const dataWithMeta = { - ...value, - [`${prefix}At`]: firebase.database.ServerValue.TIMESTAMP - } - if (instance.auth().currentUser) { - dataWithMeta[`${prefix}By`] = instance.auth().currentUser.uid - } - return rootRef.child(path)[method](dataWithMeta, onComplete) + const rootRef = firebase.database().ref() + + const instance = Object.defineProperty(firebase, '_', { + value: { + watchers: {}, + config: configs, + authUid: null + }, + writable: true, + enumerable: true, + configurable: true + }) + + /** + * @private + * @description Calls a method and attaches meta to value object + * @param {String} method - Method to run with meta attached + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete + * @return {Promise} Containing reference snapshot + */ + const withMeta = (method, path, value, onComplete) => { + if (isObject(value)) { + const prefix = method === 'update' ? 'updated' : 'created' + const dataWithMeta = { + ...value, + [`${prefix}At`]: firebase.database.ServerValue.TIMESTAMP + } + if (instance.auth().currentUser) { + dataWithMeta[`${prefix}By`] = instance.auth().currentUser.uid } - return rootRef.child(path)[method](value, onComplete) + return rootRef.child(path)[method](dataWithMeta, onComplete) } + return rootRef.child(path)[method](value, onComplete) + } - /** - * @private - * @description Sets data to Firebase. - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component } from 'react' - * import PropTypes from 'prop-types' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { set } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const set = (path, value, onComplete) => - rootRef.child(path).set(value, onComplete) - - /** - * @private - * @description Sets data to Firebase along with meta data. Currently, - * this includes createdAt and createdBy. *Warning* using this function - * may have unintented consequences (setting createdAt even if data already - * exists) - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - */ - const setWithMeta = (path, value, onComplete) => - withMeta('set', path, value, onComplete) - - /** - * @private - * @description Pushes data to Firebase. - * @param {String} path - Path to location on Firebase which to push - * @param {Object|String|Boolean|Number} value - Value to push to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component } from 'react' - * import PropTypes from 'prop-types' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { push } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const push = (path, value, onComplete) => - rootRef.child(path).push(value, onComplete) - - /** - * @private - * @description Pushes data to Firebase along with meta data. Currently, - * this includes createdAt and createdBy. - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - */ - const pushWithMeta = (path, value, onComplete) => - withMeta('push', path, value, onComplete) - - /** - * @private - * @description Updates data on Firebase and sends new data. - * @param {String} path - Path to location on Firebase which to update - * @param {Object|String|Boolean|Number} value - Value to update to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component } from 'react' - * import PropTypes from 'prop-types' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { update } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const update = (path, value, onComplete) => - rootRef.child(path).update(value, onComplete) - - /** - * @private - * @description Updates data on Firebase along with meta. *Warning* - * using this function may have unintented consequences (setting - * createdAt even if data already exists) - * @param {String} path - Path to location on Firebase which to update - * @param {Object|String|Boolean|Number} value - Value to update to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - */ - const updateWithMeta = (path, value, onComplete) => - withMeta('update', path, value, onComplete) - - /** - * @private - * @description Removes data from Firebase at a given path. - * @param {String} path - Path to location on Firebase which to remove - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component } from 'react' - * import PropTypes from 'prop-types' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { remove } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const remove = (path, onComplete) => - rootRef.child(path).remove(onComplete) - - /** - * @private - * @description Sets data to Firebase only if the path does not already - * exist, otherwise it rejects. Internally uses a Firebase transaction to - * prevent a race condition between seperate clients calling uniqueSet. - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component } from 'react' - * import PropTypes from 'prop-types' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { uniqueSet } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const uniqueSet = (path, value, onComplete) => - rootRef.child(path) - .transaction(d => d === null ? value : undefined) - .then(({ committed, snapshot }) => { - if (!committed) { - const newError = new Error('Path already exists.') - if (onComplete) onComplete(newError) - return Promise.reject(newError) - } - if (onComplete) onComplete(snapshot) - return snapshot - }) - - /** - * @private - * @description Upload a file to Firebase Storage with the option to store - * its metadata in Firebase Database - * @param {String} path - Path to location on Firebase which to set - * @param {File} file - File object to upload (usually first element from - * array output of select-file or a drag/drop `onDrop`) - * @param {String} dbPath - Database path to place uploaded file metadata - * @return {Promise} Containing the File object - */ - const uploadFile = (path, file, dbPath) => - storageActions.uploadFile(dispatch, instance, { path, file, dbPath }) - - /** - * @private - * @description Upload multiple files to Firebase Storage with the option - * to store their metadata in Firebase Database - * @param {String} path - Path to location on Firebase which to set - * @param {Array} files - Array of File objects to upload (usually from - * a select-file or a drag/drop `onDrop`) - * @param {String} dbPath - Database path to place uploaded files metadata. - * @return {Promise} Containing an array of File objects - */ - const uploadFiles = (path, files, dbPath) => - storageActions.uploadFiles(dispatch, instance, { path, files, dbPath }) - - /** - * @private - * @description Delete a file from Firebase Storage with the option to - * remove its metadata in Firebase Database - * @param {String} path - Path to location on Firebase which to set - * @param {String} dbPath - Database path to place uploaded file metadata - * @return {Promise} Containing the File object - */ - const deleteFile = (path, dbPath) => - storageActions.deleteFile(dispatch, instance, { path, dbPath }) - - /** - * @private - * @description Create watch event for Firebase Realtime Database - * @param {String} type - Type of watch event ('value', 'once', etc) - * @param {String} path - Database path on which to setup watch event - * @param {Object|String} options - Name of listener results within redux - * store. If string is passed, it is used as storeAs. - * @param {String} options.storeAs - Name of listener results within redux store - * @param {Array} options.queryParams - List of query parameters - * @param {Array} options.populates - Populates config - * @return {Promise} - * @example Basic - * import React, { PureComponent } from 'react' - * import PropTypes from 'prop-types' - * import { connect } from 'react-redux' - * import { toJS, isLoaded, isEmpty } from 'react-redux-firebase' - * - * class SomeThing extends PureComponent { - * static contextTypes = { - * store: PropTypes.object.isRequired - * } - * - * componentWillMount() { - * this.context.store.firebase.helpers.watchEvent('value', 'todos') - * } - * - * componentWillUnmount() { - * this.context.store.firebase.helpers.unWatchEvent('value', 'todos') - * } - * - * render() { - * const { todos } = this.props - * const todoData = toJS(todos) // convert from immutable map to JS object - * return ( - *
- *

Todos

- * - * { - * !isLoaded(todosData) - * ?
Loading
- * : isEmpty(todosData) - * ?
No Todos
- * : JSON.stringify(toJS(todos), null, 2) - * } - *
- *
- * ) - * } - * } - * - * export default connect(({ firebase }) => - * todos: firebase.getIn(['data', 'todos']) // pass immutable map as prop - * )(SomeThing) - */ - const watchEvent = (type, path, options) => - queryActions.watchEvent( - instance, - dispatch, - isObject(options) - ? { type, path, ...options } - : { type, path, storeAs: options } - ) - - /** - * @private - * @description Unset a listener watch event. **Note:** this method is used - * internally so examples have not yet been created, and it may not work - * as expected. - * @param {String} type - Type of watch event - * @param {String} path - Database path on which to setup watch event - * @param {Object|String} options - Name of listener results within redux - * store. If string is passed, it is used as storeAs. - * @param {String} options.storeAs - Where results are place within redux store - * @param {String} options.queryId - Id of query - * @return {Promise} - */ - const unWatchEvent = (type, path, options) => - queryActions.unWatchEvent( - instance, - dispatch, - isObject(options) - ? { type, path, ...options } - : { type, path, storeAs: options } - ) - - /** - * @private - * @description Logs user into Firebase. For examples, visit the [auth section](/docs/auth.md) - * @param {Object} credentials - Credentials for authenticating - * @param {String} credentials.provider - External provider (google | facebook | twitter) - * @param {String} credentials.type - Type of external authentication (popup | redirect) (only used with provider) - * @param {String} credentials.email - Credentials for authenticating - * @param {String} credentials.password - Credentials for authenticating (only used with email) - * @return {Promise} Containing user's auth data - */ - const login = credentials => - authActions.login(dispatch, instance, credentials) - - /** - * @private - * @description Logs user out of Firebase and empties firebase state from - * redux store - * @return {Promise} - */ - const logout = () => - authActions.logout(dispatch, instance) - - /** - * @private - * @description Creates a new user in Firebase authentication. If - * `userProfile` config option is set, user profiles will be set to this - * location. - * @param {Object} credentials - Credentials for authenticating - * @param {String} credentials.email - Credentials for authenticating - * @param {String} credentials.password - Credentials for authenticating (only used with email) - * @param {Object} profile - Data to include within new user profile - * @return {Promise} Containing user's auth data - */ - const createUser = (credentials, profile) => - authActions.createUser(dispatch, instance, credentials, profile) - - /** - * @private - * @description Sends password reset email - * @param {Object} credentials - Credentials for authenticating - * @param {String} credentials.email - Credentials for authenticating - * @return {Promise} - */ - const resetPassword = (credentials) => - authActions.resetPassword(dispatch, instance, credentials) - - /** - * @private - * @description Confirm that a user's password has been reset - * @param {String} code - Password reset code to verify - * @param {String} password - New Password to confirm reset to - * @return {Promise} - */ - const confirmPasswordReset = (code, password) => - authActions.confirmPasswordReset(dispatch, instance, code, password) - - /** - * @private - * @description Verify that a password reset code from a password reset - * email is valid - * @param {String} code - Password reset code to verify - * @return {Promise} Containing user auth info - */ - const verifyPasswordResetCode = (code) => - authActions.verifyPasswordResetCode(dispatch, instance, code) - - /** - * @private - * @description Update the currently logged in user's profile object - * @param {String} profileUpdate - Changes to apply to profile - * @return {Promise} - */ - const updateProfile = (profile) => - authActions.updateProfile(dispatch, instance, profile) - - /** - * @private - * @description Update the currently logged in user's auth object. **Note**: - * changes Auth object **only**, not user's profile. - * @param {String} code - Password reset code to verify - * @return {Promise} - */ - const updateAuth = (authUpdate) => - authActions.updateAuth(dispatch, instance, authUpdate) - - /** - * @private - * @description Update the currently logged in user's email. **Note**: - * changes email in Auth object only, not within user's profile. - * @param {String} newEmail - New email - * @param {Boolean} updateInProfile - Whether or not to update user's - * profile with email change. - * @return {Promise} - */ - const updateEmail = (email, updateInProfile) => - authActions.updateEmail(dispatch, instance, email, updateInProfile) - - /** - * @name ref - * @description Firebase ref function - * @return {firebase.database.Reference} - * @private - */ - /** - * @name database - * @description Firebase database service instance including all Firebase storage methods - * @return {firebase.database} Firebase database service - * @private - */ - /** - * @name storage - * @description Firebase storage service instance including all Firebase storage methods - * @return {firebase.storage} Firebase storage service - * @private - */ - /** - * @name messaging - * @description Firebase messaging service instance including all Firebase messaging methods - * @return {firebase.messaging} Firebase messaging service - * @private - */ - /** - * @name auth - * @description Firebase auth service instance including all Firebase auth methods - * @return {firebase.auth} - * @private - */ - /** - * @name database - * @description Firebase database service instance including all Firebase storage methods - * @return {firebase.database} Firebase database service - * @private - */ - /** - * @name storage - * @description Firebase storage service instance including all Firebase storage methods - * @return {firebase.storage} Firebase storage service - * @private - */ - /** - * @name messaging - * @description Firebase messaging service instance including all Firebase messaging methods - * @return {firebase.messaging} Firebase messaging service - * @private - */ - firebase.helpers = { - ref: path => firebase.database().ref(path), - set, - setWithMeta, - uniqueSet, - push, - pushWithMeta, - remove, - update, - updateWithMeta, - login, - logout, - uploadFile, - uploadFiles, - deleteFile, - createUser, - resetPassword, - confirmPasswordReset, - verifyPasswordResetCode, - watchEvent, - unWatchEvent, - updateProfile, - updateAuth, - updateEmail, - storage: (app) => firebase.storage(app), - messaging: (app) => firebase.messaging(app) - } + /** + * @private + * @description Sets data to Firebase. + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component } from 'react' + * import PropTypes from 'prop-types' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { set } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const set = (path, value, onComplete) => + rootRef.child(path).set(value, onComplete) + + /** + * @private + * @description Sets data to Firebase along with meta data. Currently, + * this includes createdAt and createdBy. *Warning* using this function + * may have unintented consequences (setting createdAt even if data already + * exists) + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + */ + const setWithMeta = (path, value, onComplete) => + withMeta('set', path, value, onComplete) + + /** + * @private + * @description Pushes data to Firebase. + * @param {String} path - Path to location on Firebase which to push + * @param {Object|String|Boolean|Number} value - Value to push to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component } from 'react' + * import PropTypes from 'prop-types' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { push } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const push = (path, value, onComplete) => + rootRef.child(path).push(value, onComplete) + + /** + * @private + * @description Pushes data to Firebase along with meta data. Currently, + * this includes createdAt and createdBy. + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + */ + const pushWithMeta = (path, value, onComplete) => + withMeta('push', path, value, onComplete) + + /** + * @private + * @description Updates data on Firebase and sends new data. + * @param {String} path - Path to location on Firebase which to update + * @param {Object|String|Boolean|Number} value - Value to update to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component } from 'react' + * import PropTypes from 'prop-types' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { update } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const update = (path, value, onComplete) => + rootRef.child(path).update(value, onComplete) + + /** + * @private + * @description Updates data on Firebase along with meta. *Warning* + * using this function may have unintented consequences (setting + * createdAt even if data already exists) + * @param {String} path - Path to location on Firebase which to update + * @param {Object|String|Boolean|Number} value - Value to update to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + */ + const updateWithMeta = (path, value, onComplete) => + withMeta('update', path, value, onComplete) + + /** + * @private + * @description Removes data from Firebase at a given path. + * @param {String} path - Path to location on Firebase which to remove + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component } from 'react' + * import PropTypes from 'prop-types' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { remove } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const remove = (path, onComplete) => rootRef.child(path).remove(onComplete) + + /** + * @private + * @description Sets data to Firebase only if the path does not already + * exist, otherwise it rejects. Internally uses a Firebase transaction to + * prevent a race condition between seperate clients calling uniqueSet. + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component } from 'react' + * import PropTypes from 'prop-types' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { uniqueSet } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const uniqueSet = (path, value, onComplete) => + rootRef + .child(path) + .transaction(d => (d === null ? value : undefined)) + .then(({ committed, snapshot }) => { + if (!committed) { + const newError = new Error('Path already exists.') + if (onComplete) onComplete(newError) + return Promise.reject(newError) + } + if (onComplete) onComplete(snapshot) + return snapshot + }) - authActions.init(dispatch, instance) + /** + * @private + * @description Upload a file to Firebase Storage with the option to store + * its metadata in Firebase Database + * @param {String} path - Path to location on Firebase which to set + * @param {File} file - File object to upload (usually first element from + * array output of select-file or a drag/drop `onDrop`) + * @param {String} dbPath - Database path to place uploaded file metadata + * @return {Promise} Containing the File object + */ + const uploadFile = (path, file, dbPath) => + storageActions.uploadFile(dispatch, instance, { path, file, dbPath }) + + /** + * @private + * @description Upload multiple files to Firebase Storage with the option + * to store their metadata in Firebase Database + * @param {String} path - Path to location on Firebase which to set + * @param {Array} files - Array of File objects to upload (usually from + * a select-file or a drag/drop `onDrop`) + * @param {String} dbPath - Database path to place uploaded files metadata. + * @return {Promise} Containing an array of File objects + */ + const uploadFiles = (path, files, dbPath) => + storageActions.uploadFiles(dispatch, instance, { path, files, dbPath }) + + /** + * @private + * @description Delete a file from Firebase Storage with the option to + * remove its metadata in Firebase Database + * @param {String} path - Path to location on Firebase which to set + * @param {String} dbPath - Database path to place uploaded file metadata + * @return {Promise} Containing the File object + */ + const deleteFile = (path, dbPath) => + storageActions.deleteFile(dispatch, instance, { path, dbPath }) + + /** + * @private + * @description Create watch event for Firebase Realtime Database + * @param {String} type - Type of watch event ('value', 'once', etc) + * @param {String} path - Database path on which to setup watch event + * @param {Object|String} options - Name of listener results within redux + * store. If string is passed, it is used as storeAs. + * @param {String} options.storeAs - Name of listener results within redux store + * @param {Array} options.queryParams - List of query parameters + * @param {Array} options.populates - Populates config + * @return {Promise} + * @example Basic + * import React, { PureComponent } from 'react' + * import PropTypes from 'prop-types' + * import { connect } from 'react-redux' + * import { toJS, isLoaded, isEmpty } from 'react-redux-firebase' + * + * class SomeThing extends PureComponent { + * static contextTypes = { + * store: PropTypes.object.isRequired + * } + * + * componentWillMount() { + * this.context.store.firebase.helpers.watchEvent('value', 'todos') + * } + * + * componentWillUnmount() { + * this.context.store.firebase.helpers.unWatchEvent('value', 'todos') + * } + * + * render() { + * const { todos } = this.props + * const todoData = toJS(todos) // convert from immutable map to JS object + * return ( + *
+ *

Todos

+ * + * { + * !isLoaded(todosData) + * ?
Loading
+ * : isEmpty(todosData) + * ?
No Todos
+ * : JSON.stringify(toJS(todos), null, 2) + * } + *
+ *
+ * ) + * } + * } + * + * export default connect(({ firebase }) => + * todos: firebase.getIn(['data', 'todos']) // pass immutable map as prop + * )(SomeThing) + */ + const watchEvent = (type, path, options) => + queryActions.watchEvent( + instance, + dispatch, + isObject(options) + ? { type, path, ...options } + : { type, path, storeAs: options } + ) + + /** + * @private + * @description Unset a listener watch event. **Note:** this method is used + * internally so examples have not yet been created, and it may not work + * as expected. + * @param {String} type - Type of watch event + * @param {String} path - Database path on which to setup watch event + * @param {Object|String} options - Name of listener results within redux + * store. If string is passed, it is used as storeAs. + * @param {String} options.storeAs - Where results are place within redux store + * @param {String} options.queryId - Id of query + * @return {Promise} + */ + const unWatchEvent = (type, path, options) => + queryActions.unWatchEvent( + instance, + dispatch, + isObject(options) + ? { type, path, ...options } + : { type, path, storeAs: options } + ) + + /** + * @private + * @description Logs user into Firebase. For examples, visit the [auth section](/docs/auth.md) + * @param {Object} credentials - Credentials for authenticating + * @param {String} credentials.provider - External provider (google | facebook | twitter) + * @param {String} credentials.type - Type of external authentication (popup | redirect) (only used with provider) + * @param {String} credentials.email - Credentials for authenticating + * @param {String} credentials.password - Credentials for authenticating (only used with email) + * @return {Promise} Containing user's auth data + */ + const login = credentials => + authActions.login(dispatch, instance, credentials) + + /** + * @private + * @description Logs user out of Firebase and empties firebase state from + * redux store + * @return {Promise} + */ + const logout = () => authActions.logout(dispatch, instance) + + /** + * @private + * @description Creates a new user in Firebase authentication. If + * `userProfile` config option is set, user profiles will be set to this + * location. + * @param {Object} credentials - Credentials for authenticating + * @param {String} credentials.email - Credentials for authenticating + * @param {String} credentials.password - Credentials for authenticating (only used with email) + * @param {Object} profile - Data to include within new user profile + * @return {Promise} Containing user's auth data + */ + const createUser = (credentials, profile) => + authActions.createUser(dispatch, instance, credentials, profile) + + /** + * @private + * @description Sends password reset email + * @param {Object} credentials - Credentials for authenticating + * @param {String} credentials.email - Credentials for authenticating + * @return {Promise} + */ + const resetPassword = credentials => + authActions.resetPassword(dispatch, instance, credentials) + + /** + * @private + * @description Confirm that a user's password has been reset + * @param {String} code - Password reset code to verify + * @param {String} password - New Password to confirm reset to + * @return {Promise} + */ + const confirmPasswordReset = (code, password) => + authActions.confirmPasswordReset(dispatch, instance, code, password) + + /** + * @private + * @description Verify that a password reset code from a password reset + * email is valid + * @param {String} code - Password reset code to verify + * @return {Promise} Containing user auth info + */ + const verifyPasswordResetCode = code => + authActions.verifyPasswordResetCode(dispatch, instance, code) + + /** + * @private + * @description Update the currently logged in user's profile object + * @param {String} profileUpdate - Changes to apply to profile + * @return {Promise} + */ + const updateProfile = profile => + authActions.updateProfile(dispatch, instance, profile) + + /** + * @private + * @description Update the currently logged in user's auth object. **Note**: + * changes Auth object **only**, not user's profile. + * @param {String} code - Password reset code to verify + * @return {Promise} + */ + const updateAuth = authUpdate => + authActions.updateAuth(dispatch, instance, authUpdate) + + /** + * @private + * @description Update the currently logged in user's email. **Note**: + * changes email in Auth object only, not within user's profile. + * @param {String} newEmail - New email + * @param {Boolean} updateInProfile - Whether or not to update user's + * profile with email change. + * @return {Promise} + */ + const updateEmail = (email, updateInProfile) => + authActions.updateEmail(dispatch, instance, email, updateInProfile) + + /** + * @name ref + * @description Firebase ref function + * @return {firebase.database.Reference} + * @private + */ + /** + * @name database + * @description Firebase database service instance including all Firebase storage methods + * @return {firebase.database} Firebase database service + * @private + */ + /** + * @name storage + * @description Firebase storage service instance including all Firebase storage methods + * @return {firebase.storage} Firebase storage service + * @private + */ + /** + * @name messaging + * @description Firebase messaging service instance including all Firebase messaging methods + * @return {firebase.messaging} Firebase messaging service + * @private + */ + /** + * @name auth + * @description Firebase auth service instance including all Firebase auth methods + * @return {firebase.auth} + * @private + */ + /** + * @name database + * @description Firebase database service instance including all Firebase storage methods + * @return {firebase.database} Firebase database service + * @private + */ + /** + * @name storage + * @description Firebase storage service instance including all Firebase storage methods + * @return {firebase.storage} Firebase storage service + * @private + */ + /** + * @name messaging + * @description Firebase messaging service instance including all Firebase messaging methods + * @return {firebase.messaging} Firebase messaging service + * @private + */ + firebase.helpers = { + ref: path => firebase.database().ref(path), + set, + setWithMeta, + uniqueSet, + push, + pushWithMeta, + remove, + update, + updateWithMeta, + login, + logout, + uploadFile, + uploadFiles, + deleteFile, + createUser, + resetPassword, + confirmPasswordReset, + verifyPasswordResetCode, + watchEvent, + unWatchEvent, + updateProfile, + updateAuth, + updateEmail, + storage: app => firebase.storage(app), + messaging: app => firebase.messaging(app) + } - store.firebase = instance - firebaseInstance = Object.assign({}, instance, instance.helpers) + authActions.init(dispatch, instance) - return store - } + store.firebase = instance + firebaseInstance = Object.assign({}, instance, instance.helpers) + + return store +} /** * @private @@ -655,7 +657,9 @@ export const getFirebase = () => { // TODO: Handle recieveing config and creating firebase instance if it doesn't exist /* istanbul ignore next: Firebase instance always exists during tests */ if (!firebaseInstance) { - throw new Error('Firebase instance does not yet exist. Check your compose function.') // eslint-disable-line no-console + throw new Error( + 'Firebase instance does not yet exist. Check your compose function.' + ) // eslint-disable-line no-console } // TODO: Create new firebase here with config passed in return firebaseInstance diff --git a/src/createFirebaseInstance.js b/src/createFirebaseInstance.js index 5834c1f1c..de12fe3c5 100644 --- a/src/createFirebaseInstance.js +++ b/src/createFirebaseInstance.js @@ -1,11 +1,7 @@ import { isObject } from 'lodash' import { getEventsFromInput, createCallable } from './utils' import { mapWithFirebaseAndDispatch } from './utils/actions' -import { - authActions, - queryActions, - storageActions -} from './actions' +import { authActions, queryActions, storageActions } from './actions' /** * Create a firebase instance that has helpers attached for dispatching actions @@ -62,9 +58,15 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { if (firebase.auth().currentUser) { dataWithMeta[`${prefix}By`] = firebase.auth().currentUser.uid } - return firebase.database().ref(path)[method](dataWithMeta, onComplete) + return firebase + .database() + .ref(path) + [method](dataWithMeta, onComplete) } - return firebase.database().ref(path)[method](value, onComplete) + return firebase + .database() + .ref(path) + [method](value, onComplete) } /** @@ -85,7 +87,10 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * export default firebaseConnect()(Example) */ const set = (path, value, onComplete) => - firebase.database().ref(path).set(value, onComplete) + firebase + .database() + .ref(path) + .set(value, onComplete) /** * @description Sets data to Firebase along with meta data. Currently, @@ -118,7 +123,10 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * export default firebaseConnect()(Example) */ const push = (path, value, onComplete) => - firebase.database().ref(path).push(value, onComplete) + firebase + .database() + .ref(path) + .push(value, onComplete) /** * @description Pushes data to Firebase along with meta data. Currently, @@ -149,7 +157,10 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * export default firebaseConnect()(Example) */ const update = (path, value, onComplete) => - firebase.database().ref(path).update(value, onComplete) + firebase + .database() + .ref(path) + .update(value, onComplete) /** * @description Updates data on Firebase along with meta. *Warning* @@ -183,13 +194,12 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * export default firebaseConnect()(Example) */ const remove = (path, onComplete, options) => - queryActions.remove(firebase, dispatch, path, options) - .then(() => { - if (typeof onComplete === 'function') { - onComplete() - } - return path - }) + queryActions.remove(firebase, dispatch, path, options).then(() => { + if (typeof onComplete === 'function') { + onComplete() + } + return path + }) /** * @description Sets data to Firebase only if the path does not already @@ -211,9 +221,10 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * export default firebaseConnect()(Example) */ const uniqueSet = (path, value, onComplete) => - firebase.database() + firebase + .database() .ref(path) - .transaction(d => d === null ? value : undefined) + .transaction(d => (d === null ? value : undefined)) .then(({ committed, snapshot }) => { if (!committed) { const newError = new Error('Path already exists.') @@ -236,7 +247,12 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * @return {Promise} Containing the File object */ const uploadFile = (path, file, dbPath, options) => - storageActions.uploadFile(dispatch, firebase, { path, file, dbPath, options }) + storageActions.uploadFile(dispatch, firebase, { + path, + file, + dbPath, + options + }) /** * @description Upload multiple files to Firebase Storage with the option @@ -250,7 +266,12 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * @return {Promise} Containing an array of File objects */ const uploadFiles = (path, files, dbPath, options) => - storageActions.uploadFiles(dispatch, firebase, { path, files, dbPath, options }) + storageActions.uploadFiles(dispatch, firebase, { + path, + files, + dbPath, + options + }) /** * @description Delete a file from Firebase Storage with the option to @@ -274,7 +295,12 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * @return {Promise} */ const watchEvent = (type, path, storeAs, options = {}) => - queryActions.watchEvent(firebase, dispatch, { type, path, storeAs, ...options }) + queryActions.watchEvent(firebase, dispatch, { + type, + path, + storeAs, + ...options + }) /** * @description Unset a listener watch event. **Note:** this method is used @@ -287,7 +313,12 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * @return {Promise} */ const unWatchEvent = (type, path, queryId, options = {}) => - queryActions.unWatchEvent(firebase, dispatch, { type, path, queryId, ...options }) + queryActions.unWatchEvent(firebase, dispatch, { + type, + path, + queryId, + ...options + }) /** * @description Similar to the firebaseConnect Higher Order Component but @@ -334,8 +365,7 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * redux store * @return {Promise} */ - const logout = () => - authActions.logout(dispatch, firebase) + const logout = () => authActions.logout(dispatch, firebase) /** * @description Creates a new user in Firebase authentication. If @@ -356,7 +386,7 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * @param {String} credentials.email - Credentials for authenticating * @return {Promise} */ - const resetPassword = (credentials) => + const resetPassword = credentials => authActions.resetPassword(dispatch, firebase, credentials) /** @@ -374,7 +404,7 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * @param {String} code - Password reset code to verify * @return {Promise} Containing user auth info */ - const verifyPasswordResetCode = (code) => + const verifyPasswordResetCode = code => authActions.verifyPasswordResetCode(dispatch, firebase, code) /** @@ -382,7 +412,7 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * @param {Object} profile - Profile data to place in new profile * @return {Promise} */ - const updateProfile = (profileUpdate) => + const updateProfile = profileUpdate => authActions.updateProfile(dispatch, firebase, profileUpdate) /** @@ -414,7 +444,7 @@ export const createFirebaseInstance = (firebase, configs, dispatch) => { * @param {firebase.auth.AuthCredential} credential - The auth credential * @return {Promise} */ - const linkWithCredential = (credential) => + const linkWithCredential = credential => authActions.linkWithCredential(dispatch, firebase, credential) /** diff --git a/src/enhancer.js b/src/enhancer.js index abf7f5ca7..a8426ae64 100644 --- a/src/enhancer.js +++ b/src/enhancer.js @@ -83,28 +83,37 @@ let firebaseInstance * // Use Function later to create store * const store = createStoreWithFirebase(rootReducer, initialState) */ -export default (instance, otherConfig) => next => - (reducer, initialState, middleware) => { - const store = next(reducer, initialState, middleware) +export default (instance, otherConfig) => next => ( + reducer, + initialState, + middleware +) => { + const store = next(reducer, initialState, middleware) - // firebase library or app instance not being passed in as first argument - if (!instance.SDK_VERSION && !instance.firebase_ && !instance.database) { - throw new Error('v2.0.0-beta and higher require passing a firebase app instance or a firebase library instance. View the migration guide for details.') - } - - const configs = { ...defaultConfig, ...otherConfig } - firebaseInstance = createFirebaseInstance(instance.firebase_ || instance, configs, store.dispatch) + // firebase library or app instance not being passed in as first argument + if (!instance.SDK_VERSION && !instance.firebase_ && !instance.database) { + throw new Error( + 'v2.0.0-beta and higher require passing a firebase app instance or a firebase library instance. View the migration guide for details.' + ) + } - authActions.init(store.dispatch, firebaseInstance) - store.firebase = firebaseInstance + const configs = { ...defaultConfig, ...otherConfig } + firebaseInstance = createFirebaseInstance( + instance.firebase_ || instance, + configs, + store.dispatch + ) - if (configs.attachAuthIsReady) { - store.firebaseAuthIsReady = createAuthIsReady(store, configs) - } + authActions.init(store.dispatch, firebaseInstance) + store.firebase = firebaseInstance - return store + if (configs.attachAuthIsReady) { + store.firebaseAuthIsReady = createAuthIsReady(store, configs) } + return store +} + /** * @private * @description Expose Firebase instance created internally. Useful for @@ -144,7 +153,9 @@ export const getFirebase = () => { // TODO: Handle recieveing config and creating firebase instance if it doesn't exist /* istanbul ignore next: Firebase instance always exists during tests */ if (!firebaseInstance) { - throw new Error('Firebase instance does not yet exist. Check your compose function.') // eslint-disable-line no-console + throw new Error( + 'Firebase instance does not yet exist. Check your compose function.' + ) // eslint-disable-line no-console } // TODO: Create new firebase here with config passed in return firebaseInstance diff --git a/src/firebaseConnect.js b/src/firebaseConnect.js index 6b14fa06a..310c19574 100644 --- a/src/firebaseConnect.js +++ b/src/firebaseConnect.js @@ -23,71 +23,71 @@ import { getEventsFromInput, createCallable, getDisplayName } from './utils' * // use the firebaseConnect to wrap a component * export default firebaseConnect()(SomeComponent) */ -export const createFirebaseConnect = (storeKey = 'store') => - (dataOrFn = []) => - WrappedComponent => { - class FirebaseConnect extends Component { - static displayName = `FirebaseConnect(${getDisplayName(WrappedComponent)})` - static wrappedComponent = WrappedComponent - static contextTypes = { - [storeKey]: PropTypes.object.isRequired - } - - firebaseEvents = [] - firebase = null - prevData = null - store = this.context[storeKey] +export const createFirebaseConnect = (storeKey = 'store') => ( + dataOrFn = [] +) => WrappedComponent => { + class FirebaseConnect extends Component { + static displayName = `FirebaseConnect(${getDisplayName(WrappedComponent)})` + static wrappedComponent = WrappedComponent + static contextTypes = { + [storeKey]: PropTypes.object.isRequired + } - componentWillMount () { - const { firebase, dispatch } = this.store + firebaseEvents = [] + firebase = null + prevData = null + store = this.context[storeKey] - // Allow function to be passed - const inputAsFunc = createCallable(dataOrFn) - this.prevData = inputAsFunc(this.props, this.store) + componentWillMount() { + const { firebase, dispatch } = this.store - const { ref, helpers, storage, database, auth } = firebase - this.firebase = { ref, storage, database, auth, ...helpers } + // Allow function to be passed + const inputAsFunc = createCallable(dataOrFn) + this.prevData = inputAsFunc(this.props, this.store) - this._firebaseEvents = getEventsFromInput(this.prevData) + const { ref, helpers, storage, database, auth } = firebase + this.firebase = { ref, storage, database, auth, ...helpers } - watchEvents(firebase, dispatch, this._firebaseEvents) - } + this._firebaseEvents = getEventsFromInput(this.prevData) - componentWillUnmount () { - const { firebase, dispatch } = this.store - unWatchEvents(firebase, dispatch, this._firebaseEvents) - } + watchEvents(firebase, dispatch, this._firebaseEvents) + } - componentWillReceiveProps (np) { - const { firebase, dispatch } = this.store - const inputAsFunc = createCallable(dataOrFn) - const data = inputAsFunc(np, this.store) + componentWillUnmount() { + const { firebase, dispatch } = this.store + unWatchEvents(firebase, dispatch, this._firebaseEvents) + } - // Handle a data parameter having changed - if (!isEqual(data, this.prevData)) { - this.prevData = data - // UnWatch all current events - unWatchEvents(firebase, dispatch, this._firebaseEvents) - // Get watch events from new data - this._firebaseEvents = getEventsFromInput(data) - // Watch new events - watchEvents(firebase, dispatch, this._firebaseEvents) - } - } + componentWillReceiveProps(np) { + const { firebase, dispatch } = this.store + const inputAsFunc = createCallable(dataOrFn) + const data = inputAsFunc(np, this.store) - render () { - return ( - - ) - } + // Handle a data parameter having changed + if (!isEqual(data, this.prevData)) { + this.prevData = data + // UnWatch all current events + unWatchEvents(firebase, dispatch, this._firebaseEvents) + // Get watch events from new data + this._firebaseEvents = getEventsFromInput(data) + // Watch new events + watchEvents(firebase, dispatch, this._firebaseEvents) } + } - return hoistStatics(FirebaseConnect, WrappedComponent) + render() { + return ( + + ) } + } + + return hoistStatics(FirebaseConnect, WrappedComponent) +} /** /** diff --git a/src/firestoreConnect.js b/src/firestoreConnect.js index 83158a782..b7fbd6158 100644 --- a/src/firestoreConnect.js +++ b/src/firestoreConnect.js @@ -35,7 +35,7 @@ export const createFirestoreConnect = (storeKey = 'store') => ( prevData = null store = this.context[storeKey] - componentWillMount () { + componentWillMount() { const { firebase, firestore } = this.store if (firebase.firestore && firestore) { // Allow function to be passed @@ -46,7 +46,7 @@ export const createFirestoreConnect = (storeKey = 'store') => ( } } - componentWillUnmount () { + componentWillUnmount() { const { firebase, firestore } = this.store if (firebase.firestore && this.prevData) { firestore.unsetListeners(this.prevData) @@ -54,7 +54,7 @@ export const createFirestoreConnect = (storeKey = 'store') => ( } // TODO: Re-attach listeners on query path change - componentWillReceiveProps (np) { + componentWillReceiveProps(np) { const { firebase, firestore } = this.store const inputAsFunc = createCallable(dataOrFn) const data = inputAsFunc(np, this.store) @@ -69,7 +69,7 @@ export const createFirestoreConnect = (storeKey = 'store') => ( } } - render () { + render() { const { firebase, firestore } = this.store return ( * @return {String} - Fixed path * @private */ -export const fixPath = path => - ((path.substring(0, 1) === '/') ? '' : '/') + path +export const fixPath = path => (path.substring(0, 1) === '/' ? '' : '/') + path /** * @private @@ -243,10 +242,11 @@ const populateChild = (state, child, p) => { const populateVal = get(state.data, pathString) if (populateVal) { - return set({}, p.childAlias || p.child, (p.keyProp - ? { [p.keyProp]: childVal, ...populateVal } - : populateVal - )) + return set( + {}, + p.childAlias || p.child, + p.keyProp ? { [p.keyProp]: childVal, ...populateVal } : populateVal + ) } // matching child does not exist return child @@ -285,9 +285,10 @@ const populateChild = (state, child, p) => { export const populate = (state, path, populates, notSetValue) => { const splitPath = compact(path.split('/')) // append 'data' prefix to path if it is not a top level path - const pathArr = topLevelPaths.indexOf(splitPath[0]) === -1 - ? ['data', ...splitPath] - : splitPath + const pathArr = + topLevelPaths.indexOf(splitPath[0]) === -1 + ? ['data', ...splitPath] + : splitPath const dotPath = pathArr.join('.') // Gather data from top level if path is profile (handles populating profile) const data = get(state, dotPath, notSetValue) @@ -303,9 +304,7 @@ export const populate = (state, path, populates, notSetValue) => { // check for if data is single object or a list of objects const populatesForData = getPopulateObjs( - isFunction(populates) - ? populates(last(pathArr), data) - : populates + isFunction(populates) ? populates(last(pathArr), data) : populates ) if (isArray(data)) { @@ -338,9 +337,7 @@ export const populate = (state, path, populates, notSetValue) => { } // check each populate child parameter for existence - const dataHasPopulateChilds = some(populatesForData, p => - has(data, p.child) - ) + const dataHasPopulateChilds = some(populatesForData, p => has(data, p.child)) // Single object that contains at least one child parameter if (dataHasPopulateChilds) { @@ -361,9 +358,7 @@ export const populate = (state, path, populates, notSetValue) => { const key = pathArr[0] === 'ordered' ? child.key : childKey // get populate settings on item level (passes child if populates is a function) const populatesForDataItem = getPopulateObjs( - isFunction(populates) - ? populates(key, child) - : populates + isFunction(populates) ? populates(key, child) : populates ) // confirm at least one populate value exists on child const dataHasPopulateChilds = some(populatesForDataItem, p => diff --git a/src/reducers.js b/src/reducers.js index bf5ef8cfa..68443ee27 100644 --- a/src/reducers.js +++ b/src/reducers.js @@ -137,7 +137,12 @@ export const timestampsReducer = (state = {}, { type, path }) => { const createDataReducer = (actionKey = 'data') => (state = {}, action) => { switch (action.type) { case SET: - return setWith(Object, getDotStrPath(action.path), action[actionKey], state) + return setWith( + Object, + getDotStrPath(action.path), + action[actionKey], + state + ) case MERGE: const previousData = get(state, getDotStrPath(action.path), {}) const mergedData = assign(previousData, action[actionKey]) @@ -159,7 +164,9 @@ const createDataReducer = (actionKey = 'data') => (state = {}, action) => { ? pick(state, action.preserve[actionKey]) : {} } - throw new Error('Invalid preserve parameter. It must be an Object or an Array') + throw new Error( + 'Invalid preserve parameter. It must be an Object or an Array' + ) } return {} default: @@ -174,7 +181,10 @@ const createDataReducer = (actionKey = 'data') => (state = {}, action) => { * @param {String} action.type - Type of action that was dispatched * @return {Object} Profile state after reduction */ -export const authReducer = (state = { isLoaded: false, isEmpty: true }, action) => { +export const authReducer = ( + state = { isLoaded: false, isEmpty: true }, + action +) => { switch (action.type) { case LOGIN: case AUTH_UPDATE_SUCCESS: @@ -245,7 +255,10 @@ export const authErrorReducer = (state = null, action) => { * @param {String} action.type - Type of action that was dispatched * @return {Object} Profile state after reduction */ -export const profileReducer = (state = { isLoaded: false, isEmpty: true }, action) => { +export const profileReducer = ( + state = { isLoaded: false, isEmpty: true }, + action +) => { switch (action.type) { case SET_PROFILE: if (!action.profile) { @@ -261,7 +274,7 @@ export const profileReducer = (state = { isLoaded: false, isEmpty: true }, actio isLoaded: true } case LOGIN: - // Support keeping data when logging out + // Support keeping data when logging out if (action.preserve && action.preserve.profile) { return pick(state, action.preserve.profile) // pick returns a new object } @@ -304,7 +317,9 @@ export const errorsReducer = (state = [], action) => { // Support keeping errors through a filter function if (action.preserve && action.preserve.errors) { if (typeof action.preserve.errors !== 'function') { - throw new Error('Preserve for the errors state currently only supports functions') + throw new Error( + 'Preserve for the errors state currently only supports functions' + ) } return state.filter(action.preserve.errors) // run filter function on state } @@ -333,8 +348,10 @@ const listenersById = (state = {}, { type, path, payload }) => { path } } - case UNSET_LISTENER: return omit(state, [payload.id]) - default: return state + case UNSET_LISTENER: + return omit(state, [payload.id]) + default: + return state } } @@ -349,9 +366,12 @@ const listenersById = (state = {}, { type, path, payload }) => { */ const allListeners = (state = [], { type, path, payload }) => { switch (type) { - case SET_LISTENER: return [...state, payload.id] - case UNSET_LISTENER: return state.filter(lId => lId !== payload.id) - default: return state + case SET_LISTENER: + return [...state, payload.id] + case UNSET_LISTENER: + return state.filter(lId => lId !== payload.id) + default: + return state } } diff --git a/src/utils/actions.js b/src/utils/actions.js index 1501ee029..ad0b0f34f 100644 --- a/src/utils/actions.js +++ b/src/utils/actions.js @@ -9,7 +9,10 @@ import { isObject, mapValues } from 'lodash' * @param {Array} opts.types - Action types array ([BEFORE, SUCCESS, FAILURE]) * @private */ -export const wrapInDispatch = (dispatch, { ref, meta, method, args = [], types }) => { +export const wrapInDispatch = ( + dispatch, + { ref, meta, method, args = [], types } +) => { const [requestingType, successType, errorType] = types dispatch({ type: isObject(requestingType) ? requestingType.type : requestingType, @@ -17,7 +20,7 @@ export const wrapInDispatch = (dispatch, { ref, meta, method, args = [], types } payload: isObject(requestingType) ? requestingType.payload : { args } }) return method(...args) - .then((payload) => { + .then(payload => { dispatch({ type: isObject(successType) ? successType.type : successType, meta, @@ -25,7 +28,7 @@ export const wrapInDispatch = (dispatch, { ref, meta, method, args = [], types } }) return payload }) - .catch((err) => { + .catch(err => { dispatch({ type: errorType, meta, @@ -43,8 +46,17 @@ export const wrapInDispatch = (dispatch, { ref, meta, method, args = [], types } * @return {Function} A wrapper that accepts a function to wrap with firebase * and dispatch. */ -const createWithFirebaseAndDispatch = (firebase, dispatch, dispatchFirst) => func => (...args) => - func.apply(firebase, dispatchFirst ? [dispatch, firebase, ...args] : [firebase, dispatch, ...args]) +const createWithFirebaseAndDispatch = ( + firebase, + dispatch, + dispatchFirst +) => func => (...args) => + func.apply( + firebase, + dispatchFirst + ? [dispatch, firebase, ...args] + : [firebase, dispatch, ...args] + ) /** * Map each action with Firebase and Dispatch. Includes aliasing of actions. @@ -53,13 +65,24 @@ const createWithFirebaseAndDispatch = (firebase, dispatch, dispatchFirst) => fun * @param {Object} actions - Action functions to map with firebase and dispatch * @return {Object} Actions mapped with firebase and dispatch */ -export const mapWithFirebaseAndDispatch = (firebase, dispatch, actions, aliases = []) => { - const withFirebaseAndDispatch = createWithFirebaseAndDispatch(firebase, dispatch) +export const mapWithFirebaseAndDispatch = ( + firebase, + dispatch, + actions, + aliases = [] +) => { + const withFirebaseAndDispatch = createWithFirebaseAndDispatch( + firebase, + dispatch + ) return { ...mapValues(actions, withFirebaseAndDispatch), - ...aliases.reduce((acc, { action, name }) => ({ - ...acc, - [name]: withFirebaseAndDispatch(action) - }), {}) + ...aliases.reduce( + (acc, { action, name }) => ({ + ...acc, + [name]: withFirebaseAndDispatch(action) + }), + {} + ) } } diff --git a/src/utils/auth.js b/src/utils/auth.js index c125836c2..a34f54b24 100644 --- a/src/utils/auth.js +++ b/src/utils/auth.js @@ -12,7 +12,8 @@ import { supportedAuthProviders } from '../constants' const createAuthProvider = (firebase, providerName, scopes) => { // TODO: Verify scopes are valid before adding // TODO: Validate parameter inputs - const provider = new firebase.auth[`${capitalize(providerName)}AuthProvider`]() + const capitalProviderName = `${capitalize(providerName)}AuthProvider` + const provider = new firebase.auth[capitalProviderName]() // Custom Auth Parameters const { customAuthParameters } = firebase._.config @@ -21,7 +22,10 @@ const createAuthProvider = (firebase, providerName, scopes) => { } // Handle providers without scopes - if (providerName.toLowerCase() === 'twitter' || !isFunction(provider.addScope)) { + if ( + providerName.toLowerCase() === 'twitter' || + !isFunction(provider.addScope) + ) { return provider } @@ -74,7 +78,7 @@ export const getLoginMethodAndParams = (firebase, creds) => { credential } = creds if (credential) { - return { method: 'signInWithCredential', params: [ credential ] } + return { method: 'signInWithCredential', params: [credential] } } if (provider) { // Verify providerName is valid @@ -82,27 +86,31 @@ export const getLoginMethodAndParams = (firebase, creds) => { throw new Error(`${provider} is not a valid Auth Provider`) } if (token) { - throw new Error('provider with token no longer supported, use credential parameter instead') + throw new Error( + 'provider with token no longer supported, use credential parameter instead' + ) } const authProvider = createAuthProvider(firebase, provider, scopes) if (type === 'popup') { - return { method: 'signInWithPopup', params: [ authProvider ] } + return { method: 'signInWithPopup', params: [authProvider] } } - return { method: 'signInWithRedirect', params: [ authProvider ] } + return { method: 'signInWithRedirect', params: [authProvider] } } if (token) { - return { method: 'signInWithCustomToken', params: [ token ] } + return { method: 'signInWithCustomToken', params: [token] } } if (phoneNumber) { if (!applicationVerifier) { - throw new Error('Application verifier is required for phone authentication') + throw new Error( + 'Application verifier is required for phone authentication' + ) } return { method: 'signInWithPhoneNumber', params: [phoneNumber, applicationVerifier] } } - return { method: 'signInWithEmailAndPassword', params: [ email, password ] } + return { method: 'signInWithEmailAndPassword', params: [email, password] } } /** @@ -121,7 +129,11 @@ const isAuthReady = (store, stateName) => { const firebaseState = stateName ? state[stateName] : state const firebaseAuthState = firebaseState && firebaseState.auth if (!firebaseAuthState) { - throw new Error(`The Firebase auth state could not be found in the store under the attribute '${stateName ? `${stateName}.` : ''}auth'. Make sure your react-redux-firebase reducer is correctly set in the store`) + throw new Error( + `The Firebase auth state could not be found in the store under the attribute '${ + stateName ? `${stateName}.` : '' + }auth'. Make sure your react-redux-firebase reducer is correctly set in the store` + ) } return firebaseState.auth.isLoaded } @@ -138,7 +150,7 @@ const isAuthReady = (store, stateName) => { * @return {Promise} Resolve when Firebase auth is ready in the store. */ export const authIsReady = (store, stateName = 'firebase') => - new Promise((resolve) => { + new Promise(resolve => { if (isAuthReady(store, stateName)) { resolve() } else { @@ -175,9 +187,7 @@ export const createAuthIsReady = (store, config) => { export const updateProfileOnRTDB = (firebase, profileUpdate) => { const { database, _: { config, authUid } } = firebase const profileRef = database().ref(`${config.userProfile}/${authUid}`) - return profileRef - .update(profileUpdate) - .then(() => profileRef.once('value')) + return profileRef.update(profileUpdate).then(() => profileRef.once('value')) } /** @@ -189,7 +199,5 @@ export const updateProfileOnRTDB = (firebase, profileUpdate) => { export const updateProfileOnFirestore = (firebase, profileUpdate) => { const { firestore, _: { config, authUid } } = firebase const profileRef = firestore().doc(`${config.userProfile}/${authUid}`) - return profileRef - .update(profileUpdate) - .then(() => profileRef.get()) + return profileRef.update(profileUpdate).then(() => profileRef.get()) } diff --git a/src/utils/events.js b/src/utils/events.js index 76c156271..ab73535a5 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -7,25 +7,24 @@ import { getQueryIdFromPath } from './query' * @param {String} path - Path that can contain query parameters and populates * @return {Object} watchEvents - Array of watch events */ -export const pathStrToObj = (path) => { +export const pathStrToObj = path => { let pathObj = { path, type: 'value', isQuery: false } const queryId = getQueryIdFromPath(path) // If Query id exists split params from path if (queryId) { const pathArray = path.split('#') - pathObj = Object.assign( - {}, - pathObj, - { - queryId, - isQuery: true, - path: pathArray[0], - queryParams: pathArray[1].split('&') - } - ) + pathObj = Object.assign({}, pathObj, { + queryId, + isQuery: true, + path: pathArray[0], + queryParams: pathArray[1].split('&') + }) if (getPopulates(pathArray[1].split('&'))) { pathObj.populates = getPopulates(pathArray[1].split('&')) - pathObj.queryParams = remove(pathArray[1].split('&'), (p) => p.indexOf('populate') === -1) + pathObj.queryParams = remove( + pathArray[1].split('&'), + p => p.indexOf('populate') === -1 + ) } } // if queryId does not exist, return original pathObj @@ -38,9 +37,9 @@ export const pathStrToObj = (path) => { * @return {Array} watchEvents - Array of watch events */ export const getEventsFromInput = paths => - flatMap(paths, (path) => { + flatMap(paths, path => { if (isString(path)) { - return [ pathStrToObj(path) ] + return [pathStrToObj(path)] } if (isArray(path)) { @@ -73,10 +72,12 @@ export const getEventsFromInput = paths => // Add all parameters that are missing (ones that exist will remain) path = Object.assign({}, pathStrToObj(strPath), path) - return [ path ] + return [path] } - throw new Error(`Invalid Path Definition: ${path}. Only strings, objects, and arrays accepted.`) + throw new Error( + `Invalid Path Definition: ${path}. Only strings, objects, and arrays accepted.` + ) }) export default { getEventsFromInput } diff --git a/src/utils/index.js b/src/utils/index.js index f24b13efd..7c4ca2045 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -6,7 +6,7 @@ export { getEventsFromInput } from './events' * @description Create a function if not already one * @param {Function|Object|Array|String} Callable function or value of return for new function */ -export const createCallable = f => isFunction(f) ? f : () => f +export const createCallable = f => (isFunction(f) ? f : () => f) export const getDisplayName = Component => { if (typeof Component === 'string') { diff --git a/src/utils/populate.js b/src/utils/populate.js index cc8a034ba..b973c2d0c 100644 --- a/src/utils/populate.js +++ b/src/utils/populate.js @@ -17,7 +17,7 @@ import { * @description Create standardized populate object from strings or objects * @param {String|Object} str - String or Object to standardize into populate object */ -export const getPopulateObj = (str) => { +export const getPopulateObj = str => { if (!isString(str)) { return str } @@ -30,7 +30,7 @@ export const getPopulateObj = (str) => { * @description Determine the structure of the child parameter to populate onto * @param {String|Object} child - Value at child parameter */ -export const getChildType = (child) => { +export const getChildType = child => { if (isString(child)) { return 'string' } @@ -48,11 +48,11 @@ export const getChildType = (child) => { * @description Create standardized populate object from strings or objects * @param {String|Object} str - String or Object to standardize into populate object */ -export const getPopulateObjs = (arr) => { +export const getPopulateObjs = arr => { if (!isArray(arr)) { return arr } - return arr.map((o) => isObject(o) ? o : getPopulateObj(o)) + return arr.map(o => (isObject(o) ? o : getPopulateObj(o))) } /** @@ -60,9 +60,11 @@ export const getPopulateObjs = (arr) => { * @description Get array of populates from list of query params * @param {Array} queryParams - Query parameters from which to get populates */ -export const getPopulates = (params) => { - const populates = filter(params, param => - param.indexOf('populate') !== -1 || (isObject(param) && param.populates) +export const getPopulates = params => { + const populates = filter( + params, + param => + param.indexOf('populate') !== -1 || (isObject(param) && param.populates) ).map(p => p.split('=')[1]) // No populates if (!populates.length) { @@ -80,7 +82,8 @@ export const getPopulates = (params) => { * @param {String} id - String id */ export const getPopulateChild = (firebase, populate, id) => - firebase.database() + firebase + .database() .ref() .child(`${populate.root}/${id}`) .once('value') @@ -106,14 +109,13 @@ export const populateList = (firebase, list, p, results) => { map(list, (id, childKey) => { // handle list of keys const populateKey = id === true ? childKey : id - return getPopulateChild(firebase, p, populateKey) - .then(pc => { - if (pc) { - // write child to result object under root name if it is found - return set(results, `${p.root}.${populateKey}`, pc) - } - return results - }) + return getPopulateChild(firebase, p, populateKey).then(pc => { + if (pc) { + // write child to result object under root name if it is found + return set(results, `${p.root}.${populateKey}`, pc) + } + return results + }) }) ) } @@ -125,34 +127,36 @@ export const populateList = (firebase, list, p, results) => { * @param {Object} originalObj - Object to have parameter populated * @param {Object} populateString - String containg population data */ -export const promisesForPopulate = (firebase, dataKey, originalData, populatesIn) => { +export const promisesForPopulate = ( + firebase, + dataKey, + originalData, + populatesIn +) => { // TODO: Handle selecting of parameter to populate with (i.e. displayName of users/user) let promisesArray = [] let results = {} // test if data is a single object, try generating populates and looking for the child const populatesForData = getPopulateObjs( - isFunction(populatesIn) - ? populatesIn(dataKey, originalData) - : populatesIn + isFunction(populatesIn) ? populatesIn(dataKey, originalData) : populatesIn ) - const dataHasPopulateChilds = some(populatesForData, (populate) => ( + const dataHasPopulateChilds = some(populatesForData, populate => has(originalData, populate.child) - )) + ) if (dataHasPopulateChilds) { // Data is a single object, resolve populates directly - forEach(populatesForData, (p) => { + forEach(populatesForData, p => { if (isString(get(originalData, p.child))) { return promisesArray.push( - getPopulateChild(firebase, p, get(originalData, p.child)) - .then((v) => { - // write child to result object under root name if it is found - if (v) { - set(results, `${p.root}.${get(originalData, p.child)}`, v) - } - }) + getPopulateChild(firebase, p, get(originalData, p.child)).then(v => { + // write child to result object under root name if it is found + if (v) { + set(results, `${p.root}.${get(originalData, p.child)}`, v) + } + }) ) } @@ -166,12 +170,12 @@ export const promisesForPopulate = (firebase, dataKey, originalData, populatesIn // { '1': {someobject}, '2': {someobject} } forEach(originalData, (d, key) => { // generate populates for this data item if a fn was passed - const populatesForDataItem = getPopulateObj(isFunction(populatesIn) - ? populatesIn(key, d) - : populatesIn) + const populatesForDataItem = getPopulateObj( + isFunction(populatesIn) ? populatesIn(key, d) : populatesIn + ) // resolve each populate for this data item - forEach(populatesForDataItem, (p) => { + forEach(populatesForDataItem, p => { // get value of parameter to be populated (key or list of keys) const idOrList = get(d, p.child) @@ -183,14 +187,13 @@ export const promisesForPopulate = (firebase, dataKey, originalData, populatesIn // Parameter of each list item is single ID if (isString(idOrList)) { return promisesArray.push( - getPopulateChild(firebase, p, idOrList) - .then((v) => { - // write child to result object under root name if it is found - if (v) { - set(results, `${p.root}.${idOrList}`, v) - } - return results - }) + getPopulateChild(firebase, p, idOrList).then(v => { + // write child to result object under root name if it is found + if (v) { + set(results, `${p.root}.${idOrList}`, v) + } + return results + }) ) } diff --git a/src/utils/query.js b/src/utils/query.js index 8f15f578d..46df8e7da 100644 --- a/src/utils/query.js +++ b/src/utils/query.js @@ -9,7 +9,7 @@ import { isNaN, forEach, size, isString } from 'lodash' * @return {Number|Any} Number if parse to number was successful, otherwise, * original value */ -const tryParseToNumber = (value) => { +const tryParseToNumber = value => { const result = Number(value) if (isNaN(result)) { return value @@ -28,7 +28,7 @@ export const getWatchPath = (event, path) => { if (!event || event === '' || !path) { throw new Error('Event and path are required') } - return `${event}:${((path.substring(0, 1) === '/') ? '' : '/')}${path}` + return `${event}:${path.substring(0, 1) === '/' ? '' : '/'}${path}` } /** @@ -48,16 +48,20 @@ export const getQueryIdFromPath = (path, event) => { const isQuery = pathSplitted.length > 1 const queryParams = isQuery ? pathSplitted[1].split('&') : [] - const queryId = isQuery ? queryParams.map((param) => { - let splittedParam = param.split('=') - // Handle query id in path - if (splittedParam[0] === 'queryId') { - return splittedParam[1] - } - }).filter(q => q) : undefined + const queryId = isQuery + ? queryParams + .map(param => { + let splittedParam = param.split('=') + // Handle query id in path + if (splittedParam[0] === 'queryId') { + return splittedParam[1] + } + }) + .filter(q => q) + : undefined return queryId && queryId.length > 0 - ? (event ? `${event}:/${queryId}` : queryId[0]) - : (isQuery ? origPath : undefined) + ? event ? `${event}:/${queryId}` : queryId[0] + : isQuery ? origPath : undefined } /** @@ -70,7 +74,8 @@ export const getQueryIdFromPath = (path, event) => { * @return {Integer} watcherCount - count */ export const setWatcher = (firebase, dispatch, event, path, queryId) => { - const id = queryId || getQueryIdFromPath(path, event) || getWatchPath(event, path) + const id = + queryId || getQueryIdFromPath(path, event) || getWatchPath(event, path) if (firebase._.watchers[id]) { firebase._.watchers[id]++ @@ -93,7 +98,8 @@ export const setWatcher = (firebase, dispatch, event, path, queryId) => { * @return {Number} watcherCount */ export const getWatcherCount = (firebase, event, path, queryId) => { - const id = queryId || getQueryIdFromPath(path, event) || getWatchPath(event, path) + const id = + queryId || getQueryIdFromPath(path, event) || getWatchPath(event, path) return firebase._.watchers[id] } @@ -107,13 +113,18 @@ export const getWatcherCount = (firebase, event, path, queryId) => { * @param {String} queryId - Id of query */ export const unsetWatcher = (firebase, dispatch, event, path, queryId) => { - let id = queryId || getQueryIdFromPath(path, event) || getWatchPath(event, path) + let id = + queryId || getQueryIdFromPath(path, event) || getWatchPath(event, path) path = path.split('#')[0] const { watchers } = firebase._ if (watchers[id] <= 1) { delete watchers[id] if (event !== 'first_child' && event !== 'once') { - firebase.database().ref().child(path).off(event) + firebase + .database() + .ref() + .child(path) + .off(event) } } else if (watchers[id]) { watchers[id]-- @@ -171,23 +182,26 @@ export const applyParamsToQuery = (queryParams, query) => { equalToParam = equalToParam === 'null' ? null : equalToParam equalToParam = equalToParam === 'false' ? false : equalToParam equalToParam = equalToParam === 'true' ? true : equalToParam - query = param.length === 3 - ? query.equalTo(equalToParam, param[2]) - : query.equalTo(equalToParam) + query = + param.length === 3 + ? query.equalTo(equalToParam, param[2]) + : query.equalTo(equalToParam) break case 'startAt': let startAtParam = !doNotParse ? tryParseToNumber(param[1]) : param[1] startAtParam = startAtParam === 'null' ? null : startAtParam - query = param.length === 3 - ? query.startAt(startAtParam, param[2]) - : query.startAt(startAtParam) + query = + param.length === 3 + ? query.startAt(startAtParam, param[2]) + : query.startAt(startAtParam) break case 'endAt': let endAtParam = !doNotParse ? tryParseToNumber(param[1]) : param[1] endAtParam = endAtParam === 'null' ? null : endAtParam - query = param.length === 3 - ? query.endAt(endAtParam, param[2]) - : query.endAt(endAtParam) + query = + param.length === 3 + ? query.endAt(endAtParam, param[2]) + : query.endAt(endAtParam) break } }) @@ -202,13 +216,13 @@ export const applyParamsToQuery = (queryParams, query) => { * an ordered array. * @return {Array|Null} Ordered list of children from snapshot or null */ -export const orderedFromSnapshot = (snap) => { +export const orderedFromSnapshot = snap => { if (snap.hasChildren && !snap.hasChildren()) { return null } const ordered = [] if (snap.forEach) { - snap.forEach((child) => { + snap.forEach(child => { ordered.push({ key: child.key, value: child.val() }) }) } @@ -232,7 +246,7 @@ export const populateAndDispatch = (firebase, dispatch, config) => { const { data, populates, snapshot, path, storeAs } = config // TODO: Allow setting of unpopulated data before starting population through config return promisesForPopulate(firebase, snapshot.key, data, populates) - .then((results) => { + .then(results => { // dispatch child sets first so isLoaded is only set to true for // populatedDataToJS after all data is in redux (Issue #121) // TODO: Allow config to toggle Combining into one SET action @@ -252,7 +266,7 @@ export const populateAndDispatch = (firebase, dispatch, config) => { }) return results }) - .catch((err) => { + .catch(err => { dispatch({ type: actionTypes.ERROR, payload: err diff --git a/src/utils/reducers.js b/src/utils/reducers.js index 342cf96e9..d0d56a450 100644 --- a/src/utils/reducers.js +++ b/src/utils/reducers.js @@ -7,7 +7,7 @@ import { unset } from 'lodash/fp' * @return {Array} Path as Array * @private */ -export function pathToArr (path) { +export function pathToArr(path) { return path ? path.split(/\//).filter(p => !!p) : [] } @@ -17,7 +17,7 @@ export function pathToArr (path) { * @return {String} Path seperated with slashes * @private */ -export function getSlashStrPath (path) { +export function getSlashStrPath(path) { return pathToArr(path).join('/') } @@ -27,7 +27,7 @@ export function getSlashStrPath (path) { * @return {String} Path seperated with dots * @private */ -export function getDotStrPath (path) { +export function getDotStrPath(path) { return pathToArr(path).join('.') } @@ -41,18 +41,15 @@ export function getDotStrPath (path) { * passed object, and builds a state object with the same shape. * @private */ -export const combineReducers = reducers => - (state = {}, action) => - Object.keys(reducers).reduce( - (nextState, key) => { - nextState[key] = reducers[key]( // eslint-disable-line no-param-reassign - state[key], - action - ) - return nextState - }, - {} +export const combineReducers = reducers => (state = {}, action) => + Object.keys(reducers).reduce((nextState, key) => { + nextState[key] = reducers[key]( + // eslint-disable-line no-param-reassign + state[key], + action ) + return nextState + }, {}) /** * Recursively unset a property starting at the deep path, and unsetting the parent diff --git a/src/utils/storage.js b/src/utils/storage.js index 7dacd5e8f..0647beffb 100644 --- a/src/utils/storage.js +++ b/src/utils/storage.js @@ -1,13 +1,15 @@ export const deleteFile = (firebase, { path, dbPath }) => - firebase.storage() + firebase + .storage() .ref(path) .delete() - .then(() => - !dbPath || !firebase.database - ? ({ path }) // return path if dbPath does not exist - : firebase // Handle option for removing file info from database - .database() - .ref(dbPath) - .remove() - .then(() => ({ path, dbPath })) + .then( + () => + !dbPath || !firebase.database + ? { path } // return path if dbPath does not exist + : firebase // Handle option for removing file info from database + .database() + .ref(dbPath) + .remove() + .then(() => ({ path, dbPath })) ) diff --git a/src/withFirebase.js b/src/withFirebase.js index 722063eac..03e0a5999 100644 --- a/src/withFirebase.js +++ b/src/withFirebase.js @@ -34,7 +34,7 @@ export const createWithFirebase = (storeKey = 'store') => WrappedComponent => { store = this.context[storeKey] - render () { + render() { return ( WrappedComponent => { store = this.context[storeKey] - render () { + render() { return ( ') -new FirebaseServer(5000, 'localhost.firebaseio.test', { // eslint-disable-line no-new +/* eslint-disable no-new */ +new FirebaseServer(5000, 'localhost.firebaseio.test', { users: { [uid]: { displayName: 'Tester' } } }) +/* eslint-enable no-new */ // Chai Plugins chai.use(chaiAsPromised) @@ -67,14 +69,10 @@ global.firebase = Object.defineProperty(Firebase, '_', { value: { watchers: {}, authUid: null, - config: Object.assign( - {}, - fbConfig, - { - userProfile: 'users', - enableRedirectHandling: false // disabled due to lack of http/https - } - ) + config: Object.assign({}, fbConfig, { + userProfile: 'users', + enableRedirectHandling: false // disabled due to lack of http/https + }) }, writable: true, enumerable: true, diff --git a/test/unit/actions/auth.spec.js b/test/unit/actions/auth.spec.js index b0adfaa6a..029bef398 100644 --- a/test/unit/actions/auth.spec.js +++ b/test/unit/actions/auth.spec.js @@ -18,7 +18,11 @@ import { verifyPasswordResetCode } from '../../../src/actions/auth' import { actionTypes } from '../../../src/constants' -import { fakeFirebase, onAuthStateChangedSpy, firebaseWithConfig } from '../../utils' +import { + fakeFirebase, + onAuthStateChangedSpy, + firebaseWithConfig +} from '../../utils' // import { promisesForPopulate } from '../../../src/utils/populate' let functionSpy @@ -27,20 +31,25 @@ let res let profile let profileSnap let dispatch -const createSuccessSpy = (some) => sinon.spy(() => Promise.resolve(some)) -const createFailureSpy = () => sinon.spy(() => Promise.reject(new Error('test'))) -const addSpyToCurrentUser = (methodName, spyFunc) => ({ ...fakeFirebase, +const createSuccessSpy = some => sinon.spy(() => Promise.resolve(some)) +const createFailureSpy = () => + sinon.spy(() => Promise.reject(new Error('test'))) +const addSpyToCurrentUser = (methodName, spyFunc) => ({ + ...fakeFirebase, auth: () => ({ - get currentUser () { + get currentUser() { return { [methodName]: spyFunc } } - })}) -const addSpyWithArgsToAuthMethod = (methodName, spyFunc, args = []) => ({ ...fakeFirebase, + }) +}) +const addSpyWithArgsToAuthMethod = (methodName, spyFunc, args = []) => ({ + ...fakeFirebase, auth: () => ({ [methodName]: () => spyFunc(args) - })}) + }) +}) const fakeLogin = { email: 'test@tst.com', password: 'asdfasdf', role: 'admin' } describe('Actions: Auth -', () => { @@ -77,21 +86,19 @@ describe('Actions: Auth -', () => { it('undefined - dispatches SET_PROFILE with profile', () => { firebase._.config.profileParamsToPopulate = undefined handleProfileWatchResponse(dispatchSpy, firebase, profileSnap) - expect(dispatchSpy) - .to.be.calledWith({ - type: actionTypes.SET_PROFILE, - profile - }) + expect(dispatchSpy).to.be.calledWith({ + type: actionTypes.SET_PROFILE, + profile + }) }) it('{} - dispatches SET_PROFILE with profile (not supported)', () => { firebase._.config.profileParamsToPopulate = {} handleProfileWatchResponse(dispatchSpy, firebase, profileSnap) - expect(dispatchSpy) - .to.be.calledWith({ - type: actionTypes.SET_PROFILE, - profile - }) + expect(dispatchSpy).to.be.calledWith({ + type: actionTypes.SET_PROFILE, + profile + }) }) it('Array - dispatches SET_PROFILE with populated profile', () => { @@ -156,38 +163,65 @@ describe('Actions: Auth -', () => { describe('createUserProfile', () => { it('creates profile if config is enabled', async () => { - const userData = { uid: '123', email: 'test@test.com', providerData: [{}] } - const profile = await createUserProfile(dispatch, Firebase, userData, { some: 'asdf' }) + const userData = { + uid: '123', + email: 'test@test.com', + providerData: [{}] + } + const profile = await createUserProfile(dispatch, Firebase, userData, { + some: 'asdf' + }) expect(profile).to.be.an.object }) it('resolves with userData if userProfile config option is not enabled', async () => { - const userData = { uid: '123', email: 'test@test.com', providerData: [{}] } + const userData = { + uid: '123', + email: 'test@test.com', + providerData: [{}] + } const fb = firebaseWithConfig({ userProfile: null }) - const profile = await createUserProfile(dispatch, fb, userData, { some: 'asdf' }) + const profile = await createUserProfile(dispatch, fb, userData, { + some: 'asdf' + }) expect(profile).to.equal(userData) }) it('creates profile using profileFactory if it exists', async () => { - const userData = { uid: '123', email: 'test@test.com', providerData: [{}] } + const userData = { + uid: '123', + email: 'test@test.com', + providerData: [{}] + } const profileObj = { some: 'asdf' } const profileFactory = sinon.spy(() => profileObj) - const profile = await createUserProfile(dispatch, firebaseWithConfig({ profileFactory }), userData) + const profile = await createUserProfile( + dispatch, + firebaseWithConfig({ profileFactory }), + userData + ) expect(profile).to.have.property('some', profileObj.some) expect(profileFactory).to.have.been.calledOnce }) it('rejects for error in profileFactory function', async () => { - const profileFactory = () => { throw new Error('test') } + const profileFactory = () => { + throw new Error('test') + } try { - await createUserProfile(dispatch, firebaseWithConfig({ profileFactory }), {}, {}) + await createUserProfile( + dispatch, + firebaseWithConfig({ profileFactory }), + {}, + {} + ) } catch (err) { expect(err).to.have.property('message', 'test') } }) }) - describe('login', function () { + describe('login', function() { // Extend default timeout to prevent test fail on slow connection this.timeout(8000) @@ -209,12 +243,19 @@ describe('Actions: Auth -', () => { it('handles token login', async () => { try { - await login(dispatch, firebase, { token: 'asdfasdf' }, { uid: 'asdfasdf' }) + await login( + dispatch, + firebase, + { token: 'asdfasdf' }, + { uid: 'asdfasdf' } + ) } catch (err) { expect(err.message) // message indicates firebase's internal auth method called // invalid key is intentionally provided - .to.equal('The custom token format is incorrect. Please check the documentation.') + .to.equal( + 'The custom token format is incorrect. Please check the documentation.' + ) } }) }) @@ -258,7 +299,9 @@ describe('Actions: Auth -', () => { it('handles no email', async () => { try { - await createUser(dispatch, fakeFirebase, { password: fakeLogin.password }) + await createUser(dispatch, fakeFirebase, { + password: fakeLogin.password + }) } catch (err) { expect(err).to.be.an.object } @@ -274,7 +317,10 @@ describe('Actions: Auth -', () => { it('handles error with createUserWithEmailAndPassword', async () => { try { - await createUser(dispatch, fakeFirebase, { email: 'error', password: 'error' }) + await createUser(dispatch, fakeFirebase, { + email: 'error', + password: 'error' + }) } catch (err) { expect(err).to.be.an.object } @@ -282,7 +328,10 @@ describe('Actions: Auth -', () => { it('handles error with login', async () => { try { - await createUser(dispatch, fakeFirebase, { email: 'error2', password: 'error2' }) + await createUser(dispatch, fakeFirebase, { + email: 'error2', + password: 'error2' + }) } catch (err) { expect(err).to.be.an.object } @@ -290,7 +339,10 @@ describe('Actions: Auth -', () => { it('handles user-not-found error', async () => { try { - res = await createUser(dispatch, fakeFirebase, { email: 'error3', password: 'error2' }) + res = await createUser(dispatch, fakeFirebase, { + email: 'error3', + password: 'error2' + }) } catch (err) { expect(err.message).to.equal('auth/user-not-found') } @@ -325,16 +377,22 @@ describe('Actions: Auth -', () => { describe('confirmPasswordReset', () => { it('resets password for real user', () => { - return confirmPasswordReset(dispatch, fakeFirebase, 'test', 'test') - .then((err) => { + return confirmPasswordReset(dispatch, fakeFirebase, 'test', 'test').then( + err => { expect(err).to.be.undefined - }) + } + ) }) describe('handles error code: ', () => { it('auth/expired-action-code', async () => { try { - res = await confirmPasswordReset(dispatch, fakeFirebase, 'auth/expired-action-code', 'error') + res = await confirmPasswordReset( + dispatch, + fakeFirebase, + 'auth/expired-action-code', + 'error' + ) } catch (err) { expect(err.code).to.be.a.string } @@ -342,7 +400,12 @@ describe('Actions: Auth -', () => { it('auth/invalid-action-code', async () => { try { - res = await confirmPasswordReset(dispatch, fakeFirebase, 'auth/invalid-action-code', 'error') + res = await confirmPasswordReset( + dispatch, + fakeFirebase, + 'auth/invalid-action-code', + 'error' + ) } catch (err) { expect(err.code).to.be.a.string } @@ -350,7 +413,12 @@ describe('Actions: Auth -', () => { it('auth/user-disabled', async () => { try { - res = await confirmPasswordReset(dispatch, fakeFirebase, 'auth/user-disabled', 'error') + res = await confirmPasswordReset( + dispatch, + fakeFirebase, + 'auth/user-disabled', + 'error' + ) } catch (err) { expect(err.code).to.be.a.string } @@ -358,7 +426,12 @@ describe('Actions: Auth -', () => { it('auth/user-not-found', async () => { try { - res = await confirmPasswordReset(dispatch, fakeFirebase, 'auth/user-not-found', 'error') + res = await confirmPasswordReset( + dispatch, + fakeFirebase, + 'auth/user-not-found', + 'error' + ) } catch (err) { expect(err.code).to.be.a.string } @@ -366,7 +439,12 @@ describe('Actions: Auth -', () => { it('auth/weak-password', async () => { try { - res = await confirmPasswordReset(dispatch, fakeFirebase, 'auth/weak-password', 'error') + res = await confirmPasswordReset( + dispatch, + fakeFirebase, + 'auth/weak-password', + 'error' + ) } catch (err) { expect(err.code).to.be.a.string } @@ -374,7 +452,12 @@ describe('Actions: Auth -', () => { it('other', async () => { try { - res = await confirmPasswordReset(dispatch, fakeFirebase, 'asdfasdf', 'error') + res = await confirmPasswordReset( + dispatch, + fakeFirebase, + 'asdfasdf', + 'error' + ) } catch (err) { expect(err.code).to.be.a.string } @@ -407,8 +490,10 @@ describe('Actions: Auth -', () => { try { res = await updateProfile(dispatch, firebase, 'test') } catch (err) { - expect(err) - .to.have.property('message', 'Reference.update failed: First argument must be an object containing the children to replace.') + expect(err).to.have.property( + 'message', + 'Reference.update failed: First argument must be an object containing the children to replace.' + ) } }) }) @@ -422,21 +507,29 @@ describe('Actions: Auth -', () => { try { res = await updateAuth(dispatch, fakeFirebase, 'test') } catch (err) { - expect(err) - .to.have.property('message', 'User must be logged in to update auth.') + expect(err).to.have.property( + 'message', + 'User must be logged in to update auth.' + ) } }) it('calls firebase updateProfile method', async () => { const updateAuthSpy = createSuccessSpy() - const newFakeFirebase = addSpyToCurrentUser('updateProfile', updateAuthSpy) + const newFakeFirebase = addSpyToCurrentUser( + 'updateProfile', + updateAuthSpy + ) await updateAuth(dispatch, newFakeFirebase, 'test') expect(updateAuthSpy).to.have.been.calledOnce }) it('calls update profile if updateInProfile is true', async () => { const updateAuthSpy = createSuccessSpy() - const newFakeFirebase = addSpyToCurrentUser('updateProfile', updateAuthSpy) + const newFakeFirebase = addSpyToCurrentUser( + 'updateProfile', + updateAuthSpy + ) try { await updateAuth(dispatch, newFakeFirebase, 'test', true) } catch (err) { @@ -451,7 +544,10 @@ describe('Actions: Auth -', () => { it('rejects and dispatches on failure', async () => { const updateAuthSpy = createFailureSpy() - const newFakeFirebase = addSpyToCurrentUser('updateProfile', updateAuthSpy) + const newFakeFirebase = addSpyToCurrentUser( + 'updateProfile', + updateAuthSpy + ) try { await updateAuth(dispatch, newFakeFirebase, 'test') } catch (err) { @@ -472,8 +568,10 @@ describe('Actions: Auth -', () => { try { res = await updateEmail(dispatch, fakeFirebase, 'test') } catch (err) { - expect(err) - .to.have.property('message', 'User must be logged in to update email.') + expect(err).to.have.property( + 'message', + 'User must be logged in to update email.' + ) } }) @@ -518,8 +616,10 @@ describe('Actions: Auth -', () => { try { res = await reloadAuth(dispatch, firebase) } catch (err) { - expect(err) - .to.have.property('message', 'User must be logged in to reload auth.') + expect(err).to.have.property( + 'message', + 'User must be logged in to reload auth.' + ) } }) @@ -549,22 +649,37 @@ describe('Actions: Auth -', () => { try { res = await linkWithCredential(dispatch, firebase, '1234567891', {}) } catch (err) { - expect(err) - .to.have.property('message', 'User must be logged in to link with credential.') + expect(err).to.have.property( + 'message', + 'User must be logged in to link with credential.' + ) } }) it('calls firebase linkWithCredential method', async () => { const linkWithCredentialSpy = createSuccessSpy(() => Promise.resolve()) - const newFakeFirebase = addSpyToCurrentUser('linkWithCredential', linkWithCredentialSpy) + const newFakeFirebase = addSpyToCurrentUser( + 'linkWithCredential', + linkWithCredentialSpy + ) await linkWithCredential(dispatch, newFakeFirebase, '1234567891', {}) expect(linkWithCredentialSpy).to.have.been.calledOnce }) it('attaches confirm method on successful resolve', async () => { - const linkWithCredentialSpy = createSuccessSpy({ confirm: () => Promise.resolve({}) }) - const newFakeFirebase = addSpyToCurrentUser('linkWithCredential', linkWithCredentialSpy) - const res = await linkWithCredential(dispatch, newFakeFirebase, '1234567891', {}) + const linkWithCredentialSpy = createSuccessSpy({ + confirm: () => Promise.resolve({}) + }) + const newFakeFirebase = addSpyToCurrentUser( + 'linkWithCredential', + linkWithCredentialSpy + ) + const res = await linkWithCredential( + dispatch, + newFakeFirebase, + '1234567891', + {} + ) expect(linkWithCredentialSpy).to.have.been.calledOnce expect(res).to.respondTo('confirm') res.confirm() @@ -572,7 +687,10 @@ describe('Actions: Auth -', () => { it('rejects and dispatches on failure', async () => { const linkWithCredentialSpy = createFailureSpy() - const newFakeFirebase = addSpyToCurrentUser('linkWithCredential', linkWithCredentialSpy) + const newFakeFirebase = addSpyToCurrentUser( + 'linkWithCredential', + linkWithCredentialSpy + ) try { await linkWithCredential(dispatch, newFakeFirebase, '1234567891', {}) } catch (err) { @@ -589,22 +707,39 @@ describe('Actions: Auth -', () => { try { res = await signInWithPhoneNumber(firebase, dispatch, '1234567891', {}) } catch (err) { - expect(err) - .to.have.property('message', 'signInWithPhoneNumber failed: Second argument "applicationVerifier" must be an implementation of firebase.auth.ApplicationVerifier.') + expect(err).to.have.property( + 'message', + 'signInWithPhoneNumber failed: Second argument "applicationVerifier" must be an implementation of firebase.auth.ApplicationVerifier.' + ) } }) it('calls firebase signInWithPhoneNumber method', async () => { const signInWithPhoneNumberSpy = createSuccessSpy(() => Promise.resolve()) - const newFakeFirebase = addSpyWithArgsToAuthMethod('signInWithPhoneNumber', signInWithPhoneNumberSpy, ['1234567891', {}]) + const newFakeFirebase = addSpyWithArgsToAuthMethod( + 'signInWithPhoneNumber', + signInWithPhoneNumberSpy, + ['1234567891', {}] + ) await signInWithPhoneNumber(newFakeFirebase, dispatch, '1234567891', {}) expect(signInWithPhoneNumberSpy).to.have.been.calledOnce }) it('attaches confirm method on successful resolve', async () => { - const signInWithPhoneNumberSpy = createSuccessSpy({ confirm: () => Promise.resolve({}) }) - const newFakeFirebase = addSpyWithArgsToAuthMethod('signInWithPhoneNumber', signInWithPhoneNumberSpy, ['1234567891', {}]) - const res = await signInWithPhoneNumber(newFakeFirebase, dispatch, '1234567891', {}) + const signInWithPhoneNumberSpy = createSuccessSpy({ + confirm: () => Promise.resolve({}) + }) + const newFakeFirebase = addSpyWithArgsToAuthMethod( + 'signInWithPhoneNumber', + signInWithPhoneNumberSpy, + ['1234567891', {}] + ) + const res = await signInWithPhoneNumber( + newFakeFirebase, + dispatch, + '1234567891', + {} + ) expect(signInWithPhoneNumberSpy).to.have.been.calledOnce expect(res).to.respondTo('confirm') res.confirm() @@ -612,7 +747,11 @@ describe('Actions: Auth -', () => { it('rejects and dispatches on failure', async () => { const signInWithPhoneNumberSpy = createFailureSpy() - const newFakeFirebase = addSpyWithArgsToAuthMethod('signInWithPhoneNumber', signInWithPhoneNumberSpy, ['1234567891', {}]) + const newFakeFirebase = addSpyWithArgsToAuthMethod( + 'signInWithPhoneNumber', + signInWithPhoneNumberSpy, + ['1234567891', {}] + ) try { await signInWithPhoneNumber(newFakeFirebase, dispatch, '1234567891', {}) } catch (err) { diff --git a/test/unit/actions/query.spec.js b/test/unit/actions/query.spec.js index 0336e48ae..2439379a7 100644 --- a/test/unit/actions/query.spec.js +++ b/test/unit/actions/query.spec.js @@ -20,30 +20,63 @@ describe('Actions: Query', () => { }) it('throws if Firebase database has not been included', () => { - expect(() => watchEvent({})) - .to.Throw('Firebase database is required to create watchers') + expect(() => watchEvent({})).to.Throw( + 'Firebase database is required to create watchers' + ) }) it('runs given basic params', () => { - expect(watchEvent(firebase, dispatch, { type: 'once', path: 'projects' }, 'projects')) - .to.eventually.be.an.object + expect( + watchEvent( + firebase, + dispatch, + { type: 'once', path: 'projects' }, + 'projects' + ) + ).to.eventually.be.an.object }) it('runs given first_child', () => { - expect(watchEvent(firebase, dispatch, { type: 'first_child', path: 'projects' }, 'projects')) - .to.eventually.be.an.object + expect( + watchEvent( + firebase, + dispatch, + { type: 'first_child', path: 'projects' }, + 'projects' + ) + ).to.eventually.be.an.object }) it('runs value query', () => { - expect(watchEvent(firebase, dispatch, { type: 'value', path: 'projects' }, 'projects')) + expect( + watchEvent( + firebase, + dispatch, + { type: 'value', path: 'projects' }, + 'projects' + ) + ) }) it('handles populates', () => { - expect(watchEvent(firebase, dispatch, { type: 'value', path: 'projects', populates: [{ child: 'uid', root: 'users' }] }, 'projects')) + expect( + watchEvent( + firebase, + dispatch, + { + type: 'value', + path: 'projects', + populates: [{ child: 'uid', root: 'users' }] + }, + 'projects' + ) + ) }) it('throws for null type', () => { - expect(() => watchEvent(firebase, dispatch, { path: 'projects' }, 'projects')).to.Throw + expect(() => + watchEvent(firebase, dispatch, { path: 'projects' }, 'projects') + ).to.Throw }) }) @@ -53,7 +86,9 @@ describe('Actions: Query', () => { }) it('runs given basic params', () => { - expect(unWatchEvent(firebase, dispatch, { type: 'once', path: 'projects' })).to.be.a.function + expect( + unWatchEvent(firebase, dispatch, { type: 'once', path: 'projects' }) + ).to.be.a.function }) }) @@ -63,14 +98,16 @@ describe('Actions: Query', () => { }) it('runs given basic params', () => { - const events = [{type: 'once', path: 'test'}] + const events = [{ type: 'once', path: 'test' }] spy = sinon.spy(events, 'map') watchEvents(firebase, dispatch, events) expect(spy).to.be.calledOnce }) it('throws if not passed array', () => { - expect(() => watchEvents(firebase, dispatch, {})).to.Throw('Events config must be an Array') + expect(() => watchEvents(firebase, dispatch, {})).to.Throw( + 'Events config must be an Array' + ) }) }) @@ -80,14 +117,14 @@ describe('Actions: Query', () => { }) it('runs given basic params', () => { - const events = [{type: 'value', path: 'test'}] + const events = [{ type: 'value', path: 'test' }] spy = sinon.spy(events, 'forEach') unWatchEvents(firebase, dispatch, events) expect(spy).to.be.calledOnce }) it('throws for bad type', () => { - const events = [{path: 'test'}] + const events = [{ path: 'test' }] spy = sinon.spy(events, 'forEach') expect(() => unWatchEvents(firebase, dispatch, events)).to.Throw }) diff --git a/test/unit/actions/storage.spec.js b/test/unit/actions/storage.spec.js index cc88ecb57..9b671697b 100644 --- a/test/unit/actions/storage.spec.js +++ b/test/unit/actions/storage.spec.js @@ -23,12 +23,13 @@ describe('Actions: Storage', () => { }) it.skip('runs given basic params', () => - uploadFileWithProgress(dispatch, fakeFirebase, { path: 'projects', file: { name: 'test.png' } }) - .then((snap) => { - expect(spy).to.have.been.calledOnce - expect(snap).to.be.an.object - }) - ) + uploadFileWithProgress(dispatch, fakeFirebase, { + path: 'projects', + file: { name: 'test.png' } + }).then(snap => { + expect(spy).to.have.been.calledOnce + expect(snap).to.be.an.object + })) }) describe('uploadFile', () => { @@ -41,7 +42,9 @@ describe('Actions: Storage', () => { }) it('throws if storage does not exist', async () => { - expect(() => uploadFile(dispatch, {})).to.Throw('Firebase storage is required to upload files') + expect(() => uploadFile(dispatch, {})).to.Throw( + 'Firebase storage is required to upload files' + ) }) it('runs given basic params', async () => { @@ -50,7 +53,10 @@ describe('Actions: Storage', () => { storage: () => ({ ref: () => ({ put: putSpy }) }), _: firebase._ } - await uploadFile(spy, fake, { path: 'projects', file: { name: 'test.png' } }) + await uploadFile(spy, fake, { + path: 'projects', + file: { name: 'test.png' } + }) // firebase.storage() put method is called expect(putSpy).to.have.been.calledOnce // dispatch is called twice (once for FILE_UPLOAD_START, the other for FILE_UPLOAD_COMPLETE) @@ -71,25 +77,29 @@ describe('Actions: Storage', () => { }) it('handles dbPath', async () => { - const res = await uploadFile(dispatch, fakeFirebase, { ...defaultFileMeta, dbPath: 'projects' }) + const res = await uploadFile(dispatch, fakeFirebase, { + ...defaultFileMeta, + dbPath: 'projects' + }) expect(res).to.be.an.object }) it('calls database.push', async () => { - const putSpy = sinon.spy(() => Promise.resolve({ - metadata: { - name: 'file.png', - fullPath: 'test', - downloadURLs: [{ path: 'asdf' }] - } - })) + const putSpy = sinon.spy(() => + Promise.resolve({ + metadata: { + name: 'file.png', + fullPath: 'test', + downloadURLs: [{ path: 'asdf' }] + } + }) + ) const pushSpy = sinon.spy(() => Promise.resolve({})) const fake = { storage: () => ({ ref: () => ({ put: putSpy }) }), - database: Object.assign( - () => ({ ref: () => ({ push: pushSpy }) }), - { ServerValue: { TIMESTAMP: 123 } } - ), + database: Object.assign(() => ({ ref: () => ({ push: pushSpy }) }), { + ServerValue: { TIMESTAMP: 123 } + }), _: firebase._ } await uploadFile(spy, fake, { ...defaultFileMeta, dbPath: 'test' }) @@ -139,12 +149,18 @@ describe('Actions: Storage', () => { describe('progress option', () => { it('calls uploadFileWithProgress', async () => { - const putSpy = sinon.spy(() => Object.assign(Promise.resolve({}), { on: sinon.spy() })) + const putSpy = sinon.spy(() => + Object.assign(Promise.resolve({}), { on: sinon.spy() }) + ) const fake = { - storage: Object.assign(() => ({ - ref: () => ({ put: putSpy }) }), - { TaskEvent: { STATE_CHANGED: 'STATE_CHANGED' } - }), + storage: Object.assign( + () => ({ + ref: () => ({ put: putSpy }) + }), + { + TaskEvent: { STATE_CHANGED: 'STATE_CHANGED' } + } + ), _: firebase._ } const options = { progress: true } @@ -164,11 +180,12 @@ describe('Actions: Storage', () => { }) it('runs given basic params', () => - uploadFiles(dispatch, fakeFirebase, { path: 'projects', file: { name: 'test.png' } }) - .then((snap) => { - expect(snap).to.be.an.object - }) - ) + uploadFiles(dispatch, fakeFirebase, { + path: 'projects', + file: { name: 'test.png' } + }).then(snap => { + expect(snap).to.be.an.object + })) }) describe('deleteFile', () => { @@ -177,18 +194,15 @@ describe('Actions: Storage', () => { }) it('runs given path', () => - expect(deleteFile(dispatch, fakeFirebase, { path: 'projects' })) - .to - .eventually - .become({path: 'projects'}) - ) + expect( + deleteFile(dispatch, fakeFirebase, { path: 'projects' }) + ).to.eventually.become({ path: 'projects' })) it('runs given basic params', () => { const metaObj = { path: 'projects/test.png', dbPath: 'test.png' } - return expect(deleteFile(dispatch, fakeFirebase, metaObj)) - .to - .eventually - .become(metaObj) + return expect( + deleteFile(dispatch, fakeFirebase, metaObj) + ).to.eventually.become(metaObj) }) }) }) diff --git a/test/unit/enhancer.spec.js b/test/unit/enhancer.spec.js index 6636c3adf..c00ebf236 100644 --- a/test/unit/enhancer.spec.js +++ b/test/unit/enhancer.spec.js @@ -2,18 +2,19 @@ import { createStore, compose } from 'redux' import enhancer, { getFirebase } from '../../src/enhancer' const reducer = sinon.spy() -const valAtPath = (path) => - Firebase.ref(path).once('value').then((snap) => snap.val()) - -const generateCreateStore = (params) => - compose(enhancer( - Firebase, - { +const valAtPath = path => + Firebase.ref(path) + .once('value') + .then(snap => snap.val()) + +const generateCreateStore = params => + compose( + enhancer(Firebase, { userProfile: 'users', enableLogging: params && params.enableLogging, enableRedirectHandling: false - } - ))(createStore) + }) + )(createStore) const store = generateCreateStore()(reducer) let path @@ -28,22 +29,28 @@ describe('enhancer', () => { }) it('throws for first argument not being a valid Firebase library instance', () => { - expect(() => enhancer({})(() => ({}))()) - .to.Throw('v2.0.0-beta and higher require passing a firebase app instance or a firebase library instance. View the migration guide for details.') + expect(() => enhancer({})(() => ({}))()).to.Throw( + 'v2.0.0-beta and higher require passing a firebase app instance or a firebase library instance. View the migration guide for details.' + ) }) it('throws for first argument not being a Firebase app instance', () => { - expect(() => enhancer({ SDK_VERSION: '' })(() => ({}))()) - .to.Throw('v2.0.0-beta and higher require passing a firebase app instance or a firebase library instance. View the migration guide for details.') + expect(() => enhancer({ SDK_VERSION: '' })(() => ({}))()).to.Throw( + 'v2.0.0-beta and higher require passing a firebase app instance or a firebase library instance. View the migration guide for details.' + ) }) it('throws for first argument not being a Firebase app instance', () => { - expect(() => enhancer({ SDK_VERSION: '' })(() => ({}))()) - .to.Throw('v2.0.0-beta and higher require passing a firebase app instance or a firebase library instance. View the migration guide for details.') + expect(() => enhancer({ SDK_VERSION: '' })(() => ({}))()).to.Throw( + 'v2.0.0-beta and higher require passing a firebase app instance or a firebase library instance. View the migration guide for details.' + ) }) it('sets store.firebaseAuthIsReady when config.attachAuthIsReady it true', () => { - const store = enhancer({ SDK_VERSION: '', firebase_: {} }, { attachAuthIsReady: true })(() => ({}))() + const store = enhancer( + { SDK_VERSION: '', firebase_: {} }, + { attachAuthIsReady: true } + )(() => ({}))() expect(store).to.have.property('firebaseAuthIsReady') expect(store.firebaseAuthIsReady).to.be.a.function }) @@ -61,53 +68,60 @@ describe('enhancer', () => { describe('set', () => { it('accepts object', () => { - expect(store.firebase.set('test', {some: 'asdf'})) - .to.eventually.become(undefined) + expect( + store.firebase.set('test', { some: 'asdf' }) + ).to.eventually.become(undefined) }) }) describe('setWithMeta', () => { describe('accepts object', () => { it('accepts object', () => { - expect(store.firebase.setWithMeta('test', {some: 'asdf'})) - .to.eventually.become(undefined) + expect( + store.firebase.setWithMeta('test', { some: 'asdf' }) + ).to.eventually.become(undefined) }) }) describe('does not attach meta to string', () => { // TODO: confirm that data set actually does not include meta it('accepts object', () => { - expect(store.firebase.setWithMeta('test', 'asdd')) - .to.eventually.equal({}) + expect( + store.firebase.setWithMeta('test', 'asdd') + ).to.eventually.equal({}) }) }) }) describe('push', () => { it('accepts object', () => { - expect(store.firebase.push('test', {some: 'asdf'})) - .to.eventually.have.property('key') + expect( + store.firebase.push('test', { some: 'asdf' }) + ).to.eventually.have.property('key') }) }) describe('pushWithMeta', () => { it('accepts object', () => { - expect(store.firebase.pushWithMeta('test', {some: 'asdf'})) - .to.eventually.have.property('key') + expect( + store.firebase.pushWithMeta('test', { some: 'asdf' }) + ).to.eventually.have.property('key') }) }) describe('update', () => { it('accepts object', async () => { // undefined represents snapshot - const res = await store.firebase.update('test', {some: 'asdf'}) + const res = await store.firebase.update('test', { some: 'asdf' }) expect(res).to.equal(undefined) }) }) describe('updateWithMeta', () => { it('sets updatedAt', async () => { - const updateRes = await store.firebase.updateWithMeta('test', {some: 'asdddf'}) + const updateRes = await store.firebase.updateWithMeta('test', { + some: 'asdddf' + }) const after = await valAtPath('test') expect(after.updatedAt).to.exist expect(updateRes).to.equal(undefined) @@ -126,13 +140,13 @@ describe('enhancer', () => { }) // Skipped due to issue with mocked transaction not returning committed it('sets if unique', async () => { - const setRes = await store.firebase.uniqueSet(path, {some: 'asdf'}) + const setRes = await store.firebase.uniqueSet(path, { some: 'asdf' }) expect(setRes).to.respondTo('val') // resolves with snapshot }) it('throws if not unique', async () => { try { - await store.firebase.uniqueSet('test', {some: 'other'}) + await store.firebase.uniqueSet('test', { some: 'other' }) } catch (err) { expect(err.toString()).to.equal('Error: Path already exists.') } @@ -140,14 +154,14 @@ describe('enhancer', () => { it('calls onComplete on success', async () => { const func = sinon.spy() - await store.firebase.uniqueSet(path, {some: 'asdf'}, func) + await store.firebase.uniqueSet(path, { some: 'asdf' }, func) expect(func).to.have.been.calledOnce }) it('calls onComplete on error', async () => { const func = sinon.spy() try { - await store.firebase.uniqueSet(path, {some: 'asdf'}, func) + await store.firebase.uniqueSet(path, { some: 'asdf' }, func) } catch (err) { expect(func).to.have.been.calledOnce expect(err).to.exist @@ -182,9 +196,7 @@ describe('enhancer', () => { }) describe('unWatchEvent', () => { - it('unWatchesEvent', () => - store.firebase.unWatchEvent('value', 'test') - ) + it('unWatchesEvent', () => store.firebase.unWatchEvent('value', 'test')) }) describe('login', () => { @@ -210,18 +222,18 @@ describe('enhancer', () => { describe('verifyPasswordResetCode', () => { it('calls verifyPasswordResetCode Firebase method', () => { - expect(store.firebase.verifyPasswordResetCode('testCode')) // message associated with calling verifyPasswordResetCode on fake db - // TODO: Bring back once single error appears all the time (firebase-server issue causes this to change wordingonly on 6.11.1 builds?) - // .to.be.rejectedWith('Your API key is invalid, please check you have copied it correctly.') - .to.be.rejected + // TODO: Bring back once single error appears all the time (firebase-server issue causes this to change wordingonly on 6.11.1 builds?) + // .to.be.rejectedWith('Your API key is invalid, please check you have copied it correctly.') + expect(store.firebase.verifyPasswordResetCode('testCode')).to.be + .rejected }) }) describe('reloadAuth', () => { it('calls reloadAuth Firebase method', () => { expect(store.firebase.reloadAuth()) - // message associated with calling reloadAuth on fake db + // message associated with calling reloadAuth on fake db .to.be.rejectedWith('Must be logged in to reload auth') }) }) @@ -229,15 +241,16 @@ describe('enhancer', () => { describe('linkWithCredential', () => { it('calls reloadAuth Firebase method', () => { expect(store.firebase.linkWithCredential()) - // message associated with calling reloadAuth on fake db + // message associated with calling reloadAuth on fake db .to.be.rejectedWith('Must be logged in to reload auth') }) }) describe('storage', () => { it('is undefined if storage does not exist', () => { - expect(() => store.firebase.storage()) - .to.Throw('store.firebase.storage is not a function') + expect(() => store.firebase.storage()).to.Throw( + 'store.firebase.storage is not a function' + ) }) // TODO: create an instance with storage mocked @@ -250,8 +263,9 @@ describe('enhancer', () => { describe('messaging', () => { it('is undefined if messaging does not exist', () => { - expect(() => store.firebase.messaging()) - .to.Throw('store.firebase.messaging is not a function') + expect(() => store.firebase.messaging()).to.Throw( + 'store.firebase.messaging is not a function' + ) }) // TODO: create an instance with messaging mocked diff --git a/test/unit/firebaseConnect.spec.js b/test/unit/firebaseConnect.spec.js index bc1301139..7c2d9cd30 100644 --- a/test/unit/firebaseConnect.spec.js +++ b/test/unit/firebaseConnect.spec.js @@ -1,19 +1,26 @@ import React from 'react' import ReactDOM from 'react-dom' -import TestUtils from 'react-addons-test-utils' +import TestUtils from 'react-dom/test-utils' import { createSink } from 'recompose' -import { storeWithFirebase, Container, ProviderMock, TestContainer } from '../utils' -import firebaseConnect, { createFirebaseConnect } from '../../src/firebaseConnect' +import { + storeWithFirebase, + Container, + ProviderMock, + TestContainer +} from '../utils' +import firebaseConnect, { + createFirebaseConnect +} from '../../src/firebaseConnect' const createContainer = () => { const store = storeWithFirebase() - const WrappedContainer = firebaseConnect((props) => [ + const WrappedContainer = firebaseConnect(props => [ `test/${props.dynamicProp}` ])(Container) const tree = TestUtils.renderIntoDocument( - + ) @@ -51,10 +58,14 @@ describe('firebaseConnect', () => { }) describe('sets displayName static as ', () => { - describe('FirebaseConnect(${WrappedComponentName}) for', () => { // eslint-disable-line no-template-curly-in-string + /* eslint-disable no-template-curly-in-string */ + describe('FirebaseConnect(${WrappedComponentName}) for', () => { + /* eslint-enable no-template-curly-in-string */ it('class components', () => { const containerPrime = firebaseConnect()(TestContainer) - expect(containerPrime.displayName).to.equal(`FirebaseConnect(TestContainer)`) + expect(containerPrime.displayName).to.equal( + `FirebaseConnect(TestContainer)` + ) }) it('string components', () => { diff --git a/test/unit/firestoreConnect.spec.js b/test/unit/firestoreConnect.spec.js index b59f51e23..b3948353c 100644 --- a/test/unit/firestoreConnect.spec.js +++ b/test/unit/firestoreConnect.spec.js @@ -1,20 +1,25 @@ import React from 'react' import ReactDOM from 'react-dom' -import TestUtils from 'react-addons-test-utils' -import { storeWithFirestore, Container, ProviderMock, TestContainer } from '../utils' +import TestUtils from 'react-dom/test-utils' +import { + storeWithFirestore, + Container, + ProviderMock, + TestContainer +} from '../utils' import firestoreConnect, { createFirestoreConnect } from '../../src/firestoreConnect' const createContainer = () => { const store = storeWithFirestore() - const WrappedContainer = firestoreConnect((props) => [ + const WrappedContainer = firestoreConnect(props => [ `test/${props.dynamicProp}` ])(Container) const tree = TestUtils.renderIntoDocument( - + ) @@ -55,7 +60,9 @@ describe('firestoreConnect', () => { }) describe('sets displayName static as ', () => { - describe('FirestoreConnect(${WrappedComponentName}) for', () => { // eslint-disable-line no-template-curly-in-string + /* eslint-disable no-template-curly-in-string */ + describe('FirestoreConnect(${WrappedComponentName}) for', () => { + /* eslint-enable no-template-curly-in-string */ it('standard components', () => { const containerPrime = firestoreConnect()(TestContainer) expect(containerPrime.displayName).to.equal( diff --git a/test/unit/helpers.spec.js b/test/unit/helpers.spec.js index b34cd3721..3f0648b4c 100644 --- a/test/unit/helpers.spec.js +++ b/test/unit/helpers.spec.js @@ -12,23 +12,18 @@ describe('Helpers:', () => { expect(helpers).to.respondTo('getVal') }) it('passes notSetValue', () => { - expect(helpers.getVal(null, 'some', [])) - .to - .be - .empty + expect(helpers.getVal(null, 'some', [])).to.be.empty }) it('gets top level data', () => { - expect(helpers.getVal(exampleData, 'data/some')) - .to - .equal('data') + expect(helpers.getVal(exampleData, 'data/some')).to.equal('data') }) it('gets nested data', () => { - expect(helpers.getVal(exampleData, 'data/projects/CDF')) - .to - .have - .property('owner', 'ABC') + expect(helpers.getVal(exampleData, 'data/projects/CDF')).to.have.property( + 'owner', + 'ABC' + ) }) }) @@ -38,64 +33,62 @@ describe('Helpers:', () => { }) it('passes notSetValue', () => { - expect(helpers.populate(null, '/some', [], exampleData)) - .to - .equal(exampleData) + expect(helpers.populate(null, '/some', [], exampleData)).to.equal( + exampleData + ) }) it('returns undefined for non existant path', () => { - expect(helpers.populate(exampleData, '/asdfasdfadsf', [])) - .to - .equal(undefined) + expect(helpers.populate(exampleData, '/asdfasdfadsf', [])).to.equal( + undefined + ) }) it('returns null for null data', () => { - expect(helpers.populate(exampleData, '/missing/data', [])) - .to - .equal(null) + expect(helpers.populate(exampleData, '/missing/data', [])).to.equal(null) }) it('returns unpopulated data for no populates', () => { path = 'projects' - expect(helpers.populate(exampleData, path, []).CDF.owner) - .to - .equal(exampleData.data[path].CDF.owner) + expect(helpers.populate(exampleData, path, []).CDF.owner).to.equal( + exampleData.data[path].CDF.owner + ) }) describe('profile', () => { it('returns unpopulated data for no populates', () => { path = 'profile' - expect(helpers.populate(exampleData, path, [])) - .to - .have - .property('role', 'tester') + expect(helpers.populate(exampleData, path, [])).to.have.property( + 'role', + 'tester' + ) }) it('returns unpopulated data for invalid populate', () => { path = 'profile' populates = [{ child: 'role', root: 'users' }] - expect(helpers.populate(exampleData, path, populates)) - .to - .deep - .property('role', 'tester') + expect(helpers.populate(exampleData, path, populates)).to.deep.property( + 'role', + 'tester' + ) }) it('populates a single parameter', () => { path = 'profile' populates = [{ child: 'role', root: 'roles' }] - expect(helpers.populate(exampleData, path, populates)) - .to - .deep - .property('role.somePermission', true) + expect(helpers.populate(exampleData, path, populates)).to.deep.property( + 'role.somePermission', + true + ) }) it('populates a list parameter', () => { path = 'profile' populates = [{ child: 'notes', root: 'notes' }] - expect(helpers.populate(exampleData, path, populates)) - .to - .deep - .property('notes.123.text', get(exampleData, 'data.notes.123.text')) + expect(helpers.populate(exampleData, path, populates)).to.deep.property( + 'notes.123.text', + get(exampleData, 'data.notes.123.text') + ) }) }) @@ -108,47 +101,48 @@ describe('Helpers:', () => { it('populates value', () => { path = 'projects/CDF' populates = [{ child: 'owner', root: rootName }] - expect(helpers.populate(exampleData, path, populates).owner) - .to - .have - .property('displayName', 'scott') + expect( + helpers.populate(exampleData, path, populates).owner + ).to.have.property('displayName', 'scott') }) it('handles child path', () => { path = 'projects/QRS' populates = [{ child: 'nested.owner', root: rootName }] const populatedData = helpers.populate(exampleData, path, populates) - expect(populatedData.nested.owner) - .to - .have - .property('displayName', 'scott') + expect(populatedData.nested.owner).to.have.property( + 'displayName', + 'scott' + ) }) it('populates childParam', () => { path = 'projects/CDF' - expect(helpers.populate(exampleData, path, [{ child: 'owner', root: rootName, childParam: 'displayName' }])) - .to - .have - .property('owner', 'scott') + expect( + helpers.populate(exampleData, path, [ + { child: 'owner', root: rootName, childParam: 'displayName' } + ]) + ).to.have.property('owner', 'scott') }) it('populates childAlias', () => { path = 'projects/CDF' const child = 'owner' const childAlias = 'ownerObj' - expect(helpers.populate(exampleData, path, [{ child, root: rootName, childAlias }])) - .to - .have - .deep - .property(`${childAlias}.displayName`, 'scott') + expect( + helpers.populate(exampleData, path, [ + { child, root: rootName, childAlias } + ]) + ).to.have.deep.property(`${childAlias}.displayName`, 'scott') }) it('keeps non-existant children', () => { path = 'projects/OKF' - expect(helpers.populate(exampleData, path, [{ child: 'owner', root: rootName }])) - .to - .have - .property('owner', 'asdfasdf') + expect( + helpers.populate(exampleData, path, [ + { child: 'owner', root: rootName } + ]) + ).to.have.property('owner', 'asdfasdf') }) }) @@ -159,44 +153,35 @@ describe('Helpers:', () => { it('populates values', () => { path = 'projects/OKF' - populates = [ - { child: 'collaborators', root: rootName } - ] + populates = [{ child: 'collaborators', root: rootName }] const populatedData = helpers.populate(exampleData, path, populates) - expect(populatedData) - .to - .have - .deep - .property(`collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName) + expect(populatedData).to.have.deep.property( + `collaborators.ABC.displayName`, + exampleData.data[rootName].ABC.displayName + ) }) }) describe('config as function -', () => { it('populates values', () => { path = 'projects/CDF' - populates = (projectKey, projectData) => ([ + populates = (projectKey, projectData) => [ // configure populates with key / data tuple... { child: 'owner', root: rootName } - ]) + ] const populatedData = helpers.populate(exampleData, path, populates) - expect(populatedData.owner) - .to - .have - .property('displayName', 'scott') + expect(populatedData.owner).to.have.property('displayName', 'scott') }) }) it('works with ordered', () => { path = 'ordered/projects/0' - populates = [ - { child: 'owner', root: rootName } - ] + populates = [{ child: 'owner', root: rootName }] const populated = helpers.populate(exampleData, path, populates) - expect(populated) - .to - .have - .deep - .property(`value.owner.displayName`, get(exampleData, `data.${rootName}.ABC.displayName`)) + expect(populated).to.have.deep.property( + `value.owner.displayName`, + get(exampleData, `data.${rootName}.ABC.displayName`) + ) }) }) @@ -209,10 +194,11 @@ describe('Helpers:', () => { it('populates value', () => { valName = 'CDF' - expect(helpers.populate(exampleData, path, [{ child: 'owner', root: rootName }])[valName].owner) - .to - .have - .property('displayName', 'scott') + expect( + helpers.populate(exampleData, path, [ + { child: 'owner', root: rootName } + ])[valName].owner + ).to.have.property('displayName', 'scott') }) it('populates value even when others are missing', () => { @@ -222,33 +208,34 @@ describe('Helpers:', () => { { child: 'owner', root: rootName }, { child: 'category', root: catRootName } ] - expect(helpers.populate(exampleData, path, populates)) - .to - .have - .deep - .property(`${valName}.owner.displayName`, 'scott') + expect( + helpers.populate(exampleData, path, populates) + ).to.have.deep.property(`${valName}.owner.displayName`, 'scott') }) it('populates childParam', () => { valName = 'CDF' const child = 'owner' const childParam = 'displayName' - expect(helpers.populate(exampleData, path, [{ child, root: rootName, childParam }])) - .to - .have - .deep - .property(`${valName}.${child}`, 'scott') + expect( + helpers.populate(exampleData, path, [ + { child, root: rootName, childParam } + ]) + ).to.have.deep.property(`${valName}.${child}`, 'scott') }) it('populates childAlias', () => { const child = 'owner' const childAlias = 'ownerObj' valName = 'CDF' - expect(helpers.populate(exampleData, path, [{ child, root: rootName, childAlias }])) - .to - .have - .deep - .property(`${valName}.${childAlias}.displayName`, 'scott') + expect( + helpers.populate(exampleData, path, [ + { child, root: rootName, childAlias } + ]) + ).to.have.deep.property( + `${valName}.${childAlias}.displayName`, + 'scott' + ) }) }) @@ -260,33 +247,27 @@ describe('Helpers:', () => { }) it('populates values', () => { - populates = [ - { child: 'collaborators', root: rootName } - ] + populates = [{ child: 'collaborators', root: rootName }] const populatedData = helpers.populate(exampleData, path, populates) - expect(populatedData) - .to - .have - .deep - .property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName) + expect(populatedData).to.have.deep.property( + `${valName}.collaborators.ABC.displayName`, + exampleData.data[rootName].ABC.displayName + ) }) it('keeps non-existant children', () => { - populates = [ - { child: 'collaborators', root: rootName } - ] + populates = [{ child: 'collaborators', root: rootName }] // Non matching collaborator - expect(helpers.populate(exampleData, path, populates)) - .to - .have - .deep - .property(`${valName}.collaborators.abc`, true) + expect( + helpers.populate(exampleData, path, populates) + ).to.have.deep.property(`${valName}.collaborators.abc`, true) // Matching collaborator - expect(helpers.populate(exampleData, path, populates)) - .to - .have - .deep - .property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName) + expect( + helpers.populate(exampleData, path, populates) + ).to.have.deep.property( + `${valName}.collaborators.ABC.displayName`, + exampleData.data[rootName].ABC.displayName + ) }) }) @@ -295,16 +276,15 @@ describe('Helpers:', () => { path = 'projects' rootName = 'users' valName = 'OKF' - populates = (projectKey, projectData) => ([ + populates = (projectKey, projectData) => [ // configure populates with key / data tuple... { child: 'collaborators', root: rootName } - ]) + ] const populatedData = helpers.populate(exampleData, path, populates) - expect(populatedData) - .to - .have - .deep - .property(`${valName}.collaborators.ABC.displayName`, get(exampleData, `data.${rootName}.ABC.displayName`)) + expect(populatedData).to.have.deep.property( + `${valName}.collaborators.ABC.displayName`, + get(exampleData, `data.${rootName}.ABC.displayName`) + ) }) }) @@ -315,15 +295,12 @@ describe('Helpers:', () => { }) it('with list child', () => { - populates = [ - { child: 'collaborators', root: rootName } - ] + populates = [{ child: 'collaborators', root: rootName }] const populatedArr = helpers.populate(exampleData, path, populates) - expect(populatedArr[0]) - .to - .have - .deep - .property(`value.collaborators.ABC.displayName`, get(exampleData, `data.${rootName}.ABC.displayName`)) + expect(populatedArr[0]).to.have.deep.property( + `value.collaborators.ABC.displayName`, + get(exampleData, `data.${rootName}.ABC.displayName`) + ) }) it('with multiple populates', () => { @@ -332,23 +309,19 @@ describe('Helpers:', () => { { child: 'collaborators', root: rootName } ] const populatedArr = helpers.populate(exampleData, path, populates) - expect(populatedArr[0]) - .to - .have - .deep - .property(`value.collaborators.ABC.displayName`, get(exampleData, `data.${rootName}.ABC.displayName`)) + expect(populatedArr[0]).to.have.deep.property( + `value.collaborators.ABC.displayName`, + get(exampleData, `data.${rootName}.ABC.displayName`) + ) }) it('with none existing child', () => { - populates = [ - { child: 'random' } - ] + populates = [{ child: 'random' }] const populatedArr = helpers.populate(exampleData, path, populates) - expect(populatedArr[0]) - .to - .have - .deep - .property(`value.collaborators.ABC`, true) + expect(populatedArr[0]).to.have.deep.property( + `value.collaborators.ABC`, + true + ) }) }) }) @@ -366,17 +339,19 @@ describe('Helpers:', () => { { child: 'notes', root: 'notes' } ] // check that notes are populated - expect(helpers.populate(exampleData, `/${path}`, populates)) - .to - .have - .deep - .property(`${valName}.notes.123.text`, exampleData.data.notes['123'].text) + expect( + helpers.populate(exampleData, `/${path}`, populates) + ).to.have.deep.property( + `${valName}.notes.123.text`, + exampleData.data.notes['123'].text + ) // check that owner is populated - expect(helpers.populate(exampleData, `/${path}`, populates)) - .to - .have - .deep - .property(`${valName}.owner.displayName`, exampleData.data.users['ABC'].displayName) + expect( + helpers.populate(exampleData, `/${path}`, populates) + ).to.have.deep.property( + `${valName}.owner.displayName`, + exampleData.data.users['ABC'].displayName + ) }) // Skipped since this is not currently supported @@ -386,16 +361,18 @@ describe('Helpers:', () => { { child: 'collaborators', root: rootName } ] // TODO: Test both children are populated - expect(helpers.populate(exampleData, `/${path}`, populates)) - .to - .have - .deep - .property(`${valName}.owner.displayName`, exampleData.data[rootName].ABC.displayName) - expect(helpers.populate(exampleData, `/${path}`, populates)) - .to - .have - .deep - .property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName) + expect( + helpers.populate(exampleData, `/${path}`, populates) + ).to.have.deep.property( + `${valName}.owner.displayName`, + exampleData.data[rootName].ABC.displayName + ) + expect( + helpers.populate(exampleData, `/${path}`, populates) + ).to.have.deep.property( + `${valName}.collaborators.ABC.displayName`, + exampleData.data[rootName].ABC.displayName + ) }) }) }) diff --git a/test/unit/reducer.spec.js b/test/unit/reducer.spec.js index ef23ee1ea..d57073ec5 100644 --- a/test/unit/reducer.spec.js +++ b/test/unit/reducer.spec.js @@ -50,8 +50,9 @@ describe('reducer', () => { describe('throws for invalid initial state', () => { it('in errors', () => { action = { type: actionTypes.UNAUTHORIZED_ERROR } - expect(() => firebaseReducer({ errors: null }, action)) - .to.throw('Errors state must be an array') + expect(() => firebaseReducer({ errors: null }, action)).to.throw( + 'Errors state must be an array' + ) }) }) @@ -60,8 +61,10 @@ describe('reducer', () => { }) it('returns state by default', () => { - expect(firebaseReducer(externalState, {})) - .to.have.property('data', externalState.data) + expect(firebaseReducer(externalState, {})).to.have.property( + 'data', + externalState.data + ) }) beforeEach(() => { @@ -77,38 +80,39 @@ describe('reducer', () => { describe.skip('empty path', () => { it('empty path', () => { action = { type: actionTypes.START, profile } - expect(firebaseReducer({}, action)).to.deep.equal({...initialState}) + expect(firebaseReducer({}, action)).to.deep.equal({ ...initialState }) }) }) describe('START action', () => { it('sets requested state for path', () => { action = { type: actionTypes.START, path: 'some' } - expect(firebaseReducer({}, action)) - .to.have.deep.property(`requested.${action.path}`) + expect(firebaseReducer({}, action)).to.have.deep.property( + `requested.${action.path}` + ) }) it('sets requesting state for path', () => { action = { type: actionTypes.START, path: 'some' } - expect(firebaseReducer({}, action)) - .to.have.deep.property(`requesting.${action.path}`) + expect(firebaseReducer({}, action)).to.have.deep.property( + `requesting.${action.path}` + ) }) it('sets timestamps state for path', () => { action = { type: actionTypes.START, path: 'some' } - expect(firebaseReducer({}, action)) - .to.have.deep.property(`timestamps.${action.path}`) + expect(firebaseReducer({}, action)).to.have.deep.property( + `timestamps.${action.path}` + ) }) }) // Skipped due to empty path being written in reducer (not expected) it.skip('handles undefined path', () => { - expect( - firebaseReducer( - exampleData, - { type: actionTypes.START } - ) - ).to.equal({ ...noError, requested: { '': false } }) + expect(firebaseReducer(exampleData, { type: actionTypes.START })).to.equal({ + ...noError, + requested: { '': false } + }) }) // Skipped due to empty path being written in reducer (not expected) @@ -116,19 +120,19 @@ describe('reducer', () => { describe('sets requesting and requested when they are', () => { it('empty', () => { expect( - firebaseReducer( - exampleData, - { type: actionTypes.START, path: 'test' } - ) + firebaseReducer(exampleData, { + type: actionTypes.START, + path: 'test' + }) ).to.equal(noError) }) it('already set', () => { expect( - firebaseReducer( - exampleData, - { type: actionTypes.START, path: 'test' } - ) + firebaseReducer(exampleData, { + type: actionTypes.START, + path: 'test' + }) ).to.equal(noError) }) }) @@ -137,166 +141,177 @@ describe('reducer', () => { describe('SET action', () => { it.skip('deletes data from state when data is null', () => { action = { type: actionTypes.SET, path: 'test' } - expect(firebaseReducer({}, action)) - .to.deep.equal(initialState) + expect(firebaseReducer({}, action)).to.deep.equal(initialState) }) it('sets data to state under path', () => { action = { type: actionTypes.SET, path, data: exampleData } - expect(firebaseReducer({}, action).data) - .to.deep.equal({ - ...initialState.data, - [path]: exampleData - }) + expect(firebaseReducer({}, action).data).to.deep.equal({ + ...initialState.data, + [path]: exampleData + }) }) it('sets data to state under paths that end in a number', () => { action = { type: actionTypes.SET, path: 'test/123', data: exampleData } - expect(firebaseReducer({}, action).data) - .to.deep.equal({ - ...initialState.data, - test: { - 123: exampleData - } - }) + expect(firebaseReducer({}, action).data).to.deep.equal({ + ...initialState.data, + test: { + 123: exampleData + } + }) }) it('sets data to path with already existing data', () => { initialData = { data: { test: { [childKey]: { foo1: 'bar1' } } } } - action = { type: actionTypes.SET, path: childPath, data: { foo2: 'bar2' } } - expect(firebaseReducer(initialData, action).data) - .to.deep.equal({ - ...initialState.data, - test: { - [childKey]: { - foo2: 'bar2' - } + action = { + type: actionTypes.SET, + path: childPath, + data: { foo2: 'bar2' } + } + expect(firebaseReducer(initialData, action).data).to.deep.equal({ + ...initialState.data, + test: { + [childKey]: { + foo2: 'bar2' } - }) + } + }) }) it('sets data to path with already existing data with numeric keys', () => { initialData = { data: { test: { [childKey]: { 123: 'bar1' } } } } action = { type: actionTypes.SET, path: childPath, data: { 124: 'bar2' } } - expect(firebaseReducer(initialData, action).data) - .to.deep.equal({ - ...initialState.data, - test: { - [childKey]: { - 124: 'bar2' - } + expect(firebaseReducer(initialData, action).data).to.deep.equal({ + ...initialState.data, + test: { + [childKey]: { + 124: 'bar2' } - }) + } + }) }) it('sets data to path with already existing value of null', () => { initialData = { data: { test: { [childKey]: null } } } action = { type: actionTypes.SET, path: childPath, data: newData } - expect(firebaseReducer(initialData, action).data) - .to.deep.equal(setWith(Object, childDotPath, newData, {})) + expect(firebaseReducer(initialData, action).data).to.deep.equal( + setWith(Object, childDotPath, newData, {}) + ) }) it('sets data to path with already existing parent of null', () => { initialData = { data: { test: null } } action = { type: actionTypes.SET, path: childPath, data: exampleData } - expect(firebaseReducer(initialData, action).data) - .to.deep.equal(setWith(Object, childDotPath, exampleData, {})) + expect(firebaseReducer(initialData, action).data).to.deep.equal( + setWith(Object, childDotPath, exampleData, {}) + ) }) }) describe('MERGE action -', () => { it.skip('deletes data from state when data is null', () => { action = { type: actionTypes.MERGE, path: 'test' } - expect(firebaseReducer({}, action)) - .to.deep.equal(initialState) + expect(firebaseReducer({}, action)).to.deep.equal(initialState) }) it('merge data to empty state under path', () => { action = { type: actionTypes.MERGE, path, data: exampleData } - expect(firebaseReducer({}, action).data) - .to.deep.equal({ - ...initialState.data, - [path]: exampleData - }) + expect(firebaseReducer({}, action).data).to.deep.equal({ + ...initialState.data, + [path]: exampleData + }) }) it('merge data to empty state under paths that end in a number', () => { action = { type: actionTypes.MERGE, path: 'test/123', data: exampleData } - expect(firebaseReducer({}, action).data) - .to.deep.equal({ - ...initialState.data, - test: { - 123: exampleData - } - }) + expect(firebaseReducer({}, action).data).to.deep.equal({ + ...initialState.data, + test: { + 123: exampleData + } + }) }) it('merges data to path with already existing data', () => { initialData = { data: { test: { [childKey]: { foo1: 'bar1' } } } } - action = { type: actionTypes.MERGE, path: childPath, data: { foo2: 'bar2' } } - expect(firebaseReducer(initialData, action).data) - .to.deep.equal({ - ...initialState.data, - test: { - [childKey]: { - foo1: 'bar1', - foo2: 'bar2' - } + action = { + type: actionTypes.MERGE, + path: childPath, + data: { foo2: 'bar2' } + } + expect(firebaseReducer(initialData, action).data).to.deep.equal({ + ...initialState.data, + test: { + [childKey]: { + foo1: 'bar1', + foo2: 'bar2' } - }) + } + }) }) it('merges data to path with already existing data with numeric keys', () => { initialData = { data: { test: { [childKey]: { 123: 'bar1' } } } } - action = { type: actionTypes.MERGE, path: childPath, data: { 124: 'bar2' } } - expect(firebaseReducer(initialData, action).data) - .to.deep.equal({ - ...initialState.data, - test: { - [childKey]: { - 123: 'bar1', - 124: 'bar2' - } + action = { + type: actionTypes.MERGE, + path: childPath, + data: { 124: 'bar2' } + } + expect(firebaseReducer(initialData, action).data).to.deep.equal({ + ...initialState.data, + test: { + [childKey]: { + 123: 'bar1', + 124: 'bar2' } - }) + } + }) }) it('merge data to path with already existing value of null', () => { initialData = { data: { test: { [childKey]: null } } } action = { type: actionTypes.MERGE, path: childPath, data: newData } - expect(firebaseReducer(initialData, action).data) - .to.deep.equal(setWith(Object, childDotPath, newData, {})) + expect(firebaseReducer(initialData, action).data).to.deep.equal( + setWith(Object, childDotPath, newData, {}) + ) }) it('merge data to path with already existing parent of null', () => { initialData = { data: { test: null } } action = { type: actionTypes.MERGE, path: childPath, data: exampleData } - expect(firebaseReducer(initialData, action).data) - .to.deep.equal(setWith(Object, childDotPath, exampleData, {})) + expect(firebaseReducer(initialData, action).data).to.deep.equal( + setWith(Object, childDotPath, exampleData, {}) + ) }) }) describe('NO_VALUE action -', () => { it('sets path to null', () => { action = { type: actionTypes.NO_VALUE, path } - expect(firebaseReducer({}, action).data) - .to.deep.equal({ [path]: null }) + expect(firebaseReducer({}, action).data).to.deep.equal({ [path]: null }) }) }) describe('UNSET_LISTENER action -', () => { it('sets state', () => { - action = { type: actionTypes.UNSET_LISTENER, path: 'asdfasdf', payload: { id: 1 } } - expect(firebaseReducer({}, action)) - .to.deep.equal(initialState) + action = { + type: actionTypes.UNSET_LISTENER, + path: 'asdfasdf', + payload: { id: 1 } + } + expect(firebaseReducer({}, action)).to.deep.equal(initialState) }) }) describe('UNSET_LISTENER action -', () => { - action = { type: actionTypes.UNSET_LISTENER, path: 'asdfasdf', payload: { id: 1 } } + action = { + type: actionTypes.UNSET_LISTENER, + path: 'asdfasdf', + payload: { id: 1 } + } it('sets state', () => { - expect(firebaseReducer({}, action)) - .to.deep.equal(initialState) + expect(firebaseReducer({}, action)).to.deep.equal(initialState) }) }) @@ -311,11 +326,10 @@ describe('reducer', () => { it('removes for no profile', () => { action = { type: actionTypes.SET_PROFILE } - expect(firebaseReducer({}, action)) - .to.deep.equal({ - ...initialState, - profile: { isLoaded: true, isEmpty: true } - }) + expect(firebaseReducer({}, action)).to.deep.equal({ + ...initialState, + profile: { isLoaded: true, isEmpty: true } + }) }) }) @@ -347,18 +361,25 @@ describe('reducer', () => { initialData = { data: { [preservePath]: todos } } action = { type: actionTypes.LOGOUT, preserve: [preservePath] } // load todos into state and confirm they are kept on logout - expect(firebaseReducer(initialData, action)) - .to.have.deep.property(`data.${preservePath}`, todos) + expect(firebaseReducer(initialData, action)).to.have.deep.property( + `data.${preservePath}`, + todos + ) }) it('supports preserving data in state.ordered', () => { const preservePath = 'todos' const todos = [{ a: 'todo' }] initialData = { ordered: { todos } } - action = { type: actionTypes.LOGOUT, preserve: { ordered: [preservePath] } } + action = { + type: actionTypes.LOGOUT, + preserve: { ordered: [preservePath] } + } // load todos into state and confirm they are kept on logout - expect(firebaseReducer(initialData, action)) - .to.have.deep.property(`ordered.${preservePath}`, todos) + expect(firebaseReducer(initialData, action)).to.have.deep.property( + `ordered.${preservePath}`, + todos + ) }) it('supports preserving data in state.auth', () => { @@ -367,18 +388,25 @@ describe('reducer', () => { initialData = { auth: { isEmpty: false, displayName } } action = { type: actionTypes.LOGOUT, preserve: { auth: [preservePath] } } // load todos into state and confirm they are kept on logout - expect(firebaseReducer(initialData, action)) - .to.have.deep.property(`auth.${preservePath}`, displayName) + expect(firebaseReducer(initialData, action)).to.have.deep.property( + `auth.${preservePath}`, + displayName + ) }) it('supports preserving data in state.profile', () => { const preservePath = 'displayName' const displayName = 'tester' initialData = { profile: { isEmpty: false, displayName } } - action = { type: actionTypes.LOGOUT, preserve: { profile: [preservePath] } } + action = { + type: actionTypes.LOGOUT, + preserve: { profile: [preservePath] } + } // load todos into state and confirm they are kept on logout - expect(firebaseReducer(initialData, action)) - .to.have.deep.property(`profile.${preservePath}`, displayName) + expect(firebaseReducer(initialData, action)).to.have.deep.property( + `profile.${preservePath}`, + displayName + ) }) it('throws for invalid preserve parameter', () => { @@ -386,8 +414,9 @@ describe('reducer', () => { initialData = { ordered: { todos } } action = { type: actionTypes.LOGOUT, preserve: 'ordered.todos' } // load todos into state and confirm they are kept on logout - expect(() => firebaseReducer(initialData, action)) - .to.throw('Invalid preserve parameter. It must be an Object or an Array') + expect(() => firebaseReducer(initialData, action)).to.throw( + 'Invalid preserve parameter. It must be an Object or an Array' + ) }) }) @@ -396,16 +425,8 @@ describe('reducer', () => { const auth = { some: 'value' } action = { type: actionTypes.LOGIN, auth } const currentState = firebaseReducer({}, action) - expect(currentState) - .to - .have - .deep - .property('auth.isLoaded', true) - expect(currentState) - .to - .have - .deep - .property('auth.isEmpty', false) + expect(currentState).to.have.deep.property('auth.isLoaded', true) + expect(currentState).to.have.deep.property('auth.isEmpty', false) }) // For details view https://github.com/prescottprue/react-redux-firebase/issues/301 @@ -413,25 +434,16 @@ describe('reducer', () => { const auth = { some: 'value' } action = { type: actionTypes.LOGIN, auth } const currentState = firebaseReducer({}, action) - expect(currentState) - .to - .have - .deep - .property('profile.isLoaded', false) - expect(currentState) - .to - .have - .deep - .property('profile.isEmpty', true) + expect(currentState).to.have.deep.property('profile.isLoaded', false) + expect(currentState).to.have.deep.property('profile.isEmpty', true) }) it('sets empty if auth not provided', () => { action = { type: actionTypes.LOGIN } - expect(firebaseReducer({}, action)) - .to - .have - .deep - .property('auth.isEmpty', true) + expect(firebaseReducer({}, action)).to.have.deep.property( + 'auth.isEmpty', + true + ) }) }) @@ -439,25 +451,39 @@ describe('reducer', () => { it('removes property from state', () => { const path = 'level0' action = { type: actionTypes.REMOVE, path } - const afterState = firebaseReducer({...initialState, data: testData}, action) + const afterState = firebaseReducer( + { ...initialState, data: testData }, + action + ) expect(afterState).to.not.have.deep.property('data.level0') }) it('removes parent properties from state if parent property is empty', () => { const path = 'level0/level1_item0/level2_item0/level3_item0' action = { type: actionTypes.REMOVE, path } - const afterState = firebaseReducer({...initialState, data: testData}, action) - expect(afterState).to.not.have.deep.property('data.level0.level1_item0.level2_item0') + const afterState = firebaseReducer( + { ...initialState, data: testData }, + action + ) + expect(afterState).to.not.have.deep.property( + 'data.level0.level1_item0.level2_item0' + ) }) it('does not remove parent property from state if parent has other children', () => { const path = 'level0/level1_item0' action = { type: actionTypes.REMOVE, path } - const afterState = firebaseReducer({...initialState, data: testData}, action) + const afterState = firebaseReducer( + { ...initialState, data: testData }, + action + ) expect(afterState).to.have.deep.property('data.level0.level1_item1') }) it('ordered state is untouched', () => { const path = 'level0/level1_item0/level2_item0/level3_item0' action = { type: actionTypes.REMOVE, path } - const afterState = firebaseReducer({...initialState, data: testData, ordered: testData}, action) + const afterState = firebaseReducer( + { ...initialState, data: testData, ordered: testData }, + action + ) expect(afterState.ordered).to.deep.equal(testData) }) }) @@ -465,21 +491,26 @@ describe('reducer', () => { describe('AUTH_EMPTY_CHANGE action -', () => { it('sets auth.isLoaded: true (matches v1 LOGOUT action)', () => { action = { type: actionTypes.AUTH_EMPTY_CHANGE } - expect(firebaseReducer({}, action)) - .to.have.deep.property('auth.isLoaded', true) + expect(firebaseReducer({}, action)).to.have.deep.property( + 'auth.isLoaded', + true + ) }) it('sets profile.isLoaded: true (matches v1 LOGOUT action)', () => { action = { type: actionTypes.AUTH_EMPTY_CHANGE } - expect(firebaseReducer({}, action)) - .to.have.deep.property('profile.isLoaded', true) + expect(firebaseReducer({}, action)).to.have.deep.property( + 'profile.isLoaded', + true + ) }) it('removes existing auth state', () => { const auth = { some: 'value', isLoaded: true, isEmpty: false } action = { type: actionTypes.AUTH_EMPTY_CHANGE } - expect(firebaseReducer({ auth }, action)) - .to.not.have.deep.property('auth.some') + expect(firebaseReducer({ auth }, action)).to.not.have.deep.property( + 'auth.some' + ) }) }) @@ -487,21 +518,26 @@ describe('reducer', () => { describe('dispatched with payload -', () => { it('sets auth.isEmpty: false', () => { action = { type: actionTypes.AUTH_LINK_SUCCESS, payload: { uid } } - expect(firebaseReducer({ auth: { isEmpty: true } }, action)) - .to.have.deep.property('auth.isEmpty', false) + expect( + firebaseReducer({ auth: { isEmpty: true } }, action) + ).to.have.deep.property('auth.isEmpty', false) }) it('sets auth data', () => { action = { type: actionTypes.AUTH_LINK_SUCCESS, payload: { uid } } - expect(firebaseReducer({}, action)) - .to.have.deep.property('auth.uid', uid) + expect(firebaseReducer({}, action)).to.have.deep.property( + 'auth.uid', + uid + ) }) it('calls .toJSON() if it exists', () => { const toJSON = sinon.spy(() => ({ email: 'test' })) action = { type: actionTypes.AUTH_LINK_SUCCESS, payload: { toJSON } } - expect(firebaseReducer({}, action)) - .to.have.deep.property('auth.email', 'test') + expect(firebaseReducer({}, action)).to.have.deep.property( + 'auth.email', + 'test' + ) expect(toJSON).to.have.been.calledOnce }) }) @@ -510,14 +546,16 @@ describe('reducer', () => { it('removes existing auth state', () => { const auth = { uid: 'value', isLoaded: true, isEmpty: false } action = { type: actionTypes.AUTH_LINK_SUCCESS } - expect(firebaseReducer({ auth }, action)) - .to.not.have.deep.property('auth.uid') + expect(firebaseReducer({ auth }, action)).to.not.have.deep.property( + 'auth.uid' + ) }) it('sets auth.isEmpty: true', () => { action = { type: actionTypes.AUTH_LINK_SUCCESS } - expect(firebaseReducer({ auth: { isEmpty: false } }, action)) - .to.have.deep.property('auth.isEmpty', true) + expect( + firebaseReducer({ auth: { isEmpty: false } }, action) + ).to.have.deep.property('auth.isEmpty', true) }) }) }) @@ -526,15 +564,12 @@ describe('reducer', () => { it('sets state', () => { const authError = { some: 'error' } expect( - firebaseReducer( - {}, - { type: actionTypes.LOGIN_ERROR, authError } - ) + firebaseReducer({}, { type: actionTypes.LOGIN_ERROR, authError }) ).to.deep.equal({ ...initialState, auth: { isLoaded: true, isEmpty: true }, profile: { isLoaded: false, isEmpty: true }, - errors: [ authError ], + errors: [authError], authError }) }) @@ -543,16 +578,20 @@ describe('reducer', () => { describe('AUTHENTICATION_INIT_STARTED action', () => { it('sets isInitializing to true', () => { action = { type: actionTypes.AUTHENTICATION_INIT_STARTED } - expect(firebaseReducer({}, action)) - .to.have.property('isInitializing', true) + expect(firebaseReducer({}, action)).to.have.property( + 'isInitializing', + true + ) }) }) describe('AUTHENTICATION_INIT_FINISHED action', () => { it('sets isInitializing to false', () => { action = { type: actionTypes.AUTHENTICATION_INIT_FINISHED } - expect(firebaseReducer({}, action)) - .to.have.property('isInitializing', false) + expect(firebaseReducer({}, action)).to.have.property( + 'isInitializing', + false + ) }) }) @@ -560,8 +599,11 @@ describe('reducer', () => { it('sets state', () => { const authError = { some: 'error' } action = { type: actionTypes.UNAUTHORIZED_ERROR, authError } - expect(firebaseReducer({}, action)) - .to.deep.equal({ ...initialState, errors: [authError], authError }) + expect(firebaseReducer({}, action)).to.deep.equal({ + ...initialState, + errors: [authError], + authError + }) }) }) @@ -570,22 +612,28 @@ describe('reducer', () => { it('sets auth.isLoaded: true', () => { const authUpdate = { email: 'newEmail' } action = { type: actionTypes.AUTH_UPDATE_SUCCESS, auth: authUpdate } - expect(firebaseReducer({}, action)) - .to.have.deep.property('auth.isLoaded', true) + expect(firebaseReducer({}, action)).to.have.deep.property( + 'auth.isLoaded', + true + ) }) it('sets data to auth state', () => { const authUpdate = { email: 'newEmail' } action = { type: actionTypes.AUTH_UPDATE_SUCCESS, auth: authUpdate } - expect(firebaseReducer({}, action)) - .to.have.deep.property('auth.email', authUpdate.email) + expect(firebaseReducer({}, action)).to.have.deep.property( + 'auth.email', + authUpdate.email + ) }) it('calls .toJSON() if it exists', () => { const toJSON = sinon.spy(() => ({ email: 'test' })) action = { type: actionTypes.AUTH_UPDATE_SUCCESS, auth: { toJSON } } - expect(firebaseReducer({}, action)) - .to.have.deep.property('auth.email', 'test') + expect(firebaseReducer({}, action)).to.have.deep.property( + 'auth.email', + 'test' + ) expect(toJSON).to.have.been.calledOnce }) }) @@ -593,52 +641,70 @@ describe('reducer', () => { describe('provided no auth - ', () => { it('sets auth.isEmpty: true', () => { action = { type: actionTypes.AUTH_UPDATE_SUCCESS } - expect(firebaseReducer({ auth: { isEmpty: false } }, action)) - .to.have.deep.property('auth.isEmpty', true) + expect( + firebaseReducer({ auth: { isEmpty: false } }, action) + ).to.have.deep.property('auth.isEmpty', true) }) }) }) describe('SET_LISTENER action', () => { it('sets id to allIds in listeners state', () => { - action = { type: actionTypes.SET_LISTENER, path: 'test', payload: { id: 'asdf' } } - expect(firebaseReducer({}, action)) - .to.have.deep.property('listeners.allIds.0', 'asdf') + action = { + type: actionTypes.SET_LISTENER, + path: 'test', + payload: { id: 'asdf' } + } + expect(firebaseReducer({}, action)).to.have.deep.property( + 'listeners.allIds.0', + 'asdf' + ) }) it('sets listener data to byId in listeners state', () => { const path = 'test' const id = 'asdf' action = { type: actionTypes.SET_LISTENER, path, payload: { id } } - expect(firebaseReducer({}, action)) - .to.have.deep.property(`listeners.byId.${id}.path`, path) + expect(firebaseReducer({}, action)).to.have.deep.property( + `listeners.byId.${id}.path`, + path + ) }) }) describe('CLEAR_ERRORS action', () => { it('clears errors', () => { action = { type: actionTypes.CLEAR_ERRORS } - expect(firebaseReducer({ errors: [{test: 'test'}] }, action).errors) - .to.have.a.lengthOf(0) + expect( + firebaseReducer({ errors: [{ test: 'test' }] }, action).errors + ).to.have.a.lengthOf(0) }) describe('preserve option', () => { it('passes through non object preserve parameters (backwards compatibility)', () => { action = { type: actionTypes.CLEAR_ERRORS, preserve: [] } - expect(firebaseReducer({ errors: [{test: 'test'}] }, action).errors) - .to.have.a.lengthOf(0) + expect( + firebaseReducer({ errors: [{ test: 'test' }] }, action).errors + ).to.have.a.lengthOf(0) }) it('supports perserving data through the perserve parameter', () => { - action = { type: actionTypes.CLEAR_ERRORS, preserve: { errors: () => true } } - expect(firebaseReducer({ errors: [{test: 'test'}] }, action).errors) - .to.have.a.lengthOf(1) + action = { + type: actionTypes.CLEAR_ERRORS, + preserve: { errors: () => true } + } + expect( + firebaseReducer({ errors: [{ test: 'test' }] }, action).errors + ).to.have.a.lengthOf(1) }) it('throws for non function preserve values', () => { action = { type: actionTypes.CLEAR_ERRORS, preserve: { errors: true } } - expect(() => firebaseReducer({ errors: [{test: 'test'}] }, action)) - .to.Throw('Preserve for the errors state currently only supports functions') + expect(() => + firebaseReducer({ errors: [{ test: 'test' }] }, action) + ).to.Throw( + 'Preserve for the errors state currently only supports functions' + ) }) }) }) @@ -650,9 +716,15 @@ describe('reducer', () => { initialData = { listeners: { allIds: [id] } } - action = { type: actionTypes.UNSET_LISTENER, path, payload: { id: 'asdf' } } - expect(firebaseReducer(initialData, action).listeners) - .to.deep.equal({ allIds: [], byId: {} }) + action = { + type: actionTypes.UNSET_LISTENER, + path, + payload: { id: 'asdf' } + } + expect(firebaseReducer(initialData, action).listeners).to.deep.equal({ + allIds: [], + byId: {} + }) }) it('removes id from byId in listeners state', () => { @@ -662,8 +734,10 @@ describe('reducer', () => { initialData = { listeners: { byId: { [id]: { id, path } } } } - expect(firebaseReducer(initialData, action).listeners) - .to.deep.equal({ allIds: [], byId: {} }) + expect(firebaseReducer(initialData, action).listeners).to.deep.equal({ + allIds: [], + byId: {} + }) }) }) }) diff --git a/test/unit/utils/actions.spec.js b/test/unit/utils/actions.spec.js index 158f948c6..9759c24f6 100644 --- a/test/unit/utils/actions.spec.js +++ b/test/unit/utils/actions.spec.js @@ -8,12 +8,18 @@ describe('Utils: Auth', () => { // Skipped due to capatalize and auth provider function it('creates valid Auth Provider', () => { // TODO: Check that dispatch is called with actions - expect(wrapInDispatch(dispatch, { method, args: [ 'arg1' ], types: [ 'ACTION' ] })) - .to.be.fulfilled + expect( + wrapInDispatch(dispatch, { method, args: ['arg1'], types: ['ACTION'] }) + ).to.be.fulfilled }) it('handles string list of scopes', () => { - expect(wrapInDispatch(dispatch, { method: failMethod, args: [ 'arg1' ], types: [ 'ACTION' ] })) - .to.be.rejectedWith('Failed') + expect( + wrapInDispatch(dispatch, { + method: failMethod, + args: ['arg1'], + types: ['ACTION'] + }) + ).to.be.rejectedWith('Failed') }) }) }) diff --git a/test/unit/utils/auth.spec.js b/test/unit/utils/auth.spec.js index cf73307d0..3113f3aec 100644 --- a/test/unit/utils/auth.spec.js +++ b/test/unit/utils/auth.spec.js @@ -4,33 +4,44 @@ describe('Utils: Auth', () => { describe('getLoginMethodAndParams', () => { it('throws for invalid provider', () => { const provider = 'asdf' - expect(() => getLoginMethodAndParams(firebase, { provider: 'asdf' })) - .to.Throw(Error, `${provider} is not a valid Auth Provider`) + expect(() => + getLoginMethodAndParams(firebase, { provider: 'asdf' }) + ).to.Throw(Error, `${provider} is not a valid Auth Provider`) }) it('google provider', () => { - expect(getLoginMethodAndParams(firebase, { provider: 'google' })) - .to.include.keys('method') + expect( + getLoginMethodAndParams(firebase, { provider: 'google' }) + ).to.include.keys('method') }) it('twitter provider', () => { // TODO: Confirm that addScope - expect(getLoginMethodAndParams(firebase, { provider: 'twitter' })) - .to.include.keys('method') + expect( + getLoginMethodAndParams(firebase, { provider: 'twitter' }) + ).to.include.keys('method') }) it('token', () => { - expect(getLoginMethodAndParams(firebase, { token: 'asdf' })) - .to.include.keys('method') + expect( + getLoginMethodAndParams(firebase, { token: 'asdf' }) + ).to.include.keys('method') }) it('throws for token with provider: ', () => { - expect(() => getLoginMethodAndParams(firebase, { provider: 'google', token: 'asdf' })) - .to.Throw('provider with token no longer supported, use credential parameter instead') + expect(() => + getLoginMethodAndParams(firebase, { provider: 'google', token: 'asdf' }) + ).to.Throw( + 'provider with token no longer supported, use credential parameter instead' + ) }) it('credential', () => { - expect(getLoginMethodAndParams(firebase, { provider: 'google', credential: 'asdf' })) - .to.include.keys('method') + expect( + getLoginMethodAndParams(firebase, { + provider: 'google', + credential: 'asdf' + }) + ).to.include.keys('method') }) it('popup', () => { @@ -52,9 +63,14 @@ describe('Utils: Auth', () => { }) it('handles customAuthParameters config option', () => { - firebase._.config.customAuthParameters = { google: [{prompt: 'select_account'}] } + firebase._.config.customAuthParameters = { + google: [{ prompt: 'select_account' }] + } // spy = sinon.spy(firebase, 'auth.GoogleAuthProvider') - const { method } = getLoginMethodAndParams(firebase, { provider: 'google', scopes: ['some'] }) + const { method } = getLoginMethodAndParams(firebase, { + provider: 'google', + scopes: ['some'] + }) expect(method).to.equal('signInWithRedirect') }) }) diff --git a/test/unit/utils/events.spec.js b/test/unit/utils/events.spec.js index 8d0314d35..a2ad1fc5a 100644 --- a/test/unit/utils/events.spec.js +++ b/test/unit/utils/events.spec.js @@ -17,15 +17,23 @@ describe('Utils: Events', () => { describe('accepts object', () => { it('that is valid', () => { - expect(getEventsFromInput([{path: 'some'}])[0]).to.include.keys('path') + expect(getEventsFromInput([{ path: 'some' }])[0]).to.include.keys( + 'path' + ) }) it('that has queryParams', () => { - expect(getEventsFromInput([{path: 'some', queryParams: ['orderByChild=uid']}])[0]).to.include.keys('path') + expect( + getEventsFromInput([ + { path: 'some', queryParams: ['orderByChild=uid'] } + ])[0] + ).to.include.keys('path') }) it('that is invalid', () => { - expect(() => getEventsFromInput([{type: 'value'}])).to.throw('Path is a required parameter within definition object') + expect(() => getEventsFromInput([{ type: 'value' }])).to.throw( + 'Path is a required parameter within definition object' + ) }) }) @@ -36,7 +44,9 @@ describe('Utils: Events', () => { describe('populate', () => { it('populates parameter set populates exist', () => { - expect(getEventsFromInput(['some#populate=uid:users'])[0]).to.include.keys('populates') + expect( + getEventsFromInput(['some#populate=uid:users'])[0] + ).to.include.keys('populates') }) it('populates parameter not set if none exists', () => { diff --git a/test/unit/utils/populate.spec.js b/test/unit/utils/populate.spec.js index 5ff9822fa..3f17e1e19 100644 --- a/test/unit/utils/populate.spec.js +++ b/test/unit/utils/populate.spec.js @@ -56,52 +56,86 @@ describe('Utils: Populate', () => { describe('getPopulateChild', () => { it('gets child', () => { - expect(getPopulateChild(Firebase, {child: 'uid', root: 'users'}, '123123')) - .to.be.fulfilled + expect( + getPopulateChild(Firebase, { child: 'uid', root: 'users' }, '123123') + ).to.be.fulfilled }) }) describe('promisesForPopulate', () => { it('handles non-existant single child', async () => { - populates = [{child: 'random', root: 'users'}] - res = await promisesForPopulate(Firebase, '', { uid: '123123' }, populates) + populates = [{ child: 'random', root: 'users' }] + res = await promisesForPopulate( + Firebase, + '', + { uid: '123123' }, + populates + ) expect(Object.keys(res)).to.have.length(0) }) it('populates single property containing a single item', async () => { - populates = [{child: 'uid', root: 'users'}] + populates = [{ child: 'uid', root: 'users' }] res = await promisesForPopulate(Firebase, '', { uid }, populates) expect(res).to.have.deep.property(`users.${uid}`) }) it('populates single property containing a list', async () => { - populates = [{child: 'collaborators', root: 'users'}] - res = await promisesForPopulate(Firebase, '', { collaborators: { [uid]: true, 'ABC123': true } }, populates) + populates = [{ child: 'collaborators', root: 'users' }] + res = await promisesForPopulate( + Firebase, + '', + { collaborators: { [uid]: true, ABC123: true } }, + populates + ) expect(res).to.have.deep.property(`users.${uid}`) }) it('populates all existing children even if one populates child does not exist', async () => { - populates = [{child: 'collaborators', root: 'users'}, {child: 'nonExistantKey', root: 'users'}] - res = await promisesForPopulate(Firebase, '', { collaborators: { [uid]: true, 'ABC123': true } }, populates) + populates = [ + { child: 'collaborators', root: 'users' }, + { child: 'nonExistantKey', root: 'users' } + ] + res = await promisesForPopulate( + Firebase, + '', + { collaborators: { [uid]: true, ABC123: true } }, + populates + ) expect(res).to.have.deep.property(`users.${uid}`) }) describe('populates list', () => { it('with single property populate', async () => { - populates = [{child: 'owner', root: 'users'}] - res = await promisesForPopulate(Firebase, '', { 1: { owner: uid } }, populates) + populates = [{ child: 'owner', root: 'users' }] + res = await promisesForPopulate( + Firebase, + '', + { 1: { owner: uid } }, + populates + ) expect(res).to.have.deep.property(`users.${uid}`) }) it('with property containing array property', async () => { - populates = [{child: 'collaborators', root: 'users'}] - res = await promisesForPopulate(Firebase, '', { 1: { collaborators: [uid, 'ABC123'] } }, populates) + populates = [{ child: 'collaborators', root: 'users' }] + res = await promisesForPopulate( + Firebase, + '', + { 1: { collaborators: [uid, 'ABC123'] } }, + populates + ) expect(res).to.have.deep.property(`users.${uid}`) }) it('with property containing key/true list', async () => { - populates = [{child: 'collaborators', root: 'users'}] - res = await promisesForPopulate(Firebase, '', { 1: { collaborators: { [uid]: true, 'ABC123': true } } }, populates) + populates = [{ child: 'collaborators', root: 'users' }] + res = await promisesForPopulate( + Firebase, + '', + { 1: { collaborators: { [uid]: true, ABC123: true } } }, + populates + ) expect(res).to.have.deep.property(`users.${uid}`) }) }) diff --git a/test/unit/utils/query.spec.js b/test/unit/utils/query.spec.js index 3affef3f0..c5af8e8a7 100644 --- a/test/unit/utils/query.spec.js +++ b/test/unit/utils/query.spec.js @@ -10,7 +10,7 @@ import { } from '../../../src/utils/query' import { fakeFirebase } from '../../utils' -let createQueryFromParams = (queryParams) => +let createQueryFromParams = queryParams => applyParamsToQuery(queryParams, fakeFirebase.database().ref()) const dispatch = () => {} @@ -22,12 +22,14 @@ describe('Utils: Query', () => { expect(getWatchPath('once', '/todos')).to.be.a.string }) it('throws for no event', () => { - expect(() => getWatchPath(null, '/todos')) - .to.throw('Event and path are required') + expect(() => getWatchPath(null, '/todos')).to.throw( + 'Event and path are required' + ) }) it('throws for no path', () => { - expect(() => getWatchPath(null, null)) - .to.throw('Event and path are required') + expect(() => getWatchPath(null, null)).to.throw( + 'Event and path are required' + ) }) }) @@ -100,15 +102,20 @@ describe('Utils: Query', () => { describe('orderByPriority', () => { it('handles single parameter', () => { - expect(createQueryFromParams(['orderByPriority']).toString()) - .to.equal('priority') + expect(createQueryFromParams(['orderByPriority']).toString()).to.equal( + 'priority' + ) }) describe('with startAt', () => { it('string containing number', () => { const startAt = '123abc' - expect(createQueryFromParams(['orderByPriority', `startAt=${startAt}`]).toString()) - .to.equal(startAt) + expect( + createQueryFromParams([ + 'orderByPriority', + `startAt=${startAt}` + ]).toString() + ).to.equal(startAt) }) }) }) @@ -127,68 +134,63 @@ describe('Utils: Query', () => { it('number', () => { const child = 'emailAddress' const equalTo = 1 - const queryParams = createQueryFromParams([`orderByChild=${child}`, `equalTo=${equalTo}`]) - expect(queryParams.child) - .to - .equal(child) - expect(queryParams.equalTo) - .to - .equal(equalTo) + const queryParams = createQueryFromParams([ + `orderByChild=${child}`, + `equalTo=${equalTo}` + ]) + expect(queryParams.child).to.equal(child) + expect(queryParams.equalTo).to.equal(equalTo) }) it('boolean', () => { const child = 'completed' const equalTo = false - const queryParams = createQueryFromParams([`orderByChild=${child}`, `equalTo=${equalTo}`]) - expect(queryParams.child) - .to - .equal(child) - expect(queryParams.equalTo) - .to - .equal(equalTo) + const queryParams = createQueryFromParams([ + `orderByChild=${child}`, + `equalTo=${equalTo}` + ]) + expect(queryParams.child).to.equal(child) + expect(queryParams.equalTo).to.equal(equalTo) }) it('string containing a boolean', () => { const child = 'emailAddress' const equalTo = 'true' - const queryParams = createQueryFromParams([`orderByChild=${child}`, `equalTo=${equalTo}`]) - expect(queryParams.child) - .to - .equal(child) - expect(queryParams.equalTo) - .to - .equal(true) + const queryParams = createQueryFromParams([ + `orderByChild=${child}`, + `equalTo=${equalTo}` + ]) + expect(queryParams.child).to.equal(child) + expect(queryParams.equalTo).to.equal(true) }) it('string containing null', () => { const child = 'emailAddress' const equalTo = 'null' - const queryParams = createQueryFromParams([`orderByChild=${child}`, `equalTo=${equalTo}`]) - expect(queryParams.child) - .to - .equal(child) - expect(queryParams.equalTo) - .to - .equal(null) + const queryParams = createQueryFromParams([ + `orderByChild=${child}`, + `equalTo=${equalTo}` + ]) + expect(queryParams.child).to.equal(child) + expect(queryParams.equalTo).to.equal(null) }) it('string containing a number', () => { const child = 'emailAddress' const equalTo = '123example@gmail.com' - const queryParams = createQueryFromParams([`orderByChild=${child}`, `equalTo=${equalTo}`]) - expect(queryParams.child) - .to - .equal(child) - expect(queryParams.equalTo) - .to - .equal(equalTo) + const queryParams = createQueryFromParams([ + `orderByChild=${child}`, + `equalTo=${equalTo}` + ]) + expect(queryParams.child).to.equal(child) + expect(queryParams.equalTo).to.equal(equalTo) }) it('does not parse if notParsed parameter passed', () => { const child = 'emailAddress' const equalTo = '123' - const queryParams = createQueryFromParams([`orderByChild=${child}`, 'notParsed', `equalTo=${equalTo}`]) - expect(queryParams.child) - .to - .equal(child) - expect(queryParams.equalTo) - .to - .equal(equalTo) + const queryParams = createQueryFromParams([ + `orderByChild=${child}`, + 'notParsed', + `equalTo=${equalTo}` + ]) + expect(queryParams.child).to.equal(child) + expect(queryParams.equalTo).to.equal(equalTo) }) }) }) @@ -213,8 +215,9 @@ describe('Utils: Query', () => { describe('orderedFromSnapshot -', () => { it('returns null if hasChildren is a function and is false', () => { const hasChildrenSpy = sinon.spy(() => false) - expect(orderedFromSnapshot({ hasChildren: hasChildrenSpy })) - .to.equal(null) + expect(orderedFromSnapshot({ hasChildren: hasChildrenSpy })).to.equal( + null + ) expect(hasChildrenSpy).to.have.been.calledOnce }) @@ -225,12 +228,12 @@ describe('Utils: Query', () => { }) it('returns null if ordered is an empty array', () => { - expect(orderedFromSnapshot({ forEach: () => ({ }) })).to.equal(null) + expect(orderedFromSnapshot({ forEach: () => ({}) })).to.equal(null) }) it('adds children to ordered if they exist', () => { - const child = { key: 'some', val: () => ({ }) } - const forEachSpy = sinon.spy((childFunc) => childFunc(child)) + const child = { key: 'some', val: () => ({}) } + const forEachSpy = sinon.spy(childFunc => childFunc(child)) const res = orderedFromSnapshot({ forEach: forEachSpy }) expect(res).to.be.an('array') expect(forEachSpy).to.have.been.calledOnce @@ -244,8 +247,11 @@ describe('Utils: Query', () => { }) it('returns populated results on', () => { - expect(populateAndDispatch(firebase, () => ({}), { snapshot: { key: 'test123' } })) - .to.be.an.object + expect( + populateAndDispatch(firebase, () => ({}), { + snapshot: { key: 'test123' } + }) + ).to.be.an.object }) // // TODO: Get this working diff --git a/test/unit/utils/storage.spec.js b/test/unit/utils/storage.spec.js index 8eb4f4d0f..664cc0e8d 100644 --- a/test/unit/utils/storage.spec.js +++ b/test/unit/utils/storage.spec.js @@ -4,12 +4,12 @@ import { fakeFirebase } from '../../utils' describe('Utils: Storage', () => { describe('deleteFile', () => { it('returns dbPath', () => - expect(deleteFile(fakeFirebase, { path: 'some', dbPath: 'some' })) - .to.eventually.have.keys(['path', 'dbPath']) - ) + expect( + deleteFile(fakeFirebase, { path: 'some', dbPath: 'some' }) + ).to.eventually.have.keys(['path', 'dbPath'])) it('returns dbPath', () => - expect(deleteFile(fakeFirebase, { path: 'some' })) - .to.eventually.have.keys('path') - ) + expect( + deleteFile(fakeFirebase, { path: 'some' }) + ).to.eventually.have.keys('path')) }) }) diff --git a/test/unit/withFirebase.spec.js b/test/unit/withFirebase.spec.js index c849b5012..edb6073dd 100644 --- a/test/unit/withFirebase.spec.js +++ b/test/unit/withFirebase.spec.js @@ -26,10 +26,14 @@ describe('withFirebase', () => { }) describe('sets displayName static as', () => { - describe('withFirebase(${WrappedComponentName}) for', () => { // eslint-disable-line no-template-curly-in-string + /* eslint-disable no-template-curly-in-string */ + describe('withFirebase(${WrappedComponentName}) for', () => { + /* eslint-enable no-template-curly-in-string */ it.skip('standard components', () => { wrapper = shallow(, { context: { store } }) - expect(wrapper.instance.displayName).to.equal(`withFirebase(TestContainer)`) + expect(wrapper.instance.displayName).to.equal( + `withFirebase(TestContainer)` + ) }) it('string components', () => { diff --git a/test/unit/withFirestore.spec.js b/test/unit/withFirestore.spec.js index 86dde83a9..75e9b5f90 100644 --- a/test/unit/withFirestore.spec.js +++ b/test/unit/withFirestore.spec.js @@ -31,10 +31,14 @@ describe('withFirestore', () => { }) describe('sets displayName static as', () => { - describe('withFirestore(${WrappedComponentName}) for', () => { // eslint-disable-line no-template-curly-in-string + /* eslint-disable no-template-curly-in-string */ + describe('withFirestore(${WrappedComponentName}) for', () => { + /* eslint-enable no-template-curly-in-string */ it.skip('standard components', () => { wrapper = shallow(, { context: { store } }) - expect(wrapper.instance.displayName).to.equal(`withFirestore(TestContainer)`) + expect(wrapper.instance.displayName).to.equal( + `withFirestore(TestContainer)` + ) }) it('string components', () => { diff --git a/test/utils.js b/test/utils.js index 10953e7b4..3481d70c5 100644 --- a/test/utils.js +++ b/test/utils.js @@ -9,7 +9,9 @@ export const storeWithFirebase = () => { const createStoreWithMiddleware = compose( reactReduxFirebase(Firebase, { userProfile: 'users' }) )(createStore) - return createStoreWithMiddleware(combineReducers({ test: (state = {}) => state })) + return createStoreWithMiddleware( + combineReducers({ test: (state = {}) => state }) + ) } export const storeWithFirestore = () => { @@ -19,7 +21,10 @@ export const storeWithFirestore = () => { )(createStore) return { ...createStoreWithMiddleware( - combineReducers({ test: (state = {}) => state, firestore: firestoreReducer }) + combineReducers({ + test: (state = {}) => state, + firestore: firestoreReducer + }) ), firestore: { add: () => ({}) } } @@ -29,18 +34,19 @@ export const TestContainer = () => createSink() export const Container = () =>
export class ProviderMock extends Component { - getChildContext () { + getChildContext() { return { store: this.props.store } } state = { test: null, dynamic: '' } - render () { + render() { return Children.only( cloneElement(this.props.children, { testProp: this.state.test, dynamicProp: this.state.dynamic - })) + }) + ) } } @@ -53,8 +59,8 @@ ProviderMock.propTypes = { children: PropTypes.node } -export const onAuthStateChangedSpy = sinon.spy((f) => { - f({uid: 'asdfasdf'}) +export const onAuthStateChangedSpy = sinon.spy(f => { + f({ uid: 'asdfasdf' }) }) export const firebaseWithConfig = (config = {}) => ({ @@ -76,7 +82,7 @@ export const fakeFirebase = { database: () => ({ ref: () => ({ val: () => ({ some: 'obj' }), - remove: () => Promise.resolve({ }), + remove: () => Promise.resolve({}), child: () => ({ on: () => ({ val: () => ({ some: 'obj' }) }), off: () => Promise.resolve({ val: () => ({ some: 'obj' }) }), @@ -88,45 +94,50 @@ export const fakeFirebase = { once: () => Promise.resolve({ val: () => ({ some: 'obj' }) }) }), orderByPriority: () => ({ - startAt: (startParam) => startParam, + startAt: startParam => startParam, toString: () => 'priority' }), - orderByChild: (child) => ({ - equalTo: (equalTo) => ({ + orderByChild: child => ({ + equalTo: equalTo => ({ child, equalTo }), toString: () => child }), - orderByKey: () => ({ }), - limitToFirst: () => ({ }), - limitToLast: () => ({ }), - equalTo: () => ({ }), - startAt: () => ({ }), - endAt: () => ({ }) + orderByKey: () => ({}), + limitToFirst: () => ({}), + limitToLast: () => ({}), + equalTo: () => ({}), + startAt: () => ({}), + endAt: () => ({}) }), - update: () => Promise.resolve({ - val: () => ({ some: 'obj' }) - }) + update: () => + Promise.resolve({ + val: () => ({ some: 'obj' }) + }) }), auth: () => ({ onAuthStateChanged: onAuthStateChangedSpy, - getRedirectResult: (f) => { - return Promise.resolve({uid: 'asdfasdf'}) + getRedirectResult: f => { + return Promise.resolve({ uid: 'asdfasdf' }) }, - signOut: () => - Promise.resolve({}), + signOut: () => Promise.resolve({}), createUserWithEmailAndPassword: (email, password) => email.indexOf('error') !== -1 ? Promise.reject(new Error('auth/user-not-found')) : email === 'error' ? Promise.reject(new Error('asdfasdf')) - : Promise.resolve({ uid: '123', email: 'test@test.com', providerData: [{}] }), + : 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' + accessToken: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjbGFpbXMiOnsiZGlzcGxheU5hbWUiOiJFZHdhcmQgV3UiLCJlbWFpbCI6ImVkZGlld3U4MEBnbWFpbC5jb20iLCJvcmlnaW5hbElkIjoiSlFYQ2dRTkxEU1lSMFEzdjlwY3FjTDZJeGRUMiIsInByb3ZpZGVyRGF0YSI6W3siZGlzcGxheU5hbWUiOiJFZHdhcmQgV3UiLCJlbWFpbCI6ImVkZGlld3U4MEBnbWFpbC5jb20ifV19LCJ1aWQiOiJqaTh3c1BDVW5PYUhvUGZBalNCS2ZReU1pTmkxIiwiaWF0IjoxNDc1NDM3MDMyLCJleHAiOjE0NzU0NDA2MzIsImF1ZCI6Imh0dHBzOi8vaWRlbnRpdHl0b29sa2l0Lmdvb2dsZWFwaXMuY29tL2dvb2dsZS5pZGVudGl0eS5pZGVudGl0eXRvb2xraXQudjEuSWRlbnRpdHlUb29sa2l0IiwiaXNzIjoicmVzaWRlLXByb2RAcmVzaWRlLXByb2QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiJyZXNpZGUtcHJvZEByZXNpZGUtcHJvZC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSJ9.aOXOCCAL-lI5AVnd8MVlc86exvCGNySq8X7DM4Gr7PG0ek5mh_8qnFfLuzw2gfv6mHNVgY2UngUmG0qETaBdox7l3wBo1GdP9hB1bM8NltCYffXwxyw7sN36BFWD3l-cz4rlxfmfzosCLj3XtDK8ARDQ76pAXxsK-rRBvMG6N-wgE_ZLf17FVvwB95e1DAmL39fp6dRVxoPflG--m4QEKVk8xIeDx4ol9HJw512gMGtTkRDMEPWVJEdaEAp6L6Lo2-Bk-TxBCHs8gpb7b7eidWMUEXObk0UPQIz2DRh-3olbruimzL_SgPNg4Pz0uUYSn11-Mx_HxxiVtyQj1ufoLA' }, uid: 'asdfasdfsdf' }) @@ -137,8 +148,12 @@ export const fakeFirebase = { ? Promise.reject(new Error('asdfasdf')) : email === 'error3' ? Promise.reject(new Error('auth/user-not-found')) - : Promise.resolve({ uid: '123', email: 'test@test.com', providerData: [{}] }), - sendPasswordResetEmail: (email) => + : Promise.resolve({ + uid: '123', + email: 'test@test.com', + providerData: [{}] + }), + sendPasswordResetEmail: email => email === 'error' ? Promise.reject({ code: 'auth/user-not-found' }) // eslint-disable-line prefer-promise-reject-errors : email === 'error2' @@ -148,17 +163,22 @@ export const fakeFirebase = { password === 'error' ? Promise.reject({ code: code }) // eslint-disable-line prefer-promise-reject-errors : Promise.resolve(), - verifyPasswordResetCode: (code) => code === 'error' - ? Promise.reject(new Error('some')) - ? Promise.reject({ code: 'asdfasdf' }) // eslint-disable-line prefer-promise-reject-errors - : Promise.resolve({ uid: '123', email: 'test@test.com', providerData: [{}] }) - : Promise.resolve('success') + verifyPasswordResetCode: code => + code === 'error' + ? Promise.reject(new Error('some')) + ? Promise.reject({ code: 'asdfasdf' }) // eslint-disable-line prefer-promise-reject-errors + : Promise.resolve({ + uid: '123', + email: 'test@test.com', + providerData: [{}] + }) + : Promise.resolve('success') }), storage: () => ({ ref: () => ({ put: () => ({ on: (event, funcsObj) => { - funcsObj.next({bytesTransferred: 12, totalBytes: 12}) + funcsObj.next({ bytesTransferred: 12, totalBytes: 12 }) funcsObj.error() funcsObj.complete() return sinon.spy() From bdb754c823a1c1f26b2abc1c7a103b944a389775 Mon Sep 17 00:00:00 2001 From: prescottprue Date: Sun, 21 Jan 2018 02:33:52 -0800 Subject: [PATCH 4/4] scopes parameter added to auth docs - #380 --- docs/api/constants.md | 4 + docs/auth.md | 6 +- package-lock.json | 204 +++++++++++++++++++++--------------------- src/constants.js | 4 + 4 files changed, 112 insertions(+), 106 deletions(-) diff --git a/docs/api/constants.md b/docs/api/constants.md index 5841113e9..160a06e42 100644 --- a/docs/api/constants.md +++ b/docs/api/constants.md @@ -139,3 +139,7 @@ Default configuration options - `firestoreNamespace` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** `firestoreHelpers` Namespace for firestore helpers (**WARNING** Changing this will break firestoreConnect HOC. Do **NOT** change to `'firestore'`) +- `keysToRemoveFromAuth` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** (default at end) + list of keys to remove from authentication reponse before writing to profile + (currenlty only used for profiles stored on Firestore). `['appName', 'apiKey' + , 'authDomain', 'redirectEventId', 'stsTokenManager', 'uid']` diff --git a/docs/auth.md b/docs/auth.md index 55166fbbd..86531bcfc 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -63,7 +63,8 @@ export default firebaseConnect()(SomeComponent) // or withFirebase(SomeComponent ```js { provider: "facebook | google | twitter", - type: "popup | redirect" // popup is default + type: "popup | redirect", // popup is default + scopes: ['email'] // email is default } ``` * credential (runs `ref.signInWithCredential(credential)`) : @@ -122,7 +123,8 @@ props.firebase.login({ ```js props.firebase.login({ provider: 'google', - type: 'popup' + type: 'popup', + // scopes: ['email'] // not required }) ``` diff --git a/package-lock.json b/package-lock.json index 98e637ec8..1ee6ed8f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1924,7 +1924,8 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true }, "boom": { "version": "2.10.1", @@ -2330,6 +2331,7 @@ "version": "0.22.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "dev": true, "requires": { "css-select": "1.2.0", "dom-serializer": "0.1.0", @@ -2482,17 +2484,6 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, - "clipboard": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", - "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=", - "optional": true, - "requires": { - "good-listener": "1.2.2", - "select": "1.1.2", - "tiny-emitter": "2.0.2" - } - }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -2729,7 +2720,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "create-ecdh": { "version": "4.0.0", @@ -2843,6 +2835,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, "requires": { "boolbase": "1.0.0", "css-what": "2.1.0", @@ -2853,7 +2846,8 @@ "css-what": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true }, "cssmin": { "version": "0.3.2", @@ -3044,12 +3038,6 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, - "delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=", - "optional": true - }, "depd": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", @@ -3537,6 +3525,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, "requires": { "domelementtype": "1.1.3", "entities": "1.1.1" @@ -3545,7 +3534,8 @@ "domelementtype": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true } } }, @@ -3564,7 +3554,8 @@ "domelementtype": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true }, "domexception": { "version": "1.0.0", @@ -3576,6 +3567,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "dev": true, "requires": { "domelementtype": "1.3.0" } @@ -3584,6 +3576,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, "requires": { "dom-serializer": "0.1.0", "domelementtype": "1.3.0" @@ -3724,7 +3717,8 @@ "entities": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true }, "enzyme": { "version": "2.9.1", @@ -4036,6 +4030,15 @@ } } }, + "eslint-config-prettier": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.9.0.tgz", + "integrity": "sha512-ag8YEyBXsm3nmOv1Hz991VtNNDMRa+MNy8cY47Pl4bw6iuzqKbJajXdqUpiw13STdLLrznxgm1hj9NhxeOYq0A==", + "dev": true, + "requires": { + "get-stdin": "5.0.1" + } + }, "eslint-config-standard": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", @@ -4220,6 +4223,16 @@ } } }, + "eslint-plugin-prettier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.5.0.tgz", + "integrity": "sha512-L06bewYpt2Wb8Uk7os8f/0cL5DjddL38t1M/nOpjw5MqVFBn1RIIBBE6tfr37lHUH7AvAubZsvu/bDmNl4RBKQ==", + "dev": true, + "requires": { + "fast-diff": "1.1.2", + "jest-docblock": "21.2.0" + } + }, "eslint-plugin-promise": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.6.0.tgz", @@ -4496,6 +4509,12 @@ "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", "dev": true }, + "fast-diff": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", + "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -6173,6 +6192,12 @@ "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", "dev": true }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true + }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -6272,41 +6297,6 @@ } } }, - "gitbook-plugin-anchorjs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/gitbook-plugin-anchorjs/-/gitbook-plugin-anchorjs-1.1.1.tgz", - "integrity": "sha1-co4Ecbp0J/zeIUkifCn+Fyte7N0=" - }, - "gitbook-plugin-edit-link": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gitbook-plugin-edit-link/-/gitbook-plugin-edit-link-2.0.2.tgz", - "integrity": "sha1-2PzZJ+ztgeemYqctWdtgnq/X5y8=" - }, - "gitbook-plugin-ga": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gitbook-plugin-ga/-/gitbook-plugin-ga-1.0.1.tgz", - "integrity": "sha1-yF17jAFkDEuz3DsjGrn+dKpuUhs=" - }, - "gitbook-plugin-github": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gitbook-plugin-github/-/gitbook-plugin-github-2.0.0.tgz", - "integrity": "sha1-UWbnY8/MQC1DKIC3pshcHFS1ao0=" - }, - "gitbook-plugin-prism": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gitbook-plugin-prism/-/gitbook-plugin-prism-2.3.0.tgz", - "integrity": "sha1-aGN9VP/lZQAt0QwwuqG0M7NewvI=", - "requires": { - "cheerio": "0.22.0", - "mkdirp": "0.5.1", - "prismjs": "1.6.0" - } - }, - "gitbook-plugin-versions-select": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/gitbook-plugin-versions-select/-/gitbook-plugin-versions-select-0.1.1.tgz", - "integrity": "sha1-JyOtRpIkTdvHlUCg5EX0XuJJeQk=" - }, "github-slugger": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.0.tgz", @@ -6434,15 +6424,6 @@ } } }, - "good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", - "optional": true, - "requires": { - "delegate": "3.2.0" - } - }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -7697,6 +7678,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, "requires": { "domelementtype": "1.3.0", "domhandler": "2.4.1", @@ -7791,7 +7773,8 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true }, "ini": { "version": "1.3.5", @@ -8309,7 +8292,8 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", @@ -8479,6 +8463,12 @@ "handlebars": "4.0.11" } }, + "jest-docblock": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", + "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==", + "dev": true + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -9019,12 +9009,14 @@ "lodash.assignin": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", + "dev": true }, "lodash.bind": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", + "dev": true }, "lodash.cond": { "version": "4.5.2", @@ -9046,22 +9038,26 @@ "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true }, "lodash.filter": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", + "dev": true }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true }, "lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", + "dev": true }, "lodash.get": { "version": "4.4.2", @@ -9095,27 +9091,32 @@ "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true }, "lodash.merge": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.0.tgz", - "integrity": "sha1-aYhLoUSsM/5plzemCG3v+t0PicU=" + "integrity": "sha1-aYhLoUSsM/5plzemCG3v+t0PicU=", + "dev": true }, "lodash.pick": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", + "dev": true }, "lodash.reduce": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", + "dev": true }, "lodash.reject": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", + "dev": true }, "lodash.set": { "version": "4.3.2", @@ -9126,7 +9127,8 @@ "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "dev": true }, "lodash.sortby": { "version": "4.7.0", @@ -9432,7 +9434,8 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true }, "mixin-deep": { "version": "1.3.0", @@ -9459,6 +9462,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" } @@ -14788,6 +14792,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, "requires": { "boolbase": "1.0.0" } @@ -15384,13 +15389,11 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, - "prismjs": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.6.0.tgz", - "integrity": "sha1-EY2V+3pm26InLjQ7NF9SNmWds2U=", - "requires": { - "clipboard": "1.7.1" - } + "prettier": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.10.2.tgz", + "integrity": "sha512-TcdNoQIWFoHblurqqU6d1ysopjq7UX0oRcT/hJ8qvBAELiYWn+Ugf0AXdnzISEJ7vuhNnQ98N8jR8Sh53x4IZg==", + "dev": true }, "private": { "version": "0.1.8", @@ -15407,7 +15410,8 @@ "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true }, "progress": { "version": "2.0.0", @@ -15789,6 +15793,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", @@ -16278,7 +16283,8 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true }, "safe-json-parse": { "version": "1.0.1", @@ -16298,12 +16304,6 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, - "select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", - "optional": true - }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", @@ -16885,6 +16885,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, "requires": { "safe-buffer": "5.1.1" } @@ -17316,12 +17317,6 @@ "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=", "dev": true }, - "tiny-emitter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", - "integrity": "sha1-gtJ0aKylrejl/R5tIrV91D69+3w=", - "optional": true - }, "tiny-lr": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.0.5.tgz", @@ -17955,7 +17950,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "util-extend": { "version": "1.0.3", diff --git a/src/constants.js b/src/constants.js index feb66a50d..482629c1f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -165,6 +165,10 @@ export const actionTypes = { * @property {Boolean} firestoreNamespace - `firestoreHelpers` Namespace for * firestore helpers (**WARNING** Changing this will break firestoreConnect HOC. * Do **NOT** change to `'firestore'`) + * @property {Array} keysToRemoveFromAuth - (default at end) + * list of keys to remove from authentication reponse before writing to profile + * (currenlty only used for profiles stored on Firestore). `['appName', 'apiKey' + * , 'authDomain', 'redirectEventId', 'stsTokenManager', 'uid']` * @type {Object} */ export const defaultConfig = {