├── .gitattributes ├── example ├── src │ ├── server.js │ ├── redux │ │ ├── middleware │ │ │ └── index.js │ │ ├── reducers │ │ │ ├── index.js │ │ │ └── example.js │ │ └── index.js │ ├── components │ │ ├── Spacer.jsx │ │ ├── Title.jsx │ │ ├── Output.jsx │ │ └── Input.jsx │ ├── client.js │ ├── containers │ │ └── AppContainer.jsx │ └── server │ │ └── index.js ├── test │ ├── redux │ │ ├── reducers │ │ │ ├── index.js │ │ │ └── example.js │ │ └── index.js │ ├── components │ │ ├── Spacer.js │ │ ├── Title.js │ │ ├── Output.js │ │ └── Input.js │ └── containers │ │ └── AppContainer.js ├── webpack.config.js └── package.json ├── .travis.yml ├── .gitignore ├── .editorconfig ├── src └── index.js ├── license ├── package.json ├── readme.md └── test └── index.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /example/src/server.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | require('./server/') 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '4' 5 | after_success: 6 | npm run coverage 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Directories 2 | .nyc_output 3 | coverage 4 | lib 5 | node_modules 6 | 7 | # Files 8 | .DS_Store 9 | npm-debug.log 10 | -------------------------------------------------------------------------------- /example/src/redux/middleware/index.js: -------------------------------------------------------------------------------- 1 | import debounce from '../../../../src' 2 | 3 | export default () => ([ 4 | debounce({ simple: 500 }), 5 | ]) 6 | -------------------------------------------------------------------------------- /example/src/components/Spacer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Spacer = () => ( 4 |
5 | ) 6 | 7 | export default Spacer 8 | -------------------------------------------------------------------------------- /example/src/redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import example from './example' 3 | 4 | export default combineReducers({ 5 | example, 6 | }) 7 | -------------------------------------------------------------------------------- /example/src/components/Title.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Title = () => ( 4 |
5 |

redux-debounce demo

6 |
7 |
8 | ) 9 | 10 | export default Title 11 | -------------------------------------------------------------------------------- /example/test/redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import reducers from '../../../src/redux/reducers' 2 | import test from 'ava' 3 | 4 | test('combines reducers', t => { 5 | const reducer = reducers() 6 | 7 | t.is(reducer.example, '') 8 | }) 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 2 5 | indent_style = space 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.md] 10 | trim_trailing_whitespace = false 11 | -------------------------------------------------------------------------------- /example/src/redux/index.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, createStore } from 'redux' 2 | import createMiddleware from './middleware' 3 | import reducers from './reducers' 4 | 5 | export default () => 6 | applyMiddleware(...createMiddleware())(createStore)(reducers) 7 | -------------------------------------------------------------------------------- /example/src/components/Output.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Output = ({ value }) => ( 4 |
5 | 6 | {value} 7 |
8 | ) 9 | 10 | Output.propTypes = { 11 | value: PropTypes.string, 12 | } 13 | 14 | export default Output 15 | -------------------------------------------------------------------------------- /example/test/components/Spacer.js: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme' 2 | import React from 'react' 3 | import Spacer from '../../src/components/Spacer.jsx' 4 | import test from 'ava' 5 | 6 | test('renders', t => { 7 | const component = shallow() 8 | 9 | t.deepEqual(component.prop('style'), { height: '1em' }) 10 | }) 11 | -------------------------------------------------------------------------------- /example/test/redux/index.js: -------------------------------------------------------------------------------- 1 | import createStore from '../../src/redux' 2 | import test from 'ava' 3 | 4 | test('creates a store', t => { 5 | const { dispatch, subscribe, getState } = createStore() 6 | 7 | t.is(typeof dispatch, 'function') 8 | t.is(typeof subscribe, 'function') 9 | t.is(typeof getState, 'function') 10 | }) 11 | -------------------------------------------------------------------------------- /example/test/components/Title.js: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme' 2 | import React from 'react' 3 | import Title from '../../src/components/Title.jsx' 4 | import test from 'ava' 5 | 6 | test('renders a value', t => { 7 | const component = shallow() 8 | 9 | t.is(component.find('h1').text(), 'redux-debounce demo') 10 | }) 11 | -------------------------------------------------------------------------------- /example/test/components/Output.js: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme' 2 | import Output from '../../src/components/Output.jsx' 3 | import React from 'react' 4 | import test from 'ava' 5 | 6 | test('renders a value', async t => { 7 | const component = shallow(<Output value="Hi!" />) 8 | 9 | t.is(component.find('span').text(), 'Hi!') 10 | }) 11 | -------------------------------------------------------------------------------- /example/src/client.js: -------------------------------------------------------------------------------- 1 | import { Provider } from 'react-redux' 2 | import { render } from 'react-dom' 3 | import AppContainer from './containers/AppContainer' 4 | import React from 'react' 5 | import createStore from './redux' 6 | 7 | const store = createStore() 8 | 9 | render(( 10 | <Provider store={store}> 11 | <AppContainer /> 12 | </Provider> 13 | ), document.getElementById('root')) 14 | -------------------------------------------------------------------------------- /example/src/components/Input.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Input = ({ onChange, value }) => ( 4 | <div> 5 | <label><strong>Type here: </strong></label> 6 | <input 7 | defaultValue={value} 8 | onChange={onChange} 9 | /> 10 | </div> 11 | ) 12 | 13 | Input.propTypes = { 14 | onChange: PropTypes.func, 15 | value: PropTypes.string, 16 | } 17 | 18 | export default Input 19 | -------------------------------------------------------------------------------- /example/src/redux/reducers/example.js: -------------------------------------------------------------------------------- 1 | const INPUT = 'redux-debounce-example/example/INPUT' 2 | 3 | const initialState = '' 4 | 5 | const reducer = ( state = initialState, action = {} ) => { 6 | switch ( action.type ) { 7 | case INPUT: 8 | return action.payload 9 | default: 10 | return state 11 | } 12 | } 13 | 14 | export const input = payload => ({ 15 | meta: { 16 | debounce: 'simple', 17 | }, 18 | payload, 19 | type: INPUT, 20 | }) 21 | 22 | export default reducer 23 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | export default { 4 | devtool: 'eval', 5 | entry: './src/client', 6 | output: { 7 | path: path.join(__dirname, 'dist'), 8 | filename: 'bundle.js', 9 | publicPath: '/static/', 10 | }, 11 | module: { 12 | loaders: [ 13 | { 14 | test: /\.jsx?$/, 15 | loader: 'babel', 16 | exclude: /node_modules/, 17 | }, 18 | ], 19 | }, 20 | resolve: { 21 | extensions: [ '', '.js', '.jsx' ], 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /example/test/redux/reducers/example.js: -------------------------------------------------------------------------------- 1 | import reducer, { input } from '../../../src/redux/reducers/example' 2 | import test from 'ava' 3 | 4 | test('reduces', t => { 5 | const state = reducer() 6 | 7 | t.is(state, '') 8 | 9 | const action = input('something') 10 | const newState = reducer(state, action) 11 | 12 | t.is(newState, 'something') 13 | }) 14 | 15 | test('has input action', t => { 16 | const action = input('text') 17 | 18 | t.is(action.payload, 'text') 19 | t.deepEqual(action.meta, { debounce: 'simple' }) 20 | t.truthy(action.type) 21 | }) 22 | -------------------------------------------------------------------------------- /example/test/components/Input.js: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme' 2 | import { spy } from 'sinon' 3 | import Input from '../../src/components/Input.jsx' 4 | import React from 'react' 5 | import test from 'ava' 6 | 7 | test('renders a value', t => { 8 | const component = shallow(<Input value="Hi!" />) 9 | 10 | t.is(component.find('input').prop('defaultValue'), 'Hi!') 11 | }) 12 | 13 | test('fires onChange', t => { 14 | const onChange = spy() 15 | const component = shallow(<Input onChange={onChange} />) 16 | 17 | component.find('input').simulate('change', 'Hello!') 18 | 19 | t.truthy(onChange.called) 20 | t.truthy(onChange.calledWith('Hello!')) 21 | }) 22 | -------------------------------------------------------------------------------- /example/test/containers/AppContainer.js: -------------------------------------------------------------------------------- 1 | import { 2 | AppContainer, 3 | mapStateToProps, 4 | } from '../../src/containers/AppContainer.jsx' 5 | import { shallow } from 'enzyme' 6 | import React from 'react' 7 | import test from 'ava' 8 | 9 | test('renders', t => { 10 | const component = shallow(<AppContainer value="Hi!" />) 11 | 12 | t.truthy(component.find('Title').length) 13 | t.truthy(component.find('Spacer').length) 14 | t.truthy(component.find('Input').length) 15 | t.truthy(component.find('Output').length) 16 | t.is(component.find('Output').prop('value'), 'Hi!') 17 | }) 18 | 19 | test('maps state to props', t => { 20 | const props = mapStateToProps({ example: 'Something.' }) 21 | 22 | t.deepEqual(props, { value: 'Something.' }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { isFSA } from 'flux-standard-action' 2 | import debounce from 'lodash.debounce' 3 | import mapValues from 'lodash.mapvalues' 4 | 5 | const debounceMiddleware = ( config = {} ) => () => next => { 6 | const debouncers = mapValues(config, option => { 7 | if ( typeof option === 'number' ) { 8 | return debounce(next, option) 9 | } 10 | 11 | const { wait = 0, ...options } = option 12 | 13 | return debounce(next, wait, options) 14 | }) 15 | 16 | return action => { 17 | if ( !isFSA(action) ) { 18 | return next(action) 19 | } 20 | 21 | const { meta = {} } = action 22 | const debouncer = debouncers[meta.debounce] 23 | 24 | if ( debouncer ) { 25 | return debouncer(action) 26 | } 27 | 28 | return next(action) 29 | } 30 | } 31 | 32 | export default debounceMiddleware 33 | -------------------------------------------------------------------------------- /example/src/containers/AppContainer.jsx: -------------------------------------------------------------------------------- 1 | import * as exampleActions from '../redux/reducers/example' 2 | import { connect } from 'react-redux' 3 | import Input from '../components/Input' 4 | import Output from '../components/Output' 5 | import React, { PropTypes } from 'react' 6 | import Spacer from '../components/Spacer' 7 | import Title from '../components/Title' 8 | 9 | export const AppContainer = ({ input, value }) => ( 10 | <div> 11 | <Title /> 12 | <Spacer /> 13 | <Input 14 | value={value} 15 | onChange={({ target }) => input(target.value)} 16 | /> 17 | <Spacer /> 18 | <Output value={value} /> 19 | </div> 20 | ) 21 | 22 | AppContainer.propTypes = { 23 | input: PropTypes.func, 24 | value: PropTypes.string, 25 | } 26 | 27 | export const mapStateToProps = ({ example }) => ({ 28 | value: example, 29 | }) 30 | 31 | export default connect(mapStateToProps, exampleActions)(AppContainer) 32 | -------------------------------------------------------------------------------- /example/src/server/index.js: -------------------------------------------------------------------------------- 1 | import config from '../../webpack.config' 2 | import express from 'express' 3 | import webpack from 'webpack' 4 | 5 | const app = express() 6 | const compiler = webpack(config) 7 | 8 | app.use(require('webpack-dev-middleware')(compiler, { 9 | noInfo: true, 10 | publicPath: config.output.publicPath, 11 | })) 12 | 13 | app.get('*', ( req, res ) => 14 | res.send(` 15 | <!doctype html> 16 | <html> 17 | <head> 18 | <title>redux-debounce Example 19 | 20 | 21 |
22 | 23 | 24 | 25 | `) 26 | ) 27 | 28 | app.listen(3000, 'localhost', listenErr => { 29 | if ( listenErr ) { 30 | console.log(listenErr) // eslint-disable-line 31 | 32 | return 33 | } 34 | 35 | console.log('Listening at http://localhost:3000') // eslint-disable-line 36 | }) 37 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Neil Kistner (neilkistner.com) 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 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-debounce-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "A redux-debounce example.", 6 | "license": "MIT", 7 | "repository": "wyze/redux-debounce", 8 | "author": { 9 | "name": "Neil Kistner", 10 | "email": "neil.kistner@gmail.com", 11 | "url": "neilkistner.com" 12 | }, 13 | "engines": { 14 | "node": ">=0.12.0" 15 | }, 16 | "scripts": { 17 | "pretest": "eslint src test", 18 | "start": "node src/server.js", 19 | "test": "nyc ava", 20 | "test:watch": "npm test -- --watch" 21 | }, 22 | "ava": { 23 | "babel": "inherit", 24 | "require": [ 25 | "babel-register" 26 | ] 27 | }, 28 | "babel": { 29 | "presets": [ 30 | "es2015", 31 | "react", 32 | "stage-2" 33 | ] 34 | }, 35 | "eslintConfig": { 36 | "extends": "wyze" 37 | }, 38 | "dependencies": { 39 | "express": "^4.13.4", 40 | "react": "^15.0.1", 41 | "react-dom": "^15.0.1", 42 | "react-redux": "^4.4.5", 43 | "redux": "^3.4.0" 44 | }, 45 | "devDependencies": { 46 | "ava": "^0.14.0", 47 | "babel-eslint": "^6.0.2", 48 | "babel-loader": "^6.2.4", 49 | "babel-preset-es2015": "^6.6.0", 50 | "babel-preset-react": "^6.5.0", 51 | "babel-preset-stage-2": "^6.5.0", 52 | "babel-register": "^6.7.2", 53 | "enzyme": "^2.2.0", 54 | "eslint": "^2.8.0", 55 | "eslint-config-wyze": "^1.3.0", 56 | "eslint-plugin-react": "^4.2.3", 57 | "nyc": "^6.4.0", 58 | "react-addons-test-utils": "^15.0.1", 59 | "webpack": "^1.13.0", 60 | "webpack-dev-middleware": "^1.6.1" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-debounce", 3 | "version": "1.0.1", 4 | "description": "FSA-compliant middleware for Redux to debounce actions.", 5 | "license": "MIT", 6 | "repository": "wyze/redux-debounce", 7 | "author": { 8 | "name": "Neil Kistner", 9 | "email": "neil.kistner@gmail.com", 10 | "url": "neilkistner.com" 11 | }, 12 | "main": "lib/index.js", 13 | "engines": { 14 | "node": ">=0.12.0" 15 | }, 16 | "scripts": { 17 | "build": "babel src --out-dir lib", 18 | "clean": "rimraf .nyc_output coverage lib", 19 | "coverage": "nyc report -r text-lcov | codecov", 20 | "lint": "eslint src test", 21 | "prebuild": "npm run lint", 22 | "pretest": "npm run clean && npm run build", 23 | "test": "nyc ava", 24 | "test:watch": "npm test -- --watch" 25 | }, 26 | "ava": { 27 | "babel": "inherit", 28 | "require": [ 29 | "babel-register" 30 | ] 31 | }, 32 | "babel": { 33 | "presets": [ 34 | "es2015", 35 | "stage-2" 36 | ] 37 | }, 38 | "eslintConfig": { 39 | "extends": "wyze/base" 40 | }, 41 | "files": [ 42 | "lib", 43 | "license", 44 | "package.json", 45 | "readme.md" 46 | ], 47 | "keywords": [ 48 | "redux", 49 | "debounce", 50 | "middleware", 51 | "redux-middleware", 52 | "fsa", 53 | "flux" 54 | ], 55 | "dependencies": { 56 | "flux-standard-action": "^0.6.1", 57 | "lodash.debounce": "^4.0.8", 58 | "lodash.mapvalues": "^4.6.0" 59 | }, 60 | "devDependencies": { 61 | "ava": "^0.16.0", 62 | "babel-cli": "^6.11.4", 63 | "babel-eslint": "^6.1.2", 64 | "babel-preset-es2015": "^6.13.2", 65 | "babel-preset-stage-2": "^6.13.0", 66 | "babel-register": "^6.11.6", 67 | "codecov.io": "^0.1.6", 68 | "eslint": "^3.3.1", 69 | "eslint-config-airbnb": "^10.0.1", 70 | "eslint-config-wyze": "^3.0.0", 71 | "eslint-plugin-import": "^1.13.0", 72 | "eslint-plugin-wyze": "^2.0.0", 73 | "nyc": "^8.1.0", 74 | "rimraf": "^2.5.4", 75 | "sinon": "^1.17.5" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # redux-debounce 2 | 3 | [![Build Status][travis-image]][travis-url] 4 | [![npm][npm-image]][npm-url] 5 | [![Codecov.io][codecov-image]][codecov-url] 6 | 7 | > FSA-compliant middleware for Redux to debounce actions. 8 | 9 | ## Installation 10 | 11 | ```sh 12 | $ npm install --save redux-debounce 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```javascript 18 | // Store setup 19 | import { applyMiddleware, createStore } from 'redux' 20 | import createDebounce from 'redux-debounce' 21 | import createLogger from 'redux-logger' 22 | import promise from 'redux-promise' 23 | import thunk from 'redux-thunk' 24 | 25 | const config = { 26 | simple: 300 27 | } 28 | 29 | const debouncer = createDebounce(config) 30 | const logger = createLogger() 31 | const createMiddleware = applyMiddleware(thunk, debouncer, promise, logger) 32 | const store = createMiddleware(createStore)(reducer) 33 | 34 | const debounceAction = () => ({ 35 | meta: { 36 | debounce: 'simple', 37 | }, 38 | type: 'TEST', 39 | }) 40 | ``` 41 | 42 | Debounce middleware **should be** placed near the top of the chain. 43 | 44 | ### Example 45 | 46 | See the example directory. 47 | 48 | ```sh 49 | $ cd example 50 | $ npm install 51 | $ npm start 52 | ``` 53 | 54 | ## API 55 | 56 | `redux-debounce` exposes single constructor function for creating debounce middleware. 57 | 58 | > createDebounce( options: Object ) 59 | 60 | ### Options 61 | 62 | > **Each option is a property to setup different debounces for different actions.** 63 | 64 | #### Number 65 | 66 | Number of milliseconds to debounce the action for. 67 | 68 | #### Object 69 | 70 | ##### wait (Number) 71 | 72 | Number of milliseconds to debounce the action for. 73 | 74 | ##### maxWait (Number) 75 | 76 | Maximum number of milliseconds before the action is called. 77 | 78 | See [lodash][lodash-url] for the rest of the supported options. 79 | 80 | ## License 81 | 82 | Copyright © 2015-2016 [Neil Kistner](//github.com/wyze) 83 | 84 | Released under the MIT license. See [license](license) for details. 85 | 86 | [lodash-url]: https://lodash.com/docs#debounce 87 | 88 | [travis-image]: https://img.shields.io/travis/wyze/redux-debounce.svg?style=flat-square 89 | [travis-url]: https://travis-ci.org/wyze/redux-debounce 90 | 91 | [npm-image]: https://img.shields.io/npm/v/redux-debounce.svg?style=flat-square 92 | [npm-url]: https://npmjs.com/package/redux-debounce 93 | 94 | [codecov-image]: https://img.shields.io/codecov/c/github/wyze/redux-debounce.svg?style=flat-square 95 | [codecov-url]: https://codecov.io/github/wyze/redux-debounce 96 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import { spy } from 'sinon' 2 | import debounceMiddleware from '../src' 3 | import test from 'ava' 4 | 5 | const config = { 6 | simple: 100, 7 | nowait: {}, 8 | maxwait: { wait: 100, maxWait: 150 }, 9 | } 10 | const nextHandler = debounceMiddleware(config)() 11 | 12 | test('returns a function to handle next', t => { 13 | t.is(typeof nextHandler, 'function') 14 | t.is(nextHandler.length, 1) 15 | }) 16 | 17 | test('handle next returns function to handle action', t => { 18 | const actionHandler = nextHandler(spy()) 19 | 20 | t.is(typeof actionHandler, 'function') 21 | t.is(actionHandler.length, 1) 22 | }) 23 | 24 | test('calls next when not flux standard action', t => { 25 | const next = spy() 26 | const actionHandler = nextHandler(next) 27 | const action = { id: 1 } 28 | 29 | actionHandler(action) 30 | 31 | t.truthy(next.called) 32 | t.truthy(next.calledWith({ id: 1 })) 33 | }) 34 | 35 | test.cb('calls debounce when config is passed a number', t => { 36 | const next = spy() 37 | const actionHandler = nextHandler(next) 38 | const action = { type: 'TEST', meta: { debounce: 'simple' } } 39 | 40 | actionHandler(action) 41 | 42 | t.falsy(next.called) 43 | 44 | setTimeout(() => { 45 | t.truthy(next.called) 46 | t.truthy(next.calledWith(action)) 47 | t.end() 48 | }, 105) 49 | }) 50 | 51 | test.cb('only calls debounced function once', t => { 52 | const next = spy() 53 | const actionHandler = nextHandler(next) 54 | const action = { type: 'TEST', meta: { debounce: 'simple' } } 55 | 56 | actionHandler(action) 57 | actionHandler(action) 58 | actionHandler(action) 59 | 60 | setTimeout(() => { 61 | t.is(next.callCount, 1) 62 | t.end() 63 | }, 105) 64 | }) 65 | 66 | test.cb('supports an object passed as config to debounce', t => { 67 | const next = spy() 68 | const actionHandler = nextHandler(next) 69 | const action = { type: 'TEST', meta: { debounce: 'nowait' } } 70 | 71 | actionHandler(action) 72 | 73 | setTimeout(() => { 74 | t.truthy(next.called) 75 | t.end() 76 | }, 100) 77 | }) 78 | 79 | test.cb('supports other lodash.debounce options', t => { 80 | const next = spy() 81 | const actionHandler = nextHandler(next) 82 | const action = { type: 'TEST', meta: { debounce: 'maxwait' } } 83 | 84 | actionHandler(action) 85 | 86 | setTimeout(() => { 87 | actionHandler(action) 88 | }, 75) 89 | 90 | setTimeout(() => { 91 | t.falsy(next.called) 92 | }, 100) 93 | 94 | setTimeout(() => { 95 | t.truthy(next.called) 96 | t.end() 97 | }, 155) 98 | }) 99 | 100 | test('skips debounce if not passed matching key', t => { 101 | const next = spy() 102 | const actionHandler = nextHandler(next) 103 | const action = { type: 'TEST', meta: { debounce: 'nomatch' } } 104 | 105 | actionHandler(action) 106 | t.truthy(next.called) 107 | }) 108 | --------------------------------------------------------------------------------