diff --git a/.eslintignore b/.eslintignore index e96870d76..93f17b5b0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,4 +6,6 @@ lib/** _book/** _site/** docs/** -index.d.ts \ No newline at end of file +index.d.ts +examples/** +test/utils.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index ec060e555..585492c98 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,8 +1,8 @@ module.exports = { root: true, parser: 'babel-eslint', - extends: ['standard', 'standard-react', 'prettier', 'prettier/react', 'plugin:jsdoc/recommended'], - plugins: ['babel', 'react', 'prettier', 'react-hooks', 'jsdoc'], + extends: ['standard', 'standard-react', 'prettier', 'prettier/react'], + plugins: ['babel', 'react', 'prettier', 'react-hooks'], settings: { react: { version: 'detect' @@ -15,18 +15,29 @@ module.exports = { rules: { semi: [2, 'never'], 'no-console': 'error', - 'jsdoc/newline-after-description': 0, - 'jsdoc/no-undefined-types': [1, { definedTypes: ['React', 'firebase'] }], - 'prettier/prettier': ['error', { - singleQuote: true, - trailingComma: 'none', - semi: false, - bracketSpacing: true, - jsxBracketSameLine: true, - printWidth: 80, - tabWidth: 2, - useTabs: false - }] - } -}; - + 'prettier/prettier': [ + 'error', + { + singleQuote: true, + trailingComma: 'none', + semi: false, + bracketSpacing: true, + jsxBracketSameLine: true, + printWidth: 80, + tabWidth: 2, + useTabs: false + } + ] + }, + overrides: [ + { + files: ['./src/**/**.js'], + plugins: ['jsdoc'], + extends: ['plugin:jsdoc/recommended'], + rules: { + 'jsdoc/newline-after-description': 0, + 'jsdoc/no-undefined-types': [1, { definedTypes: ['React', 'firebase'] }] + } + } + ] +} diff --git a/docs/api/firestoreConnect.md b/docs/api/firestoreConnect.md index 74fe0a09d..7a59419df 100644 --- a/docs/api/firestoreConnect.md +++ b/docs/api/firestoreConnect.md @@ -14,7 +14,7 @@ Higher Order Component that automatically listens/unListens to provided Cloud Firestore paths using React's Lifecycle hooks. Make sure you -have required/imported Cloud Firestore, including it's reducer, before +have required/imported Cloud Firestore, including its reducer, before attempting to use. **Note** Populate is not yet supported. ### Parameters diff --git a/docs/api/useFirestoreConnect.md b/docs/api/useFirestoreConnect.md index 1dbee6f1b..ddc3ba609 100644 --- a/docs/api/useFirestoreConnect.md +++ b/docs/api/useFirestoreConnect.md @@ -2,23 +2,23 @@ ### Table of Contents -- [useFirestoreConnect][1] - - [Parameters][2] - - [Examples][3] +- [useFirestoreConnect][1] + - [Parameters][2] + - [Examples][3] ## useFirestoreConnect - React hook that automatically listens/unListens to provided Cloud Firestore paths. Make sure you have required/imported Cloud Firestore, including it's reducer, before attempting to use. -**Note** Populate is not yet supported. +Populate is supported for Firestore as of v0.6.0 of redux-firestore (added +[as part of issue #48][5]). ### Parameters -- `queriesConfigs` **([object][5] \| [string][6] \| [Array][7] \| [Function][8])** An object, string, - or array of object or string for paths to sync from firestore. Can also be - a function that returns the object, string, or array of object or string. +- `queriesConfigs` **([object][6] \| [string][7] \| [Array][8] \| [Function][9])** An object, string, + or array of object or string for paths to sync from firestore. Can also be + a function that returns the object, string, or array of object or string. ### Examples @@ -30,15 +30,17 @@ import { useSelector } from 'react-redux' import { useFirestoreConnect } from 'react-redux-firebase' export default function TodosList() { - useFirestoreConnect('todos') // sync todos collection from Firestore into redux - const todos = useSelector(state => state.firestore.data.todos) + useFirestoreConnect(['todos']) // sync todos collection from Firestore into redux + const todos = useSelector((state) => state.firestore.data.todos) return ( + ) } ``` @@ -51,10 +53,12 @@ import { useSelector } from 'react-redux' import { useFirestoreConnect } from 'react-redux-firebase' export default function TodoItem({ todoId }) { - useFirestoreConnect([{ - collection: 'todos', - doc: todoId - }]) + useFirestoreConnect([ + { + collection: 'todos', + doc: todoId + } + ]) const todo = useSelector( ({ firestore: { data } }) => data.todos && data.todos[todoId] ) @@ -64,17 +68,11 @@ export default function TodoItem({ todoId }) { ``` [1]: #usefirestoreconnect - [2]: #parameters - [3]: #examples - [4]: https://react-redux-firebase.com/docs/api/useFirestoreConnect.html - -[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object - -[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String - -[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array - -[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function +[5]: https://github.com/prescottprue/redux-firestore/issues/48 +[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object +[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String +[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array +[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function diff --git a/docs/firestore.md b/docs/firestore.md index 77898bf73..dd3c6c247 100644 --- a/docs/firestore.md +++ b/docs/firestore.md @@ -3,12 +3,13 @@ The Firestore integration is built on [`redux-firestore`](https://github.com/prescottprue/redux-firestore). Auth, Storage, and RTDB interactions still occur within `react-redux-firebase`, while `redux-firestore` handles attaching listeners and updating state for Firestore. To begin using Firestore with `react-redux-firebase`, make sure you have the following: -* `v2.0.0` or higher of `react-redux-firebase` -* Install `redux-firestore` in your project using `npm i --save redux-firestore@latest` -* `firestore` imported with `import 'firebase/firestore'` -* `firestore` initialize with `firebase.firestore()` -* `ReactReduxFirebaseProvider` or `ReduxFirestoreProvider` used to make instance available to HOCs -* `firestoreReducer` added to your reducers + +- `v2.0.0` or higher of `react-redux-firebase` +- Install `redux-firestore` in your project using `npm i --save redux-firestore@latest` +- `firestore` imported with `import 'firebase/firestore'` +- `firestore` initialize with `firebase.firestore()` +- `ReactReduxFirebaseProvider` or `ReduxFirestoreProvider` used to make instance available to HOCs +- `firestoreReducer` added to your reducers Should look something similar to: @@ -20,14 +21,17 @@ import firebase from 'firebase/app' import 'firebase/auth' import 'firebase/firestore' // <- needed if using firestore import { createStore, combineReducers, compose } from 'redux' -import { ReactReduxFirebaseProvider, firebaseReducer } from 'react-redux-firebase' +import { + ReactReduxFirebaseProvider, + firebaseReducer +} from 'react-redux-firebase' import { createFirestoreInstance, firestoreReducer } from 'redux-firestore' // <- needed if using firestore const firebaseConfig = {} // react-redux-firebase config const rrfConfig = { - userProfile: 'users', + userProfile: 'users' // useFirestoreForProfile: true // Firestore for Profile instead of Realtime DB } @@ -62,10 +66,10 @@ function App() { - ); + ) } -render(, document.getElementById('root')); +render(, document.getElementById('root')) ``` ## Profile @@ -94,72 +98,76 @@ Firestore queries can be created in the following ways: `useFirestoreConnect` is a React hook that manages attaching and detaching listeners for you as the component mounts and unmounts. #### Examples + 1. Basic query that will attach/detach as the component passed mounts/unmounts. In this case we are setting a listener for the `'todos'` collection: - ```js - import React from 'react' - import { useSelector } from 'react-redux' - import { useFirestoreConnect } from 'react-redux-firebase' +```js +import React from 'react' +import { useSelector } from 'react-redux' +import { useFirestoreConnect } from 'react-redux-firebase' - export default function SomeComponent() { - useFirestoreConnect([ - { collection: 'todos' } // or 'todos' - ]) - const todos = useSelector(state => state.firestore.ordered.todos) - } - ``` +export default function SomeComponent() { + useFirestoreConnect([ + { collection: 'todos' } // or 'todos' + ]) + const todos = useSelector((state) => state.firestore.ordered.todos) +} +``` 2. Props can be used as part of queries. In this case we will get a specific todo: - ```js - import React from 'react' - import { useSelector } from 'react-redux' - import { useFirestoreConnect } from 'react-redux-firebase' - - export default function SomeComponent({ todoId }) { - useFirestoreConnect(() => [ - { collection: 'todos', doc: todoId } // or `todos/${props.todoId}` - ]) - const todo = useSelector(({ firestore: { data } }) => data.todos && data.todos[todoId]) - } - ``` +```js +import React from 'react' +import { useSelector } from 'react-redux' +import { useFirestoreConnect } from 'react-redux-firebase' + +export default function SomeComponent({ todoId }) { + useFirestoreConnect(() => [ + { collection: 'todos', doc: todoId } // or `todos/${props.todoId}` + ]) + const todo = useSelector( + ({ firestore: { data } }) => data.todos && data.todos[todoId] + ) +} +``` ### Automatically with HOC {#firestoreConnect} `firestoreConnect` is a React Higher Order component that manages attaching and detaching listeners for you as the component mounts and unmounts. It is possible to roll a similar solution yourself, but can get complex when dealing with advanced situations (queries based on props, props changing, etc.) #### Examples + 1. Basic query that will attach/detach as the component passed mounts/unmounts. In this case we are setting a listener for the `'todos'` collection: - ```js - import { compose } from 'redux' - import { connect } from 'react-redux' - import { firestoreConnect } from 'react-redux-firebase' +```js +import { compose } from 'redux' +import { connect } from 'react-redux' +import { firestoreConnect } from 'react-redux-firebase' - export default compose( - firestoreConnect(() => ['todos']), // or { collection: 'todos' } - connect((state, props) => ({ - todos: state.firestore.ordered.todos - })) - )(SomeComponent) - ``` +export default compose( + firestoreConnect(() => ['todos']), // or { collection: 'todos' } + connect((state, props) => ({ + todos: state.firestore.ordered.todos + })) +)(SomeComponent) +``` 2. Create a query based on props by passing a function. In this case we will get a specific todo: - ```js - import { compose } from 'redux' - import { connect } from 'react-redux' - import { firestoreConnect } from 'react-redux-firebase' - - export default compose( - firestoreConnect((props) => [ - { collection: 'todos', doc: props.todoId } // or `todos/${props.todoId}` - ]), - connect(({ firestore: { data } }, props) => ({ - todos: data.todos && data.todos[todoId] - })) - )(SomeComponent) - ``` +```js +import { compose } from 'redux' +import { connect } from 'react-redux' +import { firestoreConnect } from 'react-redux-firebase' + +export default compose( + firestoreConnect((props) => [ + { collection: 'todos', doc: props.todoId } // or `todos/${props.todoId}` + ]), + connect(({ firestore: { data } }, props) => ({ + todos: data.todos && data.todos[todoId] + })) +)(SomeComponent) +``` ## Manual {#manual} @@ -177,7 +185,7 @@ class Todos extends Component { store: PropTypes.object.isRequired } - componentDidMount () { + componentDidMount() { const { firebase } = this.context.store firebase.setListener('todos') // firebase.setListener({ collection: 'todos' }) // or object notation @@ -189,16 +197,12 @@ class Todos extends Component { // firebase.unsetListener({ collection: 'todos' }) // or object notation } - render () { + render() { return (
- { - todos.map(todo => ( -
- {JSON.stringify(todo)} -
- )) - } + {todos.map((todo) => ( +
{JSON.stringify(todo)}
+ ))}
) } @@ -242,7 +246,6 @@ export default compose( This can be useful, but then can limit usage of lifecycle hooks and other features of Component Classes. - [`recompose` helps solve this](https://github.com/acdlite/recompose/blob/master/docs/API.md) by providing Higher Order Component functions such as `lifecycle`, and `withHandlers`. ```js @@ -253,7 +256,7 @@ import { compose, withHandlers, lifecycle } from 'recompose' const enhance = compose( withFirestore, // add firestore to props withHandlers({ - loadData: props => path => props.firestore.get(path) + loadData: (props) => (path) => props.firestore.get(path) }), lifecycle({ componentDidMount() { @@ -262,14 +265,13 @@ const enhance = compose( } }), connect((state) => ({ - todos: state.firestore.ordered.todos, + todos: state.firestore.ordered.todos })) ) export default enhance(SomeComponent) ``` - For more information [on using recompose visit the docs](https://github.com/acdlite/recompose/blob/master/docs/API.md) ### storeAs {#storeAs} @@ -277,6 +279,7 @@ For more information [on using recompose visit the docs](https://github.com/acdl By default the results of queries are stored in redux under the path of the query. If you would like to change where the query results are stored in redux, use `storeAs`. #### Examples + 1. Querying the same path with different query parameters ```js @@ -286,29 +289,30 @@ import { firestoreConnect } from 'react-redux-firebase' const myProjectsReduxName = 'myProjects' compose( - firestoreConnect(props => [ + firestoreConnect((props) => [ { collection: 'projects' }, { collection: 'projects', - where: [ - ['uid', '==', '123'] - ], + where: [['uid', '==', '123']], storeAs: myProjectsReduxName } ]), connect((state, props) => ({ projects: state.firestore.data.projects, - myProjects: state.firestore.data[myProjectsReduxName], // use storeAs path to gather from redux + myProjects: state.firestore.data[myProjectsReduxName] // use storeAs path to gather from redux })) ) ``` 2. Set `useFirestoreConnect` for subcollections documents -For example, in Firestore cloud you have such message structure: -`chatMessages (collection) / chatID (document) / messages (collection) / messageID (document)` + For example, in Firestore cloud you have such message structure: + `chatMessages (collection) / chatID (document) / messages (collection) / messageID (document)` You can't write the path in `useFirestoreConnect` like: -```useFirestoreConnect(`chatMessages/${chatID}/messages`)``` + +```js +useFirestoreConnect(`chatMessages/${chatID}/messages`) +``` You will have error: @@ -316,7 +320,8 @@ You will have error: Solution: Use `subcollections` for 'messages' and `storeAs`. -```import { useFirestoreConnect } from 'react-redux-firebase' + +````import { useFirestoreConnect } from 'react-redux-firebase' useFirestoreConnect([ { collection: 'chatMessages', @@ -329,4 +334,5 @@ Use `subcollections` for 'messages' and `storeAs`. ## Populate {#populate} -Populate is supported for Firestore as of v0.6.0. It was added [with issue #48](https://github.com/prescottprue/redux-firestore/issues/48). +Populate is supported for Firestore as of [v0.6.0 of redux-firestore](https://github.com/prescottprue/redux-firestore/releases/tag/v0.6.0). It was added [as part of issue #48](https://github.com/prescottprue/redux-firestore/issues/48). +```` diff --git a/examples/snippets/watchEvent/README.md b/examples/snippets/watchEvent/README.md index 11f41e0e6..17b520fb9 100644 --- a/examples/snippets/watchEvent/README.md +++ b/examples/snippets/watchEvent/README.md @@ -1,38 +1,42 @@ -# watchEvent snipppet +# watchEvent snippet This example shows creating a listener directly using `watchEvent`, which is handled automatically by `firebaseConnect` ## Whats Included -* Basic snippet - Uses `watchEvent` directly -* Recompose snippet - Uses `recompose` to simplify common patterns +- Basic snippet - Uses `watchEvent` directly +- Recompose snippet - Uses `recompose` to simplify common patterns ## What It Does -1. Top level component uses `connect` function to bring `todos` from redux state into a prop name "todosMap" (an ImmutableJS map): - ```js - export default compose( - withFirebase, // add props.firebase - connect(({ firebase: { data: { todos } } }) => ({ - todos // map todos from redux state to props - })) - )(SomeThing) - ``` +1. Top level component uses `connect` function to bring `todos` from redux state into a prop name "todos": -1. That component then uses `isLoaded` and `isEmpty` to show different views based on auth state (whether data is loaded or not): +```js +export default compose( + withFirebase, // add props.firebase + connect(({ firebase: { data: { todos } } }) => ({ + todos // map todos from redux state to props + })) +)(SomeThing) +``` - ```js - render () { - const { todos } = this.props +1. When the component mounts `watchEvent` is called to attach a listener for `todos` path of real time database +1. That component then uses `isLoaded` and `isEmpty` to show different views based on auth state (whether data is loaded or not): - if (!isLoaded(todos)) { - return
Loading...
- } +```js +render () { + const { todos } = this.props - if (isEmpty(todos)) { - return
No Todos Found
- } + if (!isLoaded(todos)) { + return
Loading...
+ } - return
{JSON.stringify(todos, null, 2)}
+ if (isEmpty(todos)) { + return
No Todos Found
} - ``` + + return
{JSON.stringify(todos, null, 2)}
+} +``` + +**NOTE**: This is simplified functionality of what is available through [`firebaseConnect`](https://react-redux-firebase.com/docs/api/firebaseConnect.html) and {`useFirebaseConnect`](https://react-redux-firebase.com/docs/api/useFirebaseConnect.html). diff --git a/index.d.ts b/index.d.ts index 00c0400ea..89dd2e33e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,6 +10,8 @@ import { Dispatch } from 'redux' */ type Omit = Pick> +type FileOrBlob = T extends File ? File : Blob + /** * Injects props and removes them from the prop requirements. * Will not pass through the injected props if they are passed in during @@ -738,11 +740,11 @@ interface ExtendedStorageInstance { * @param options.documentId - Id of document to update with metadata if using Firestore * @see https://react-redux-firebase.com/docs/api/storage.html#uploadFile */ - uploadFile: ( + uploadFile: ( path: string, - file: File | Blob, + file: FileOrBlob, dbPath?: string, - options?: UploadFileOptions + options?: UploadFileOptions ) => Promise<{ uploadTaskSnapshot: StorageTypes.UploadTaskSnapshot }> /** @@ -758,42 +760,46 @@ interface ExtendedStorageInstance { * @param options.documentId - Id of document to update with metadata if using Firestore * @see https://react-redux-firebase.com/docs/api/storage.html#uploadFiles */ - uploadFiles: ( + uploadFiles: ( path: string, - files: File[] | Blob[], + files: FileOrBlob[], dbPath?: string, - options?: UploadFileOptions + options?: UploadFileOptions ) => Promise<{ uploadTaskSnapshot: StorageTypes.UploadTaskSnapshot }[]> } /** * Configuration object passed to uploadFile and uploadFiles functions */ -export interface UploadFileOptions { - name?: string | (( - file: File | Blob, - internalFirebase: WithFirebaseProps['firebase'], - uploadConfig: { - path: string, - file: File | Blob, - dbPath?: string, - options?: UploadFileOptions - } - ) => string) - documentId?: string | (( - uploadRes: StorageTypes.UploadTaskSnapshot, - firebase: WithFirebaseProps['firebase'], - metadata: StorageTypes.UploadTaskSnapshot['metadata'], - downloadURL: string - ) => string) +export interface UploadFileOptions { + name?: + | string + | (( + file: FileOrBlob, + internalFirebase: WithFirebaseProps['firebase'], + uploadConfig: { + path: string + file: FileOrBlob + dbPath?: string + options?: UploadFileOptions + } + ) => string) + documentId?: + | string + | (( + uploadRes: StorageTypes.UploadTaskSnapshot, + firebase: WithFirebaseProps['firebase'], + metadata: StorageTypes.UploadTaskSnapshot['metadata'], + downloadURL: string + ) => string) useSetForMetadata?: boolean metadata?: StorageTypes.UploadMetadata - metadataFactory? : (( + metadataFactory?: ( uploadRes: StorageTypes.UploadTaskSnapshot, firebase: WithFirebaseProps['firebase'], metadata: StorageTypes.UploadTaskSnapshot['metadata'], downloadURL: string - ) => object) + ) => object } export interface WithFirebaseProps { @@ -1032,11 +1038,20 @@ interface ReactReduxFirebaseConfig { /** * Function for changing how profile is written to database (both RTDB and Firestore). */ - profileFactory?: (userData?: AuthTypes.User, profileData?: any, firebase?: WithFirebaseProps['firebase']) => Promise | any + profileFactory?: ( + userData?: AuthTypes.User, + profileData?: any, + firebase?: WithFirebaseProps['firebase'] + ) => Promise | any /** * Function that returns that meta data object stored after a file is uploaded (both RTDB and Firestore). */ - fileMetadataFactory?: (uploadRes: StorageTypes.UploadTaskSnapshot, firebase: WithFirebaseProps['firebase'], metadata: StorageTypes.UploadTaskSnapshot.metadata, downloadURL: string) => object + fileMetadataFactory?: ( + uploadRes: StorageTypes.UploadTaskSnapshot, + firebase: WithFirebaseProps['firebase'], + metadata: StorageTypes.UploadTaskSnapshot.metadata, + downloadURL: string + ) => object } /** diff --git a/package-lock.json b/package-lock.json index 227de1177..fe01a32f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-redux-firebase", - "version": "3.5.0", + "version": "3.5.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c6a7d2d7d..90f9e802f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-redux-firebase", - "version": "3.5.0", + "version": "3.5.1", "description": "Redux integration for Firebase. Comes with a Higher Order Components for use with React.", "main": "lib/index.js", "module": "es/index.js", @@ -9,7 +9,7 @@ "unpkg": "dist/react-redux-firebase.min.js", "scripts": { "clean": "rimraf es lib dist coverage", - "lint": "eslint ./src/** ./bin/** ./test/**/*.js", + "lint": "eslint .", "lint:fix": "npm run lint -- --fix", "format": "prettier --write \"src/**/*.js\" \"test/**/*.js\"", "test": "mocha -R spec ./test/unit/**", diff --git a/src/useFirestoreConnect.js b/src/useFirestoreConnect.js index 319c43706..1b4b08746 100644 --- a/src/useFirestoreConnect.js +++ b/src/useFirestoreConnect.js @@ -7,7 +7,8 @@ import useFirestore from './useFirestore' * @description React hook that automatically listens/unListens * to provided Cloud Firestore paths. Make sure you have required/imported * Cloud Firestore, including it's reducer, before attempting to use. - * **Note** Populate is not yet supported. + * Populate is supported for Firestore as of v0.6.0 of redux-firestore (added + * [as part of issue #48](https://github.com/prescottprue/redux-firestore/issues/48)). * @param {object|string|Array|Function} queriesConfigs - An object, string, * or array of object or string for paths to sync from firestore. Can also be * a function that returns the object, string, or array of object or string. @@ -18,7 +19,7 @@ import useFirestore from './useFirestore' * import { useFirestoreConnect } from 'react-redux-firebase' * * export default function TodosList() { - * useFirestoreConnect('todos') // sync todos collection from Firestore into redux + * useFirestoreConnect(['todos']) // sync todos collection from Firestore into redux * const todos = useSelector(state => state.firestore.data.todos) * return ( *
    diff --git a/test/.eslintrc.js b/test/.eslintrc.js index 0ecb331a9..bab250816 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -1,23 +1,23 @@ module.exports = { extends: '../.eslintrc.js', -globals:{ - sinon: true, - expect: true, - after: true, - afterEach: true, - before: true, - beforeEach: true, - it: true, - describe: true, - Firebase: true, - firebase: true, - fbConfig: true, - uid: true, - existingProfile: true -}, + globals: { + sinon: true, + expect: true, + after: true, + afterEach: true, + before: true, + beforeEach: true, + it: true, + describe: true, + Firebase: true, + firebase: true, + fbConfig: true, + uid: true, + existingProfile: true + }, -rules: { - 'no-unused-expressions': [0] -} + rules: { + 'no-unused-expressions': [0] + } } diff --git a/test/unit/actions/auth.spec.js b/test/unit/actions/auth.spec.js index 56ca28caa..ce7d611c5 100644 --- a/test/unit/actions/auth.spec.js +++ b/test/unit/actions/auth.spec.js @@ -87,7 +87,7 @@ describe('Actions: Auth -', () => { it('calls profile watch then sets to null when useFirestoreForProfile: true', () => { let profileCalled - let currentFake = cloneDeep(fakeFirebase) + const currentFake = cloneDeep(fakeFirebase) currentFake._.profileWatch = () => { profileCalled = true } @@ -175,7 +175,7 @@ describe('Actions: Auth -', () => { }) it('Any when useFirestoreForProfile: true - calls console.warn', () => { - let currentFake = cloneDeep(fakeFirebase) + const currentFake = cloneDeep(fakeFirebase) currentFake._.profileWatch = () => {} currentFake._.config.useFirestoreForProfile = true currentFake._.config.profileParamsToPopulate = ['some'] @@ -326,7 +326,7 @@ describe('Actions: Auth -', () => { }) }) - describe('login', function() { + describe('login', function () { // Extend default timeout to prevent test fail on slow connection this.timeout(8000) @@ -489,7 +489,7 @@ describe('Actions: Auth -', () => { describe('confirmPasswordReset', () => { it('resets password for real user', () => { return confirmPasswordReset(dispatch, fakeFirebase, 'test', 'test').then( - err => { + (err) => { expect(err).to.be.undefined } ) @@ -658,10 +658,9 @@ describe('Actions: Auth -', () => { config: { userProfile: 'users', useFirestoreForProfile: true } } res = await updateProfile(dispatch, newStubbed, profileUpdate) - expect(firebaseStub.firestore().doc().set).to.have.been.calledWith( - profileUpdate, - { merge: true } - ) + expect( + firebaseStub.firestore().doc().set + ).to.have.been.calledWith(profileUpdate, { merge: true }) }) it('rejects if profile update fails', async () => { diff --git a/test/unit/actions/query.spec.js b/test/unit/actions/query.spec.js index 49919191d..9ac0e5e2d 100644 --- a/test/unit/actions/query.spec.js +++ b/test/unit/actions/query.spec.js @@ -80,9 +80,9 @@ describe('Actions: Query', () => { }) it('refcounts watching and unwatching of equal path', () => { - let projectPath = { type: 'value', path: 'projects/test' } - let numWatchers = () => Object.keys(firebase._.watchers).length - let numWatchersBefore = numWatchers() + const projectPath = { type: 'value', path: 'projects/test' } + const numWatchers = () => Object.keys(firebase._.watchers).length + const numWatchersBefore = numWatchers() expect(watchEvent(firebase, dispatch, projectPath, 'projects')) expect(watchEvent(firebase, dispatch, projectPath, 'projects')) expect(numWatchers() - numWatchersBefore).to.be.equal(1) diff --git a/test/unit/actions/storage.spec.js b/test/unit/actions/storage.spec.js index 0d85003ba..183466a5b 100644 --- a/test/unit/actions/storage.spec.js +++ b/test/unit/actions/storage.spec.js @@ -4,11 +4,7 @@ import { uploadFiles, deleteFile } from 'actions/storage' -import { - fakeFirebase, - createFirebaseStub, - createSuccessStub -} from '../../utils' +import { fakeFirebase, createFirebaseStub } from '../../utils' let spy let firebaseStub @@ -36,7 +32,7 @@ describe('Actions: Storage', () => { uploadFileWithProgress(dispatch, fakeFirebase, { path: 'projects', file: { name: 'test.png' } - }).then(snap => { + }).then((snap) => { expect(spy).to.have.been.calledOnce expect(snap).to.be.an.object })) @@ -138,10 +134,7 @@ describe('Actions: Storage', () => { dbPath: 'storageTest' }) // Confirm metadata factory was called with result of storage put - const putResult = await newFirebaseStub - .storage() - .ref() - .put() + const putResult = await newFirebaseStub.storage().ref().put() expect(metadataFactorySpy).to.have.been.calledWith(putResult) // firebase.storage() ref is in correct location expect(newFirebaseStub.database().ref).to.have.been.calledWith( @@ -245,10 +238,7 @@ describe('Actions: Storage', () => { options: { metadataFactory: metadataFactorySpy } }) // Confirm metadata factory was called with result of storage put - const putResult = await firebaseStub - .storage() - .ref() - .put() + const putResult = await firebaseStub.storage().ref().put() expect(metadataFactorySpy).to.have.been.calledWith(putResult) // firebase.storage() ref is in correct location expect(firebaseStub.database().ref).to.have.been.calledWith( @@ -256,10 +246,7 @@ describe('Actions: Storage', () => { ) const pushSpy = firebaseStub.database().ref().push expect(pushSpy).to.have.been.calledOnce - const setSpy = firebaseStub - .database() - .ref() - .push().set + const setSpy = firebaseStub.database().ref().push().set const metaArg = setSpy.getCall(0).args[0] // firebase.database() push method is called with file metadata (provided by factory) expect(metaArg).to.have.property('asdf', fileMetadata.asdf) @@ -276,7 +263,7 @@ describe('Actions: Storage', () => { uploadFiles(dispatch, fakeFirebase, { path: 'projects', file: { name: 'test.png' } - }).then(snap => { + }).then((snap) => { expect(snap).to.be.an.object })) }) diff --git a/test/unit/reducer.spec.js b/test/unit/reducer.spec.js index fb4b2d6ef..62b83c122 100644 --- a/test/unit/reducer.spec.js +++ b/test/unit/reducer.spec.js @@ -219,7 +219,7 @@ describe('reducer', () => { expect(firebaseReducer({}, action)).to.deep.equal(initialState) }) - it('merge data to empty state under path', () => { + it('merges data to empty state under path', () => { action = { type: actionTypes.MERGE, path, data: exampleData } expect(firebaseReducer({}, action).data).to.deep.equal({ ...initialState.data, @@ -227,7 +227,7 @@ describe('reducer', () => { }) }) - it('merge data to empty state under paths that end in a number', () => { + it('merges 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, @@ -273,7 +273,7 @@ describe('reducer', () => { }) }) - it('merge data to path with already existing value of null', () => { + it('merges 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( @@ -281,7 +281,7 @@ describe('reducer', () => { ) }) - it('merge data to path with already existing parent of null', () => { + it('merges 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( diff --git a/test/unit/utils/query.spec.js b/test/unit/utils/query.spec.js index 57d55c8bf..8796893ab 100644 --- a/test/unit/utils/query.spec.js +++ b/test/unit/utils/query.spec.js @@ -10,7 +10,7 @@ import { } from 'utils/query' import { fakeFirebase } from '../../utils' -let createQueryFromParams = queryParams => +const createQueryFromParams = (queryParams) => applyParamsToQuery(queryParams, fakeFirebase.database().ref()) const dispatch = () => {} @@ -231,7 +231,7 @@ describe('Utils: Query', () => { it('adds children to ordered if they exist', () => { const child = { key: 'some', val: () => ({}) } - const forEachSpy = sinon.spy(childFunc => childFunc(child)) + const forEachSpy = sinon.spy((childFunc) => childFunc(child)) const res = orderedFromSnapshot({ forEach: forEachSpy }) expect(res).to.be.an('array') expect(forEachSpy).to.have.been.calledOnce diff --git a/test/utils.js b/test/utils.js index 75ff56d2d..c75ac09f2 100644 --- a/test/utils.js +++ b/test/utils.js @@ -81,7 +81,7 @@ ProviderMock.propTypes = { children: PropTypes.node } -export const onAuthStateChangedSpy = sinon.spy(f => { +export const onAuthStateChangedSpy = sinon.spy((f) => { f({ uid: 'asdfasdf' }) }) @@ -95,6 +95,7 @@ export const firebaseWithConfig = (config = {}) => ({ /** * Create a Sinon stub that returns with a resolved Promise Object + * * @param {Any} some * @returns {Sinon.stub} */ @@ -117,12 +118,13 @@ export function createFailureStub(some) { * Create an object representing a "profileReference" (a Firebase * RTDB Reference at the Profile path) which contains Sinon stubs * in place of Firebase JS SDK methods (such as update and once) + * * @returns {object} */ function createRtdbProfileRefStub() { let profileUpdate = {} return { - update: sinon.spy(setData => { + update: sinon.spy((setData) => { // Fail automatically for intentionally rejected promise // (used in testing that rejecting set promise is handled) if (setData === 'fail') { @@ -142,13 +144,14 @@ function createRtdbProfileRefStub() { * Firestore Reference for the current user's profile document) which * contains Sinon stubs in place of Firebase JS SDK methods (such as * update and get) + * * @returns {object} */ function createFirestoreProfileRefStub() { let profileUpdate = {} return { update: createSuccessStub(), - set: sinon.spy(setData => { + set: sinon.spy((setData) => { // Fail automatically for intentionally rejected promise // (used in testing that rejecting set promise is handled) if (setData === 'fail') { @@ -173,6 +176,7 @@ function createFirestoreProfileRefStub() { /** * Create a Sinon stub for Firestore + * * @returns {Sinon.stub} */ function createFirestoreStub() { @@ -208,6 +212,7 @@ function createRtdbStub(refExtension) { /** * Create a Sinon stub for Firebase's Auth + * * @returns {Sinon.stub} */ function createAuthStub() { @@ -218,6 +223,7 @@ function createAuthStub() { /** * Create a Sinon stub for Firebase Storage + * * @returns {Sinon.stub} */ function createStorageStub() { @@ -276,11 +282,11 @@ 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 }), @@ -304,7 +310,7 @@ export const fakeFirebase = { }), auth: () => ({ onAuthStateChanged: onAuthStateChangedSpy, - getRedirectResult: f => { + getRedirectResult: (f) => { return Promise.resolve({ uid: 'asdfasdf' }) }, signOut: () => Promise.resolve({}), @@ -312,12 +318,12 @@ export const fakeFirebase = { 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.reject(new Error('asdfasdf')) + : Promise.resolve({ + uid: '123', + email: 'test@test.com', + providerData: [{}] + }), signInAndRetrieveDataWithCustomToken: () => { return Promise.resolve({ toJSON: () => ({ @@ -333,23 +339,23 @@ export const fakeFirebase = { email.indexOf('error2') !== -1 ? 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.reject(new Error('auth/user-not-found')) + : 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' - ? Promise.reject(new Error('asdfasdf')) - : Promise.resolve({ some: 'val' }), + ? Promise.reject(new Error('asdfasdf')) + : Promise.resolve({ some: 'val' }), confirmPasswordReset: (code, password) => password === 'error' ? Promise.reject({ code: code }) // eslint-disable-line prefer-promise-reject-errors : Promise.resolve(), - verifyPasswordResetCode: code => + verifyPasswordResetCode: (code) => code === 'error' ? Promise.reject(new Error('some')) ? Promise.reject({ code: 'asdfasdf' }) // eslint-disable-line prefer-promise-reject-errors @@ -440,9 +446,10 @@ export const createContainer = ({ /** * Sleep/wait for a set amount of time + * * @param {number} ms - Amount of time to sleep * @returns {Promise} Resolves after timeout */ export function sleep(ms = 0) { - return new Promise(resolve => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)) }