├── .babelrc.json
├── .circleci
└── config.yml
├── .codeclimate.yml
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README.md
├── package.json
├── rollup.config.js
├── src
├── constants.js
├── createAction.js
├── createReducer.js
├── createThunk.js
├── index.js
├── statusReducer.js
├── thunkMiddleware.js
├── useLoading.js
└── useStatus.js
└── tests
└── thunkMiddleware.spec.js
/.babelrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "node": "current"
8 | }
9 | }
10 | ]
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | orbs:
3 | node: circleci/node@1.1.6
4 | jobs:
5 | build-and-test:
6 | executor:
7 | name: node/default
8 | steps:
9 | - checkout
10 | - node/with-cache:
11 | steps:
12 | - run: npm install
13 | - run: npm test
14 | workflows:
15 | build-and-test:
16 | jobs:
17 | - build-and-test
18 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | checks:
3 | # Methods or functions defined with a high number of arguments
4 | argument-count:
5 | config:
6 | threshold: 5
7 |
8 | # Boolean logic that may be hard to understand
9 | complex-logic:
10 | config:
11 | threshold: 4
12 |
13 | # Excessive lines of code within a single file
14 | file-lines:
15 | config:
16 | threshold: 500
17 |
18 | # Functions or methods that may be hard to understand
19 | method-complexity:
20 | config:
21 | threshold: 20
22 |
23 | # Classes defined with a high number of functions or methods
24 | method-count:
25 | config:
26 | threshold: 20
27 |
28 | # Deeply nested control structures like if or case
29 | nested-control-flow:
30 | config:
31 | threshold: 4
32 |
33 | # Functions or methods with a high number of return statements
34 | return-statements:
35 | config:
36 | threshold: 4
37 |
38 | # Excessive lines of code within a single function or method
39 | # Disabled because the render function will exceed this threshold many times
40 | method-lines:
41 | enabled: false
42 |
43 | # Duplicate code which is not identical but shares the same structure (e.g. variable names may differ)
44 | # Disabled because it gives some false positives
45 | similar-code:
46 | enabled: false
47 |
48 | # Duplicate code which is syntactically identical (but may be formatted differently)
49 | # Disabled because it gives some false positives
50 | identical-code:
51 | enabled: false
52 | plugins:
53 | eslint:
54 | enabled: true
55 | config:
56 | config: ~/.eslintrc
57 | nodesecurity:
58 | enabled: true
59 | stylelint:
60 | enabled: true
61 |
62 | exclude_patterns:
63 | - "node_modules/"
64 | - "/test/**/*"
65 | - "**/vendor/"
66 | - "**/*.d.ts"
67 | - "coverage/**/*"
68 | - "src/locales/"
69 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /*
2 | !src
3 | !tests
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true,
6 | "jest": true
7 | },
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:prettier/recommended",
11 | "plugin:import/errors",
12 | "plugin:import/warnings"
13 | ],
14 | "plugins": ["prettier"],
15 | "globals": {
16 | "Atomics": "readonly",
17 | "SharedArrayBuffer": "readonly"
18 | },
19 | "parserOptions": {
20 | "ecmaVersion": 2018,
21 | "sourceType": "module"
22 | },
23 | "rules": {
24 | "import/no-unresolved": ["error"],
25 | "indent": [
26 | "error",
27 | 2
28 | ],
29 | "linebreak-style": [
30 | "error",
31 | "unix"
32 | ],
33 | "quotes": [
34 | "error",
35 | "single"
36 | ],
37 | "semi": [
38 | "error",
39 | "never"
40 | ]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | lib
4 |
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "semi": false
5 | }
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at peter@rootstrap.com, ximena.lasserre@rootstrap.com, elizabeth@rootstrap.com, eugenia.miranda@rootstrap.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | MIT License
4 |
5 | Copyright (c) 2018 Rootstrap
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @rootstrap/redux-tools
2 | This package has some basic functionality common to both react bases.
3 | It includes a status reducer that lets you track the status of your async actions and a thunks-like middleware that will automatically dispatch success and failure actions.
4 |
5 | ## Basic usage
6 |
7 | ### ActionCreators
8 |
9 | This package provides an action creator utility, that together with the provided middleware will make it very easy to create side effects for your actions.
10 | This setup will automatically execute your side effect thunk and dispatch success or error actions when the thunk succeeds or fails, respectively.
11 |
12 | `createThunk` receives the action names prefix as the first argument and the async thunk as the second one.
13 |
14 | Example:
15 | ```js
16 | // src/actions/userActions.js
17 | import { createThunk } from '@rootstrap/redux-tools'
18 |
19 | export const getProfile = createThunk(
20 | 'GET_PROFILE',
21 | userService.getProfile,
22 | );
23 | ```
24 |
25 | You can then dispatch this `getProfile` action, and the middleware will automatically dispatch actions with types `GET_PROFILE_SUCCESS` or `GET_PROFILE_ERROR` for you.
26 |
27 | The returned object, (`getProfile` in the example above) has 4 properties you can use in order to handle the different dispatched actions in your reducer:
28 | - request
29 | - success
30 | - error
31 | - reset
32 |
33 | Following the previous example:
34 |
35 | ```js
36 | // src/reducers/userReducer.js
37 |
38 | import { getProfile } from 'src/actions/userActions';
39 |
40 | const actionHandlers = {
41 | [getProfile.success]: (state, { payload }) => {
42 | state.user = payload;
43 | },
44 | };
45 | ```
46 |
47 | If you need to access the store, or dispatch extra actions from your thunk, you can use `dispatch` and `getState` as the last two parameters.
48 |
49 | Example:
50 |
51 | Dispatching some custom analytics event that requires store data:
52 | ```js
53 | // src/actions/userActions.js
54 | import { createThunk } from '@rootstrap/redux-tools'
55 |
56 | export const getProfile = createThunk(
57 | 'GET_PROFILE',
58 | async (userId, dispatch, getState) => {
59 | const { analytics: { analyticsToken } } = getState();
60 | const profile = await userService.getProfile(profileId);
61 | dispatch(analytics.logProfile(analyticsToken, profile));
62 | return profile;
63 | },
64 | );
65 | ```
66 |
67 | ### Status tracking
68 |
69 | To access status information on a component the `useStatus` hook is provided.
70 | The following status constants are exported:
71 | - LOADING
72 | - SUCCESS
73 | - ERROR
74 |
75 | Here is a simple example:
76 |
77 | ```js
78 | import { useStatus, useDispatch } from 'hooks';
79 | import { getProfile } from 'src/actions/userActions';
80 | import { SUCCESS, LOADING, ERROR } from '@rootstrap/redux-tools'
81 |
82 | const MyComponent = () => {
83 | const getProfileRequest = useDispatch(getProfile);
84 | const { status, error } = useStatus(getProfile);
85 |
86 | return <>
87 |
88 | {(status === LOADING) && }
89 | {(status === SUCCESS) && }
90 | {(status === ERROR) && }
91 | >
92 | }
93 | ```
94 |
95 | A `useLoading` hook is also available if you only care about loading status. It returns a boolean indicating whether the action is still loading or not.
96 |
97 | To reset the status of an action you can dispatch the `reset` action returned by `createThunk`.
98 |
99 |
100 | ## Installation guide
101 |
102 | ### Step 1: install the package
103 |
104 | `npm i @rootstrap/redux-tools`
105 | or
106 | `yarn add @rootstrap/redux-tools`
107 |
108 | ### Step 2: configure the reducer
109 | ```js
110 | // src/reducers/index.js
111 | import { combineReducers } from 'redux'
112 | import { statusReducer } from '@rootstrap/redux-tools'
113 |
114 | const rootReducer = combineReducers({
115 | // ...your other reducers here
116 | // you have to pass statusReducer under 'statusReducer' key,
117 | statusReducer
118 | })
119 | ```
120 |
121 | ### Step 3: configure the middleware
122 | ```js
123 | import { createStore, applyMiddleware } from 'redux'
124 | import { thunkMiddleware } from '@rootstrap/redux-tools'
125 |
126 | import rootReducer from 'src/reducers/index'
127 |
128 | const store = createStore(rootReducer, applyMiddleware(thunkMiddleware))
129 | ```
130 |
131 | ## License
132 |
133 | **@rootstrap/redux-tools** is available under the MIT license. See the LICENSE file for more info.
134 |
135 | ## Credits
136 |
137 | **@rootstrap/redux-tools** is maintained by [Rootstrap](http://www.rootstrap.com) with the help of our [contributors](https://github.com/rootstrap/redux-tools/contributors).
138 |
139 | [
](http://www.rootstrap.com)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@rootstrap/redux-tools",
3 | "version": "1.0.0",
4 | "description": "Redux tools we use in both react bases",
5 | "main": "lib/index.js",
6 | "module": "src/index.js",
7 | "files": [
8 | "src",
9 | "lib",
10 | "README.md"
11 | ],
12 | "scripts": {
13 | "test": "jest tests",
14 | "build": "rollup -c",
15 | "prepare": "rollup -c"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/rootstrap/redux-tools.git"
20 | },
21 | "keywords": [
22 | "redux",
23 | "rootstrap",
24 | "react"
25 | ],
26 | "author": "Pedro Zunino ",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/rootstrap/redux-tools/issues"
30 | },
31 | "homepage": "https://github.com/rootstrap/redux-tools#readme",
32 | "dependencies": {
33 | "immer": "^5.0.0"
34 | },
35 | "devDependencies": {
36 | "rollup": "^1.29.0",
37 | "@babel/core": "^7.9.0",
38 | "@babel/preset-env": "^7.9.0",
39 | "babel-jest": "^25.1.0",
40 | "eslint": "^6.6.0",
41 | "eslint-config-prettier": "^6.5.0",
42 | "eslint-plugin-import": "^2.18.2",
43 | "eslint-plugin-prettier": "^3.1.1",
44 | "eslint-plugin-react-hooks": "^2.2.0",
45 | "jest": "^24.9.0",
46 | "prettier": "^1.19.1",
47 | "react-redux": "^7.1.1",
48 | "redux": "^4.0.4",
49 | "redux-mock-store": "^1.5.3"
50 | },
51 | "peerDependencies": {
52 | "react": "^16.8.0",
53 | "redux": "^4.0.4",
54 | "react-redux": "^7.1.1"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json';
2 |
3 | export default {
4 | input: 'src/index.js',
5 | external: ['immer', 'react-redux'],
6 | output: [
7 | { file: pkg.main, format: 'cjs' },
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const NOT_STARTED = 'NOT_STARTED'
2 |
3 | export const LOADING = 'LOADING'
4 |
5 | export const SUCCESS = 'SUCCESS'
6 |
7 | export const ERROR = 'ERROR'
8 |
9 | export const REQUEST = 'REQUEST'
10 |
11 | export const RESET = 'RESET'
12 |
--------------------------------------------------------------------------------
/src/createAction.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Makes action creators
3 | *
4 | * @param {string} type - Dispatched actions type
5 | * @return {function} Action creator
6 | *
7 | * @example
8 | * const loginSuccess = createAction('LOGIN_SUCCESS')
9 | */
10 |
11 | export default type => {
12 | const action = payload => ({
13 | type,
14 | payload,
15 | })
16 | action.toString = () => type
17 | return action
18 | }
19 |
--------------------------------------------------------------------------------
/src/createReducer.js:
--------------------------------------------------------------------------------
1 | import imm from 'immer'
2 |
3 | /**
4 | * Reducer creator util
5 | *
6 | * @param initialState Reducer initial state
7 | * @param actionHandlers - An object with all the reducer handlers
8 | *
9 | * @return {function} A reducer ready to use in createStore
10 | *
11 | * @example
12 | * const myReducer = createReducer({}, {
13 | * [loginSuccess]: (state, action) => {
14 | * state.user = action.payload
15 | * }
16 | * })
17 | */
18 |
19 | export default (initialState, actionHandlers) => (
20 | state = initialState,
21 | action,
22 | ) =>
23 | imm(state, draft =>
24 | actionHandlers[action.type]
25 | ? actionHandlers[action.type](draft, action)
26 | : state,
27 | )
28 |
--------------------------------------------------------------------------------
/src/createThunk.js:
--------------------------------------------------------------------------------
1 | import { SUCCESS, ERROR, REQUEST, RESET } from './constants'
2 | import createAction from './createAction'
3 |
4 | /**
5 | * Creates different actions creators
6 | *
7 | * @param {string} actionName - Action name, will be used as a prefix for the action creators.
8 | * @param {function} thunk - This is your async thunk, receives all forwarded params and `dispatch` and `getState` as params
9 | *
10 | * @returns {ActionCreator} Action that can be dispatched to start the async thunk, can also be
11 | * deconstructed to get request, error, and success action creators (can be used as keys in reducer)
12 | *
13 | * @example
14 | * export const getProfile = createActionWithThunk(
15 | * 'LOGIN',
16 | * user => userService.login(user),
17 | * );
18 | * export const { success, error } = getProfile;
19 | */
20 |
21 | export default (actionName, thunk) => {
22 | const request = createAction(`${actionName}_${REQUEST}`)
23 | const error = createAction(`${actionName}_${ERROR}`)
24 | const success = createAction(`${actionName}_${SUCCESS}`)
25 | const reset = createAction(`${actionName}_${RESET}`)
26 |
27 | const action = (...params) => ({
28 | success,
29 | error,
30 | thunk: (dispatch, getState) => thunk(...params, dispatch, getState),
31 | type: request.toString(),
32 | })
33 |
34 | action.request = request
35 | action.error = error
36 | action.success = success
37 | action.reset = reset
38 | action.toString = () => actionName
39 |
40 | return action
41 | }
42 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as createAction } from './createAction'
2 | export { default as createThunk } from './createThunk'
3 | export { default as createReducer } from './createReducer'
4 | export { default as statusReducer } from './statusReducer'
5 | export { default as thunkMiddleware } from './thunkMiddleware'
6 | export { default as useStatus } from './useStatus'
7 | export { default as useLoading } from './useLoading'
8 | export * from './constants'
9 |
--------------------------------------------------------------------------------
/src/statusReducer.js:
--------------------------------------------------------------------------------
1 | import imm from 'immer'
2 | import { NOT_STARTED, LOADING, SUCCESS, ERROR } from './constants'
3 |
4 | const handleAction = (state, action) => {
5 | const { type, payload } = action
6 |
7 | const matchesStart = /(.*)_REQUEST/.exec(type)
8 | const matchesError = /(.*)_ERROR/.exec(type)
9 | const matchesReset = /(.*)_RESET/.exec(type)
10 | const matchesSuccess = /(.*)_SUCCESS/.exec(type)
11 |
12 | let status = NOT_STARTED
13 | let key = null
14 |
15 | if (matchesStart) {
16 | const [, requestName] = matchesStart
17 | key = requestName
18 | status = LOADING
19 | } else if (matchesReset) {
20 | const [, requestName] = matchesReset
21 | key = requestName
22 | status = NOT_STARTED
23 | } else if (matchesError) {
24 | const [, requestName] = matchesError
25 | key = requestName
26 | status = ERROR
27 | } else if (matchesSuccess) {
28 | const [, requestName] = matchesSuccess
29 | key = requestName
30 | status = SUCCESS
31 | }
32 |
33 | if (key) state[key] = { status, error: matchesError ? payload : undefined }
34 |
35 | return state
36 | }
37 |
38 | export default (state = {}, action) =>
39 | imm(state, draft => handleAction(draft, action))
40 |
--------------------------------------------------------------------------------
/src/thunkMiddleware.js:
--------------------------------------------------------------------------------
1 | const id = item => item
2 |
3 | const thunkMiddlewareCreator = ({
4 | parseError = id,
5 | parseResponse = id,
6 | } = {}) => ({ dispatch, getState }) => next => async action => {
7 | next(action)
8 |
9 | const { thunk, success, error } = action
10 | if (typeof thunk === 'function') {
11 | try {
12 | const response = await thunk(dispatch, getState)
13 | return dispatch(success(parseResponse(response)))
14 | } catch (err) {
15 | return dispatch(error(parseError(err)))
16 | }
17 | }
18 | }
19 |
20 | const thunkMiddleware = thunkMiddlewareCreator()
21 | thunkMiddleware.withConfig = thunkMiddlewareCreator
22 |
23 | export default thunkMiddleware
24 |
--------------------------------------------------------------------------------
/src/useLoading.js:
--------------------------------------------------------------------------------
1 | import { useSelector } from 'react-redux'
2 | import { LOADING } from './constants'
3 |
4 | /**
5 | * useLoading hook
6 | *
7 | * @param {string} action Prefix for the action names
8 | *
9 | * @returns {boolean} Wether the action is loading
10 | *
11 | * @example
12 | * const isLoading = useStatus(getProfile)
13 | */
14 |
15 | export default action =>
16 | useSelector(({ statusReducer }) => {
17 | const { status } = statusReducer[action] || {}
18 | return status === LOADING
19 | })
20 |
--------------------------------------------------------------------------------
/src/useStatus.js:
--------------------------------------------------------------------------------
1 | import { useSelector } from 'react-redux'
2 |
3 | /**
4 | * useStatus hook
5 | *
6 | * @param {string} action Prefix for the action names
7 | *
8 | * @returns {object} Object with status and error keys
9 | *
10 | * @example
11 | * const { status, error } = useStatus(login)
12 | */
13 |
14 | export default action =>
15 | useSelector(({ statusReducer }) => {
16 | const { status, error } = statusReducer[action] || {}
17 | return {
18 | status,
19 | error,
20 | }
21 | })
22 |
--------------------------------------------------------------------------------
/tests/thunkMiddleware.spec.js:
--------------------------------------------------------------------------------
1 | import configureStore from 'redux-mock-store'
2 |
3 | import thunkMiddleware from '../src/thunkMiddleware'
4 | import createThunk from '../src/createThunk'
5 |
6 | const parseError = error => error.toString()
7 | const parseResponse = response => response.toUpperCase()
8 | let mockStore = configureStore([thunkMiddleware])
9 |
10 | describe('actionThunk Middleware', () => {
11 | let store
12 | let mockErrorAction
13 | let mockSuccessAction
14 |
15 | const error = new Error('testing')
16 | const success = 'Yay!'
17 |
18 | beforeEach(() => {
19 | store = mockStore({})
20 | mockErrorAction = createThunk('error', () => {
21 | throw error
22 | })
23 | mockSuccessAction = createThunk('success', () => {
24 | return success
25 | })
26 | })
27 |
28 | describe('when dispatching any action', () => {
29 | it('the action should not be dismissed', () => {
30 | store.dispatch(mockSuccessAction())
31 | const actions = store.getActions()
32 | expect(actions[0]).toHaveProperty(
33 | 'type',
34 | mockSuccessAction.request.toString(),
35 | )
36 | })
37 | })
38 |
39 | describe('when dispatching the action that fails', () => {
40 | it('should dispatch the error action', async () => {
41 | await store.dispatch(mockErrorAction())
42 | const actions = store.getActions()
43 | expect(actions).toContainEqual(mockErrorAction.error(error))
44 | })
45 | })
46 |
47 | describe('when dispatching the action that succeeds', () => {
48 | it('should dispatch the success action', async () => {
49 | await store.dispatch(mockSuccessAction())
50 | const actions = store.getActions()
51 | expect(actions).toContainEqual(mockSuccessAction.success(success))
52 | })
53 | })
54 |
55 | describe('when adding a parsing functions', () => {
56 | let mockStoreWithParseFunctions
57 | beforeEach(() => {
58 | mockStoreWithParseFunctions = configureStore([
59 | thunkMiddleware.withConfig({ parseError, parseResponse }),
60 | ])({})
61 | mockErrorAction = createThunk('error', () => {
62 | throw error
63 | })
64 | mockSuccessAction = createThunk('success', () => {
65 | return success
66 | })
67 | })
68 |
69 | describe('when dispatching the action that fails', () => {
70 | it('should dispatch the error action with the parsed error', async () => {
71 | await mockStoreWithParseFunctions.dispatch(mockErrorAction())
72 | const actions = mockStoreWithParseFunctions.getActions()
73 | expect(actions).toContainEqual(mockErrorAction.error(parseError(error)))
74 | })
75 | })
76 |
77 | describe('when dispatching the action that succeeds', () => {
78 | it('should dispatch the success action with the parsed data', async () => {
79 | await mockStoreWithParseFunctions.dispatch(mockSuccessAction())
80 | const actions = mockStoreWithParseFunctions.getActions()
81 | expect(actions).toContainEqual(
82 | mockSuccessAction.success(parseResponse(success)),
83 | )
84 | })
85 | })
86 | })
87 | })
88 |
--------------------------------------------------------------------------------