├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── package.json ├── src ├── actionCreators.js ├── index.js ├── middleware.js ├── reducer.js └── util.js └── test ├── actionCreators.js ├── index.js ├── middleware.js ├── reducer.js └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["stage-2", "es2015"], 3 | "plugins": [ 4 | "add-module-exports" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain 2 | # consistent coding styles between different editors and IDEs. 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "mocha": true 6 | }, 7 | "rules": { 8 | "max-len": ["error", 150, 2] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Editors 40 | .idea 41 | 42 | # Lib 43 | lib 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | script: 5 | - npm run lint 6 | - npm run test 7 | - npm run build 8 | branches: 9 | only: 10 | - master 11 | after_success: 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Divyansh Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-action-watch 2 | 3 | [![Build Status](https://travis-ci.org/Codebrahma/redux-action-watch.svg?branch=master)](https://travis-ci.org/Codebrahma/redux-action-watch) 4 | [![Coverage Status](https://coveralls.io/repos/github/Codebrahma/redux-action-watch/badge.svg?branch=master)](https://coveralls.io/github/Codebrahma/redux-action-watch?branch=master) 5 | 6 | **`redux-action-watch` provides feature to listen action dispatched to redux.** 7 | 8 | - You can watch for a redux action dispatch. 9 | - Provides some helpers to register you own function as watcher like, `onAction`, `onActionOnce`, `subscribeActions`. 10 | - It can act as IPC (inter-process communication) b/w components. 11 | 12 | > I don't think, we should only dispatch action to make changes in flux/redux state. Action can either change state or acknowledge that something happen. 13 | 14 | 15 | ### Installation 16 | 17 | You should install it as dependency: 18 | 19 | ```sh 20 | $ npm install --save redux-action-watch 21 | ``` 22 | 23 | ### Setup with redux 24 | app.js / index.js 25 | ```javascript 26 | import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; 27 | import thunk from 'redux-thunk'; // optional 28 | import reducers from './reducers'; 29 | import actionWatchMiddlewaregenerator from 'redux-action-watch/lib/middleware'; 30 | import actionWatchReducer from 'redux-action-watch/lib/reducer'; 31 | // import { middleware, reducer } from 'redux-action-watch'; 32 | 33 | const initialState = {}; 34 | // Important! name redux-action-watch reducer in your redux. 35 | const actionWatcherStateName = 'watcher'; 36 | const reducersWithActionWatchReducer = Object.assign({}, reducers, { 37 | [actionWatcherStateName]: actionWatchReducer, 38 | }); 39 | 40 | // generate middleware 41 | const actionWatchMiddleware = actionWatchMiddlewaregenerator(actionWatcherStateName); 42 | 43 | const store = createStore( 44 | combineReducers(reducersWithActionWatchReducer), 45 | initialState, 46 | compose( 47 | /** configure middlewares with redux */ 48 | applyMiddleware(actionWatchMiddleware, thunk), 49 | /** optional, redux dev tool */ 50 | window.devToolsExtension ? window.devToolsExtension() : f => f 51 | ) 52 | ) 53 | ``` 54 | 55 | ### How to use 56 | **Container** 57 | ```javascript 58 | import { connect } from 'react-redux'; 59 | import { 60 | onAction, // takes a action and a listener. 61 | onActionOnce, // takes a action and a listener, will called only once. 62 | subscribeActions, // takes multiple actions and listners. 63 | } from 'redux-action-watch/lib/actionCreators'; 64 | 65 | const mapPropsToDispatch = dispatch => ({ 66 | // bind dispatch function 67 | subscribeActions: subscribeActions(dispatch), 68 | unsubscribeActions: unsubscribeActions(dispatch), 69 | onActionOnce: onActionOnce(dispatch), 70 | }); 71 | 72 | export default connect(null, mapPropsToDispatch)(Component); 73 | ``` 74 | 75 | **Component** 76 | 77 | ```javascript 78 | import React from 'react'; 79 | import { LOGIN_SUCCEEDED, LOGIN_FAILED } from './../actions'; 80 | import Notifier from './notifier'; 81 | 82 | class LoginForm extends React.Component { 83 | 84 | componentDidMount() { 85 | this.unsubscribe = this.props.onAction(LOGIN_FAILED, (action) => Notifier.show(action.error)); 86 | /* 87 | --------------------------or--------------------------------- 88 | this.watchMeta = { 89 | ACTION_A: fn1, 90 | ACTION_B: [fn2, fn3]; 91 | }; 92 | this.unsubscribe = this.props.subscribeAction(this.watchMeta); 93 | --------------------------or--------------------------------- 94 | // if you want to auto unsubscribe after once call. 95 | this.props.onActionOnce(ACTION_A, callMeOnceFunction); 96 | */ 97 | } 98 | 99 | componentWillUnmount() { 100 | this.unsubscribe(); 101 | } 102 | 103 | render() { 104 | // Your code ........ 105 | } 106 | 107 | } 108 | ``` 109 | 110 | ### Documentation 111 | Note:- All functions should first call with `dispatch` 112 | 113 | - `subscribeAction(dispatch)(listenersObj)` 114 | It can register watcher for more than one actions. 115 | **Argument** 116 | `listenersObj`: It should be an object where keys will be action `type` and value will be listener or array of listeners. Example: `{ ACTION_A: func1, ACTION_B: [func2, func3] }`. 117 | **Returns** 118 | `unsubscribeFunc`: It returns function. Which should invoke to unsubscribe those listeners. 119 | 120 | - `unsubscribeAction(dispatch)(listenersObj)` 121 | It can un-subscribe actions which subscribe by `subscribeAction` function. It takes same `listenersObj` used at time of subscription. 122 | **Argument** 123 | `listenersObj`: Same as above. 124 | 125 | - `onAction(dispatch)(actionType, listener)` 126 | It can register a watcher/listener for a action. 127 | **Arguments** 128 | `actionType`: Type/name of action. Example, `const action = { type: ACTION_A }`. Here `ACTION_A` is actionType. 129 | `listener`: Function which will be invoke on action dispatch. 130 | **Returns** 131 | `unsubscribeFunc`: It returns function. Which should invoke to unsubscribe that listener. 132 | 133 | - `onActionOnce(dispatch)(actionType, listener)` 134 | It can register a watcher/listener for a action. And it will automatically un-subscribe after once invoke. 135 | **Arguments** 136 | `actionType`: Type/name of action. Example, `const action = { type: ACTION_A }`. Here `ACTION_A` is actionType. 137 | `listener`: Function which will be invoke on action dispatch. 138 | **Returns** 139 | `unsubscribeFunc`: It returns function. You can unsubscribe without once invoke. 140 | 141 | ### Development 142 | 143 | Want to contribute? Great! 144 | 145 | - Clone this repo 146 | - Make changes 147 | - Run test case `npm run test` 148 | - Create pull request 149 | 150 | ### Todos 151 | 152 | - Write example application 153 | 154 | License 155 | ---- 156 | 157 | MIT 158 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-action-watch", 3 | "version": "1.0.6", 4 | "description": "Watch actions dispatched to redux.", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "clean": "rimraf lib", 8 | "test": "cross-env BABEL_ENV=commonjs mocha --compilers js:babel-register --recursive", 9 | "test:watch": "npm test -- --watch", 10 | "cover": "cross-env BABEL_ENV=commonjs istanbul cover _mocha -- --compilers js:babel-register --recursive", 11 | "lint": "eslint src test", 12 | "build": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", 13 | "prepublish": "npm run clean && npm run lint && npm run test && npm run build" 14 | }, 15 | "files": [ 16 | "lib", 17 | "src" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/Codebrahma/redux-action-watch.git" 22 | }, 23 | "keywords": [ 24 | "redux action watch", 25 | "action", 26 | "redux watch", 27 | "redux action", 28 | "action watcher", 29 | "redux", 30 | "react" 31 | ], 32 | "author": "Divyansh Kumar mr.divyanshindore@gmail.com", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/Codebrahma/redux-action-watch/issues" 36 | }, 37 | "homepage": "https://github.com/Codebrahma/redux-action-watch#readme", 38 | "devDependencies": { 39 | "babel": "^6.5.2", 40 | "babel-cli": "^6.14.0", 41 | "babel-eslint": "^7.0.0", 42 | "babel-plugin-add-module-exports": "^0.2.1", 43 | "babel-preset-es2015": "^6.14.0", 44 | "babel-preset-stage-2": "^6.13.0", 45 | "chai": "^3.5.0", 46 | "coveralls": "^2.11.14", 47 | "cross-env": "^3.0.0", 48 | "eslint": "^3.6.0", 49 | "eslint-config-airbnb": "^12.0.0", 50 | "eslint-plugin-import": "^2.0.1", 51 | "eslint-plugin-jsx-a11y": "^2.2.3", 52 | "eslint-plugin-react": "^6.4.1", 53 | "istanbul": "^1.0.0-alpha", 54 | "mocha": "^3.0.2", 55 | "mocha-lcov-reporter": "^1.2.0", 56 | "rimraf": "^2.5.4", 57 | "sinon": "^1.17.6" 58 | }, 59 | "dependencies": { 60 | "lodash.defer": "^4.1.0", 61 | "lodash.foreach": "^4.5.0" 62 | }, 63 | "peerDependencies": { 64 | "redux": "^3.0.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/actionCreators.js: -------------------------------------------------------------------------------- 1 | // package's action namespace 2 | export const actionNamespace = '@ACTION_WATCHER'; 3 | 4 | // Actions 5 | export const SUBSCRIBE_ACTIONS = `${actionNamespace}/ADD`; 6 | export const UNSUBSCRIBE_ACTIONS = `${actionNamespace}/REMOVE`; 7 | 8 | /** 9 | * Un-subscribes/remove listeners 10 | * 11 | * @example 12 | * unsubscribeActions(dispatch)({ 13 | * ACTION_A: listenerFn1, 14 | * ACTION_B: [listenerFn2, listenerFn3], 15 | * }) 16 | * 17 | * @param {Function} dispatch Redux dispatch function 18 | * @return {Function} Action creator to dispatch subcribe action object to redux 19 | */ 20 | export const unsubscribeActions = dispatch => listenersObj => dispatch({ type: UNSUBSCRIBE_ACTIONS, listenersObj }); 21 | /** 22 | * Subscribes/add listener for redux action dispatch 23 | * 24 | * @example 25 | * const unsubscribe = subscribeActions(dispatch)({ 26 | * ACTION_A: listenerFn1, 27 | * ACTION_B: [listenerFn2, listenerFn3], 28 | * }) 29 | * 30 | * unsubscribe(); // un-subscribe 31 | * 32 | * @param {Function} dispatch Redux dispatch function 33 | * @return {Function} Action creator to dispatch un-subcribe action object to redux 34 | */ 35 | export const subscribeActions = dispatch => (listenersObj) => { 36 | dispatch({ type: SUBSCRIBE_ACTIONS, listenersObj }); 37 | return () => unsubscribeActions(dispatch)(listenersObj); 38 | }; 39 | 40 | /** 41 | * Alise of subscritionActions function, but accept one listner. 42 | * 43 | * @example 44 | * const unsubscribe = onAction(dispatch)('ACTION_A', actionObj => {console.log(actionObj)}) 45 | * 46 | * unsubscribe(); // un-subscribe 47 | * 48 | * @param {Function} dispatch Redux dispatch function 49 | * @return {Function} Action creator to dispatch subcribe action object to redux 50 | */ 51 | export const onAction = dispatch => (action, listener) => subscribeActions(dispatch)({ [action]: listener }); 52 | 53 | /** 54 | * Alise of subscritionActions function, but accept one listner at a time and automatically unsubcribe 55 | * after one call. 56 | * 57 | * @example 58 | * onActionOnce(dispatch)('ACTION_A', actionObj => {console.log(actionObj)}) 59 | * 60 | * @param {Function} dispatch Redux dispatch function 61 | * @return {Function} Action creator to dispatch subcribe action object to redux 62 | */ 63 | export const onActionOnce = dispatch => (actionType, listener) => { 64 | let unsubscribe = null; 65 | const wrapListener = (actionMeta) => { 66 | if (unsubscribe) { 67 | // un-subscribe behalf of coder. 68 | unsubscribe(); 69 | } 70 | listener(actionMeta); 71 | }; 72 | unsubscribe = subscribeActions(dispatch)({ [actionType]: wrapListener }); 73 | // return unsubscribe fn to support termination without any call. 74 | return unsubscribe; 75 | }; 76 | 77 | export default { 78 | subscribeActions, 79 | unsubscribeActions, 80 | onAction, 81 | onActionOnce, 82 | }; 83 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import middleware from './middleware'; 2 | import reducer from './reducer'; 3 | import actionCreators from './actionCreators'; 4 | 5 | export default { 6 | middleware, 7 | reducer, 8 | actionCreators, 9 | }; 10 | -------------------------------------------------------------------------------- /src/middleware.js: -------------------------------------------------------------------------------- 1 | import defer from 'lodash.defer'; 2 | import { isImmutable } from './util'; 3 | 4 | const watcherMiddleware = reducerName => store => next => (action) => { 5 | const appState = store.getState(); 6 | // check for immutable store 7 | const watcher = isImmutable(appState) ? appState.get(reducerName) : appState[reducerName]; 8 | // check whether reducer has been setup or not. 9 | if (!watcher) { 10 | throw Error('Reducer has not configured'); 11 | } 12 | const listeners = watcher[action.type] || []; 13 | // only call listner if it is function 14 | listeners.forEach(listener => (typeof listener === 'function' ? defer(listener, action) : null)); 15 | return next(action); 16 | }; 17 | 18 | // takes state/reducer name in redux store 19 | export default (reducerName = 'watcher') => watcherMiddleware(reducerName); 20 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | import forEach from 'lodash.foreach'; 2 | import { actionNamespace, SUBSCRIBE_ACTIONS, UNSUBSCRIBE_ACTIONS } from './actionCreators'; 3 | 4 | // helper function to convert listners object to acceptable object 5 | export const formatListenerObj = (listenersObj) => { 6 | const formated = {}; 7 | forEach(listenersObj, (listener, action) => (formated[action] = (listener instanceof Array ? listener : [listener]))); 8 | 9 | return formated; 10 | }; 11 | 12 | export default (state = {}, { type, listenersObj }) => { 13 | // ignore if action object is not relievent. 14 | if (!(new RegExp(actionNamespace)).test(type) || typeof listenersObj !== 'object') { 15 | return state; 16 | } 17 | 18 | // clone the state 19 | const newState = Object.assign({}, state); 20 | // parse the listenersObj 21 | const formattedListenerObj = formatListenerObj(listenersObj); 22 | // Check for subcribe or un-subscribe action 23 | switch (type) { 24 | case SUBSCRIBE_ACTIONS: { 25 | // Add listners 26 | forEach(formattedListenerObj, (listeners, actionType) => { 27 | newState[actionType] = newState[actionType] ? newState[actionType].concat(listeners) : listeners; 28 | }); 29 | return newState; 30 | } 31 | case UNSUBSCRIBE_ACTIONS: { 32 | // remove listeners 33 | forEach(formattedListenerObj, (listeners, actionType) => { 34 | if (!newState[actionType]) { 35 | return; 36 | } 37 | forEach(listeners, (listener) => { 38 | const index = newState[actionType].indexOf(listener); 39 | newState[actionType].splice(index, 1); 40 | }); 41 | }); 42 | return newState; 43 | } 44 | default: { 45 | return state; 46 | } 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | export const isImmutable = appState => (typeof appState.get === 'function'); 2 | 3 | export default { 4 | isImmutable, 5 | }; 6 | -------------------------------------------------------------------------------- /test/actionCreators.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; // eslint-disable-line import/no-extraneous-dependencies 2 | import { spy } from 'sinon'; // eslint-disable-line import/no-extraneous-dependencies 3 | import { 4 | actionNamespace, 5 | SUBSCRIBE_ACTIONS, 6 | UNSUBSCRIBE_ACTIONS, 7 | subscribeActions, 8 | unsubscribeActions, 9 | onAction, 10 | onActionOnce, 11 | } from '../src/actionCreators'; 12 | 13 | describe('Checking variables.', () => { 14 | it('should test value of `actionNamespace`', () => { 15 | assert(actionNamespace === '@ACTION_WATCHER', 'Action namespace value has changed :('); 16 | }); 17 | 18 | it('should test value of `SUBSCRIBE_ACTIONS`', () => { 19 | assert(SUBSCRIBE_ACTIONS === `${actionNamespace}/ADD`, 'SUBSCRIBE_ACTIONS\'s value has changed :('); 20 | }); 21 | 22 | it('should test value of `UNSUBSCRIBE_ACTIONS`', () => { 23 | assert(UNSUBSCRIBE_ACTIONS === `${actionNamespace}/REMOVE`, 'UNSUBSCRIBE_ACTIONS\'s value has changed :('); 24 | }); 25 | }); 26 | 27 | describe('Checking action creators.', () => { 28 | let proxyDispatch; 29 | before(() => { 30 | proxyDispatch = spy(); 31 | }); 32 | beforeEach(() => { 33 | proxyDispatch.reset(); 34 | }); 35 | describe('Checking `subscribeActions`', () => { 36 | it('should test `subscribeActions` behaviour', () => { 37 | const listenerObj = { 38 | ACTION_A: () => true, 39 | }; 40 | const unsubscribe = subscribeActions(proxyDispatch)(listenerObj); 41 | assert(proxyDispatch.calledOnce, 'call once failed'); 42 | assert(proxyDispatch.calledWithMatch({ 43 | type: SUBSCRIBE_ACTIONS, 44 | listenersObj: listenerObj, 45 | }), 'Call with mismatched'); 46 | proxyDispatch.reset(); 47 | unsubscribe(); 48 | assert(proxyDispatch.calledOnce, 'call once failed'); 49 | assert(proxyDispatch.calledWithMatch({ 50 | type: UNSUBSCRIBE_ACTIONS, 51 | listenersObj: listenerObj, 52 | }), 'Call with mismatched'); 53 | }); 54 | }); 55 | 56 | describe('Checking `unsubscribeActions`', () => { 57 | it('should test `unsubscribeActions` behaviour', () => { 58 | const listenerObj = { 59 | ACTION_A: () => true, 60 | }; 61 | unsubscribeActions(proxyDispatch)(listenerObj); 62 | assert(proxyDispatch.calledOnce, 'call once failed'); 63 | assert(proxyDispatch.calledWithMatch({ 64 | type: UNSUBSCRIBE_ACTIONS, 65 | listenersObj: listenerObj, 66 | }), 'Call with mismatched'); 67 | }); 68 | }); 69 | 70 | describe('Checking `onAction`', () => { 71 | it('should test `onAction` behaviour', () => { 72 | const listener = () => true; 73 | const action = 'ACTION_A'; 74 | const unsubscribe = onAction(proxyDispatch)(action, listener); 75 | assert(proxyDispatch.calledOnce, 'call once failed'); 76 | assert(proxyDispatch.calledWithMatch({ 77 | type: SUBSCRIBE_ACTIONS, 78 | listenersObj: { [action]: listener }, 79 | }), 'Call with mismatched'); 80 | proxyDispatch.reset(); 81 | unsubscribe(); 82 | assert(proxyDispatch.calledOnce, 'call once failed'); 83 | assert(proxyDispatch.calledWithMatch({ 84 | type: UNSUBSCRIBE_ACTIONS, 85 | listenersObj: { [action]: listener }, 86 | }), 'Call with mismatched'); 87 | }); 88 | }); 89 | 90 | describe('Checking `onActionOnce`', () => { 91 | it('should test `onActionOnce` behaviour', () => { 92 | const listener = spy(); 93 | const action = 'ACTION_A'; 94 | onActionOnce(proxyDispatch)(action, listener); 95 | assert(proxyDispatch.calledOnce, 'call once failed'); 96 | const wrapedListener = proxyDispatch.args[0][0].listenersObj[action]; 97 | assert(proxyDispatch.calledWith({ 98 | type: SUBSCRIBE_ACTIONS, 99 | listenersObj: { [action]: wrapedListener }, 100 | }), 'Call with mismatched'); 101 | proxyDispatch.reset(); 102 | wrapedListener('abc'); 103 | assert(listener.calledOnce, 'call once failed'); 104 | assert(listener.calledWithMatch('abc'), 'call once failed'); 105 | assert(proxyDispatch.calledWith({ 106 | type: UNSUBSCRIBE_ACTIONS, 107 | listenersObj: { [action]: wrapedListener }, 108 | }), 'Call with mismatched'); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // import { assert } from 'chai'; // eslint-disable-line import/no-extraneous-dependencies 2 | // import awesomeFunction from '../src'; 3 | 4 | // describe('Awesome test.', () => { 5 | // it('should test awesome function', () => { 6 | // assert(awesomeFunction(1, 1) === 2, 'Not awesome :('); 7 | // }); 8 | // }); 9 | -------------------------------------------------------------------------------- /test/middleware.js: -------------------------------------------------------------------------------- 1 | import { expect, assert } from 'chai'; // eslint-disable-line import/no-extraneous-dependencies 2 | import { stub, spy } from 'sinon'; // eslint-disable-line import/no-extraneous-dependencies 3 | import middleware from '../src/middleware'; 4 | 5 | describe('Checking middleware.', () => { 6 | describe('Checking middleware setup.', () => { 7 | it('should test to throw error on no reducer setup', () => { 8 | const stubGetState = stub(); 9 | stubGetState.onCall(0).returns({}); 10 | const store = { getState: stubGetState }; 11 | const spyNextFn = spy(); 12 | const action = { type: 'ACTION_A' }; 13 | const setupedMiddleware = middleware('watcher'); 14 | expect(setupedMiddleware(store)(spyNextFn).bind(null, action)) 15 | .to 16 | .throw('Reducer has not configured'); 17 | }); 18 | }); 19 | 20 | it('should test middleware behaviour', () => { 21 | const stubGetState = stub(); 22 | const callback = () => (true); 23 | stubGetState.onCall(0).returns({ 24 | watcher: { 25 | ACTION_A: [callback, undefined, 4, 'string'], // test with non-func also 26 | }, 27 | }); 28 | const store = { getState: stubGetState }; 29 | const spyNextFn = spy(); 30 | const action = { type: 'ACTION_A' }; 31 | const setupedMiddleware = middleware('watcher'); 32 | setupedMiddleware(store)(spyNextFn)(action); 33 | assert(spyNextFn.calledOnce, 'Middleware is not able to call next fn'); 34 | assert(spyNextFn.calledWith(action), 'Middleware is not able to call next with action object'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/reducer.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; // eslint-disable-line import/no-extraneous-dependencies 2 | import reducer from '../src/reducer'; 3 | import { SUBSCRIBE_ACTIONS, UNSUBSCRIBE_ACTIONS } from '../src/actionCreators'; 4 | 5 | describe('Checking reducer.', () => { 6 | const listenerA = () => (true); 7 | const listenerA1 = () => (true); 8 | const listenerA2 = () => (true); 9 | const listenerB = () => (true); 10 | const listenerC = () => (true); 11 | const listenerC1 = () => (true); 12 | 13 | it('should test reducer behaviour, if subcribe for actions', () => { 14 | const currentState = {}; 15 | const resultState = { 16 | ACTION_A: [listenerA, listenerA1, listenerA2], 17 | ACTION_B: [listenerB], 18 | ACTION_C: [listenerC], 19 | }; 20 | const resultState2 = Object.assign({}, resultState, { 21 | ACTION_C: [listenerC, listenerC1], 22 | }); 23 | 24 | const newState = reducer(currentState, { 25 | type: SUBSCRIBE_ACTIONS, 26 | listenersObj: { 27 | ACTION_A: [listenerA, listenerA1, listenerA2], 28 | ACTION_B: [listenerB], 29 | ACTION_C: [listenerC], 30 | }, 31 | }); 32 | const newState2 = reducer(newState, { 33 | type: SUBSCRIBE_ACTIONS, 34 | listenersObj: { 35 | ACTION_C: listenerC1, 36 | }, 37 | }); 38 | 39 | expect(newState).to.deep.equal(resultState); 40 | expect(newState2).to.deep.equal(resultState2); 41 | }); 42 | 43 | it('should test reducer behaviour, if un-subcribe for actions', () => { 44 | const currentState = { 45 | ACTION_A: [listenerA, listenerA1, listenerA2], 46 | ACTION_B: [listenerB], 47 | ACTION_C: [listenerC, listenerC1], 48 | }; 49 | const resultState = { 50 | ACTION_A: [listenerA, listenerA2], 51 | ACTION_B: [], 52 | ACTION_C: [listenerC, listenerC1], 53 | }; 54 | 55 | const newState = reducer(currentState, { 56 | type: UNSUBSCRIBE_ACTIONS, 57 | listenersObj: { 58 | ACTION_A: listenerA1, 59 | ACTION_B: [listenerB], 60 | }, 61 | }); 62 | 63 | expect(newState).to.deep.equal(resultState); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; // eslint-disable-line import/no-extraneous-dependencies 2 | import { 3 | isImmutable, 4 | } from '../src/util'; 5 | 6 | describe('Checking utility functions.', () => { 7 | it('should test `isImmutable` function, to check whether `object` is immutable object or not', () => { 8 | const subject = {}; 9 | 10 | expect(isImmutable(subject)).to.equal(false); 11 | }); 12 | }); 13 | --------------------------------------------------------------------------------