Comparing version 4.0.3 to 5.0.0
{ | ||
"env": { | ||
"browser": true, | ||
"commonjs": true, | ||
"es6": true, | ||
"node": true | ||
}, | ||
"extends": "eslint:recommended", | ||
"parserOptions": { | ||
"sourceType": "module", | ||
"ecmaVersion": "2017" | ||
}, | ||
"rules": { | ||
"indent": [ | ||
"error", | ||
2 | ||
], | ||
"linebreak-style": [ | ||
"error", | ||
"unix" | ||
], | ||
"quotes": [ | ||
"error", | ||
"single" | ||
], | ||
"semi": [ | ||
"error", | ||
"always" | ||
] | ||
} | ||
"env": { | ||
"browser": true, | ||
"commonjs": true, | ||
"es6": true, | ||
"node": true | ||
}, | ||
"extends": ["eslint:recommended", "plugin:prettier/recommended"], | ||
"parserOptions": { | ||
"sourceType": "module", | ||
"ecmaVersion": 2017 | ||
}, | ||
"rules": { | ||
"linebreak-style": ["error", "unix"] | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
/*! autodux v4.0.3 by Eric Elliott */ | ||
/*! autodux v5.0.0 by Eric Elliott */ | ||
import 'core-js/modules/es6.array.reduce-right'; | ||
@@ -9,2 +9,3 @@ import 'core-js/modules/es6.regexp.split'; | ||
import 'core-js/modules/es6.array.reduce'; | ||
import 'core-js/modules/es6.array.some'; | ||
@@ -15,2 +16,5 @@ var get = require('lodash/fp/get'); | ||
var _require = require("./errors"), | ||
SLICE_VALUE_ERROR = _require.SLICE_VALUE_ERROR; | ||
var id = function id(x) { | ||
@@ -30,2 +34,32 @@ return x; | ||
var isString = function isString(s) { | ||
return typeof s == 'string'; | ||
}; | ||
var isNumber = function isNumber(n) { | ||
return typeof n == 'number'; | ||
}; | ||
var isBoolean = function isBoolean(b) { | ||
return typeof b == 'boolean'; | ||
}; | ||
var isUndefined = function isUndefined(v) { | ||
return typeof v == 'undefined'; | ||
}; | ||
var isNull = function isNull(v) { | ||
return v === null; | ||
}; | ||
var isPrimitive = function isPrimitive(v) { | ||
return [isString, isNumber, isBoolean, isUndefined, isNull].some(function (f) { | ||
return f(v); | ||
}); | ||
}; | ||
var isEmptyString = function isEmptyString(s) { | ||
return s === ''; | ||
}; | ||
var selectFunction = selectIf(isFunction); | ||
@@ -83,3 +117,3 @@ | ||
return Object.assign({}, state, key ? (_ref = {}, _ref[key] = payload, _ref) : payload); | ||
return !key && isPrimitive(payload) ? payload : Object.assign({}, state, key ? (_ref = {}, _ref[key] = payload, _ref) : payload); | ||
} | ||
@@ -123,13 +157,28 @@ }; | ||
var autodux = function autodux(_temp) { | ||
var _ref2 = _temp === void 0 ? {} : _temp, | ||
_ref2$initial = _ref2.initial, | ||
initial = _ref2$initial === void 0 ? '' : _ref2$initial, | ||
_ref2$actions = _ref2.actions, | ||
actions = _ref2$actions === void 0 ? {} : _ref2$actions, | ||
_ref2$selectors = _ref2.selectors, | ||
selectors = _ref2$selectors === void 0 ? {} : _ref2$selectors, | ||
_ref2$slice = _ref2.slice, | ||
slice = _ref2$slice === void 0 ? '' : _ref2$slice; | ||
var isSliceValid = function isSliceValid(slice) { | ||
return isString(slice) && !isEmptyString(slice); | ||
}; | ||
var checkOptions = function checkOptions(_ref2) { | ||
var slice = _ref2.slice; | ||
if (!isSliceValid(slice)) { | ||
throw new Error(SLICE_VALUE_ERROR); | ||
} | ||
}; | ||
var autodux = function autodux(options) { | ||
if (options === void 0) { | ||
options = {}; | ||
} | ||
checkOptions(options); | ||
var _options = options, | ||
_options$initial = _options.initial, | ||
initial = _options$initial === void 0 ? '' : _options$initial, | ||
_options$actions = _options.actions, | ||
actions = _options$actions === void 0 ? {} : _options$actions, | ||
_options$selectors = _options.selectors, | ||
selectors = _options$selectors === void 0 ? {} : _options$selectors, | ||
slice = _options.slice; | ||
var allSelectors = Object.assign({}, createInitSelectors(slice, initial), createSliceSelectors(slice, selectors)); | ||
@@ -139,3 +188,3 @@ var allActions = createMappedActions(slice, addDefaultActions(slice, initial, actions)); | ||
initial: initial, | ||
reducer: function reducer(state, _temp2) { | ||
reducer: function reducer(state, _temp) { | ||
if (state === void 0) { | ||
@@ -145,3 +194,3 @@ state = initial; | ||
var _ref3 = _temp2 === void 0 ? {} : _temp2, | ||
var _ref3 = _temp === void 0 ? {} : _temp, | ||
type = _ref3.type, | ||
@@ -148,0 +197,0 @@ payload = _ref3.payload; |
@@ -1,2 +0,2 @@ | ||
/*! autodux v4.0.3 by Eric Elliott */ | ||
/*! autodux v5.0.0 by Eric Elliott */ | ||
'use strict'; | ||
@@ -11,2 +11,3 @@ | ||
require('core-js/modules/es6.array.reduce'); | ||
require('core-js/modules/es6.array.some'); | ||
@@ -17,2 +18,5 @@ var get = require('lodash/fp/get'); | ||
var _require = require("./errors"), | ||
SLICE_VALUE_ERROR = _require.SLICE_VALUE_ERROR; | ||
var id = function id(x) { | ||
@@ -32,2 +36,32 @@ return x; | ||
var isString = function isString(s) { | ||
return typeof s == 'string'; | ||
}; | ||
var isNumber = function isNumber(n) { | ||
return typeof n == 'number'; | ||
}; | ||
var isBoolean = function isBoolean(b) { | ||
return typeof b == 'boolean'; | ||
}; | ||
var isUndefined = function isUndefined(v) { | ||
return typeof v == 'undefined'; | ||
}; | ||
var isNull = function isNull(v) { | ||
return v === null; | ||
}; | ||
var isPrimitive = function isPrimitive(v) { | ||
return [isString, isNumber, isBoolean, isUndefined, isNull].some(function (f) { | ||
return f(v); | ||
}); | ||
}; | ||
var isEmptyString = function isEmptyString(s) { | ||
return s === ''; | ||
}; | ||
var selectFunction = selectIf(isFunction); | ||
@@ -85,3 +119,3 @@ | ||
return Object.assign({}, state, key ? (_ref = {}, _ref[key] = payload, _ref) : payload); | ||
return !key && isPrimitive(payload) ? payload : Object.assign({}, state, key ? (_ref = {}, _ref[key] = payload, _ref) : payload); | ||
} | ||
@@ -125,13 +159,28 @@ }; | ||
var autodux = function autodux(_temp) { | ||
var _ref2 = _temp === void 0 ? {} : _temp, | ||
_ref2$initial = _ref2.initial, | ||
initial = _ref2$initial === void 0 ? '' : _ref2$initial, | ||
_ref2$actions = _ref2.actions, | ||
actions = _ref2$actions === void 0 ? {} : _ref2$actions, | ||
_ref2$selectors = _ref2.selectors, | ||
selectors = _ref2$selectors === void 0 ? {} : _ref2$selectors, | ||
_ref2$slice = _ref2.slice, | ||
slice = _ref2$slice === void 0 ? '' : _ref2$slice; | ||
var isSliceValid = function isSliceValid(slice) { | ||
return isString(slice) && !isEmptyString(slice); | ||
}; | ||
var checkOptions = function checkOptions(_ref2) { | ||
var slice = _ref2.slice; | ||
if (!isSliceValid(slice)) { | ||
throw new Error(SLICE_VALUE_ERROR); | ||
} | ||
}; | ||
var autodux = function autodux(options) { | ||
if (options === void 0) { | ||
options = {}; | ||
} | ||
checkOptions(options); | ||
var _options = options, | ||
_options$initial = _options.initial, | ||
initial = _options$initial === void 0 ? '' : _options$initial, | ||
_options$actions = _options.actions, | ||
actions = _options$actions === void 0 ? {} : _options$actions, | ||
_options$selectors = _options.selectors, | ||
selectors = _options$selectors === void 0 ? {} : _options$selectors, | ||
slice = _options.slice; | ||
var allSelectors = Object.assign({}, createInitSelectors(slice, initial), createSliceSelectors(slice, selectors)); | ||
@@ -141,3 +190,3 @@ var allActions = createMappedActions(slice, addDefaultActions(slice, initial, actions)); | ||
initial: initial, | ||
reducer: function reducer(state, _temp2) { | ||
reducer: function reducer(state, _temp) { | ||
if (state === void 0) { | ||
@@ -147,3 +196,3 @@ state = initial; | ||
var _ref3 = _temp2 === void 0 ? {} : _temp2, | ||
var _ref3 = _temp === void 0 ? {} : _temp, | ||
type = _ref3.type, | ||
@@ -150,0 +199,0 @@ payload = _ref3.payload; |
{ | ||
"name": "autodux", | ||
"version": "4.0.3", | ||
"version": "5.0.0", | ||
"description": "Automate the Redux boilerplate.", | ||
@@ -9,7 +9,12 @@ "browser": "dist/index.js", | ||
"scripts": { | ||
"lint": "eslint src && echo 'lint finished.'", | ||
"test": "node src/test.js | tap-colorize && npm run -s lint", | ||
"debug": "echo 'open debugger: chrome://inspect' && node --inspect-brk src/test.js", | ||
"watch": "watch 'clear && npm run -s test' src", | ||
"prepare": "npm run test -s && prepublish", | ||
"lint": "eslint . && echo 'Lint finished.'", | ||
"test": "riteway src/test.js | tap-nirvana", | ||
"test-coverage": "nyc npm test", | ||
"test-coverage-ci": "nyc --reporter=text-lcov npm test", | ||
"show-coverage-ci": "nyc report --reporter=text-lcov | coveralls", | ||
"show-coverage-text": "nyc report --reporter=text || echo \"Run 'npm run test-coverage' first.\"", | ||
"show-coverage-html": "open ./coverage/index.html || echo \"Run 'npm run test-coverage' first.\"", | ||
"debug": "echo 'Open debugger in Chrome: \"chrome://inspect\".' && node --inspect-brk src/test.js", | ||
"watch": "watch 'clear && npm -s test' src", | ||
"prepare": "npm run -s lint && npm -s test && prepublish", | ||
"update": "updtr" | ||
@@ -33,7 +38,13 @@ }, | ||
"devDependencies": { | ||
"eslint": "5.6.1", | ||
"coveralls": "3.0.2", | ||
"eslint": "5.13.0", | ||
"eslint-config-prettier": "4.0.0", | ||
"eslint-plugin-prettier": "3.0.1", | ||
"husky": "1.3.1", | ||
"lint-staged": "8.1.4", | ||
"nyc": "13.3.0", | ||
"prepublish": "2.2.0", | ||
"riteway": "3.0.0", | ||
"tap-colorize": "1.2.0", | ||
"tape": "4.9.1", | ||
"prettier": "1.16.4", | ||
"riteway": "4.0.1", | ||
"tap-nirvana": "1.1.0", | ||
"updtr": "3.1.0", | ||
@@ -40,0 +51,0 @@ "watch": "1.0.2" |
173
src/index.js
const get = require('lodash/fp/get'); | ||
const capitalize = require('lodash/upperFirst'); | ||
const { SLICE_VALUE_ERROR } = require('./errors'); | ||
const id = x => x; | ||
const selectIf = predicate => x => predicate(x) && x; | ||
const isFunction = f => (typeof f === 'function'); | ||
const isFunction = f => typeof f === 'function'; | ||
const isString = s => typeof s === 'string'; | ||
const isNumber = n => typeof n === 'number'; | ||
const isBoolean = b => typeof b === 'boolean'; | ||
const isUndefined = v => typeof v === 'undefined'; | ||
const isNull = v => v === null; | ||
const isPrimitive = v => | ||
[isString, isNumber, isBoolean, isUndefined, isNull].some(f => f(v)); | ||
const isEmptyString = s => s === ''; | ||
const selectFunction = selectIf(isFunction); | ||
// # Selector creation: | ||
const toGetter = s => `get${ capitalize(s) }`; | ||
const toGetter = s => `get${capitalize(s)}`; | ||
const sliceSelector = (slice, fn) => state => fn(state[slice], state); | ||
const createInitSelectors = (slice, initial) => Object.keys(initial).reduce( | ||
(obj, selector) => slice ? | ||
Object.assign(obj, {[toGetter(selector)]: sliceSelector(slice, state => get(selector, state)) }) : | ||
Object.assign(obj, {[toGetter(selector)]: state => get(selector, state) }), | ||
{} | ||
); | ||
const createInitSelectors = (slice, initial) => | ||
Object.keys(initial).reduce( | ||
(obj, selector) => | ||
slice | ||
? Object.assign(obj, { | ||
[toGetter(selector)]: sliceSelector(slice, state => | ||
get(selector, state) | ||
) | ||
}) | ||
: Object.assign(obj, { | ||
[toGetter(selector)]: state => get(selector, state) | ||
}), | ||
{} | ||
); | ||
const createSliceSelectors = (slice, selectors) => ( | ||
Object.assign(Object.keys(selectors).reduce( | ||
(obj, selector) => slice ? | ||
Object.assign(obj, {[selector]: sliceSelector(slice, selectors[selector]) }) : | ||
Object.assign(obj, {[selector]: selectors[selector] }), | ||
{} | ||
), { | ||
[toGetter(slice)]: sliceSelector(slice, id) | ||
}) | ||
); | ||
const createSliceSelectors = (slice, selectors) => | ||
Object.assign( | ||
Object.keys(selectors).reduce( | ||
(obj, selector) => | ||
slice | ||
? Object.assign(obj, { | ||
[selector]: sliceSelector(slice, selectors[selector]) | ||
}) | ||
: Object.assign(obj, { [selector]: selectors[selector] }), | ||
{} | ||
), | ||
{ | ||
[toGetter(slice)]: sliceSelector(slice, id) | ||
} | ||
); | ||
// /selector creation | ||
// # Action creation | ||
const getActionName = key => `set${ capitalize(key) }`; | ||
const getActionName = key => `set${capitalize(key)}`; | ||
const createAction = ( | ||
slice, action, key, | ||
type = `${ slice }/${ action }` | ||
) => ({ | ||
const createAction = (slice, action, key, type = `${slice}/${action}`) => ({ | ||
create: Object.assign(payload => payload, { type }), | ||
reducer: (state, payload) => Object.assign({}, | ||
state, | ||
(key) | ||
? { [key]: payload } | ||
: payload | ||
) | ||
reducer: (state, payload) => | ||
!key && isPrimitive(payload) | ||
? payload | ||
: Object.assign({}, state, key ? { [key]: payload } : payload) | ||
}); | ||
const getInitialActions = (slice, initial) => ( | ||
const getInitialActions = (slice, initial) => | ||
Object.keys(initial).reduce((o, key) => { | ||
@@ -55,4 +73,3 @@ const action = getActionName(key); | ||
}); | ||
}, {}) | ||
); | ||
}, {}); | ||
@@ -63,3 +80,4 @@ const addDefaultActions = (slice, initial, actions) => { | ||
return Object.assign({}, | ||
return Object.assign( | ||
{}, | ||
{ | ||
@@ -73,25 +91,36 @@ [action]: createAction(slice, action) | ||
const createMappedActions = (slice, actions) => Object.keys(actions).reduce( | ||
(obj, action) => Object.assign({}, obj, { | ||
[action]: Object.assign( | ||
(...args) => ({ | ||
type: `${ slice }/${ action }`, | ||
payload: typeof actions[action].create === 'function' ? | ||
actions[action].create(...args) : | ||
args[0] | ||
const createMappedActions = (slice, actions) => | ||
Object.keys(actions).reduce( | ||
(obj, action) => | ||
Object.assign({}, obj, { | ||
[action]: Object.assign( | ||
(...args) => ({ | ||
type: `${slice}/${action}`, | ||
payload: | ||
typeof actions[action].create === 'function' | ||
? actions[action].create(...args) | ||
: args[0] | ||
}), | ||
{ type: `${slice}/${action}` } | ||
) | ||
}), | ||
{ type: `${ slice }/${ action }` } | ||
) | ||
}), | ||
{} | ||
); | ||
{} | ||
); | ||
// /action creation | ||
const autodux = ({ | ||
initial = '', | ||
actions = {}, | ||
selectors = {}, | ||
slice = '' | ||
} = {}) => { | ||
const allSelectors = Object.assign({}, | ||
const isSliceValid = slice => isString(slice) && !isEmptyString(slice); | ||
const checkOptions = ({ slice }) => { | ||
if (!isSliceValid(slice)) { | ||
throw new Error(SLICE_VALUE_ERROR); | ||
} | ||
}; | ||
const autodux = (options = {}) => { | ||
checkOptions(options); | ||
const { initial = '', actions = {}, selectors = {}, slice } = options; | ||
const allSelectors = Object.assign( | ||
{}, | ||
createInitSelectors(slice, initial), | ||
@@ -101,10 +130,11 @@ createSliceSelectors(slice, selectors) | ||
const allActions = createMappedActions(slice, | ||
const allActions = createMappedActions( | ||
slice, | ||
addDefaultActions(slice, initial, actions) | ||
); | ||
const reducer = (state = initial, {type, payload} = {}) => { | ||
const [ namespace, subType ] = type ? | ||
type.split('/') : | ||
'unknown/unknown'.split('/'); | ||
const reducer = (state = initial, { type, payload } = {}) => { | ||
const [namespace, subType] = type | ||
? type.split('/') | ||
: 'unknown/unknown'.split('/'); | ||
@@ -119,20 +149,20 @@ const defaultActions = addDefaultActions(slice, initial, {}); | ||
const actionReducer = [ | ||
get(`${ subType }.reducer`, actions), | ||
get(`${subType}.reducer`, actions), | ||
actions[subType], | ||
get(`${ subType }.reducer`, defaultActions) | ||
get(`${subType}.reducer`, defaultActions) | ||
].reduceRight((f, v) => selectFunction(v) || f); | ||
return (namespace === slice | ||
&& (actions[subType] || defaultActions[subType]) | ||
) ? | ||
(actionReducer) ? | ||
actionReducer(state, payload) : | ||
Object.assign({}, state, payload) : | ||
state | ||
; | ||
return namespace === slice && (actions[subType] || defaultActions[subType]) | ||
? actionReducer | ||
? actionReducer(state, payload) | ||
: Object.assign({}, state, payload) | ||
: state; | ||
}; | ||
return { | ||
initial, reducer, slice, | ||
selectors: allSelectors, actions: allActions, | ||
initial, | ||
reducer, | ||
slice, | ||
selectors: allSelectors, | ||
actions: allActions | ||
}; | ||
@@ -148,3 +178,2 @@ }; | ||
[key]: payload | ||
}) | ||
; | ||
}); |
633
src/test.js
@@ -1,64 +0,96 @@ | ||
const { describe } = require('riteway'); | ||
const { describe, Try } = require('riteway'); | ||
const { SLICE_VALUE_ERROR } = require('./errors'); | ||
const autodux = require('./'); | ||
const id = autodux.id; | ||
const assign = autodux.assign; | ||
const createDux = () => autodux({ | ||
slice: 'counter', | ||
initial: 0, | ||
actions: { | ||
increment: { | ||
reducer: state => state + 1 | ||
const { id, assign } = autodux; | ||
const createCounterDux = (initial = 0) => | ||
autodux({ | ||
// The slice of state your reducer controls. | ||
slice: 'counter', | ||
// The initial value for the slice of state. | ||
initial, | ||
// No need to implement switching logic, it's done for you. | ||
actions: { | ||
// Shorthand definition of action and corresponding reducer. | ||
increment: state => state + 1, | ||
decrement: state => state - 1, | ||
// Another way to define action and reducer. | ||
multiply: { | ||
// Define custom mapping of action creator parameter to action payload. | ||
create: ({ by }) => by, | ||
reducer: (state, payload) => state * payload | ||
}, | ||
// Define action and reducer without custom mapping of action creator parameter to action payload. | ||
divide: { | ||
reducer: (state, payload) => Math.floor(state / payload) | ||
} | ||
}, | ||
decrement: { | ||
reducer: state => state - 1 | ||
}, | ||
multiply: { | ||
create: id, | ||
reducer: (state, payload) => state * payload | ||
selectors: { | ||
// No need to select the state slice, it's done for you. | ||
getValue: id, | ||
// Get access to the root state in case you need it. | ||
getState: (_, rootState) => rootState | ||
} | ||
}, | ||
selectors: { | ||
getValue: id, | ||
getStore: (_, store) => store | ||
} | ||
}); | ||
}); | ||
describe('autodux().slice', async assert => { | ||
describe('autodux({ … }).slice', async assert => { | ||
assert({ | ||
given: 'autodux is called with args', | ||
should: 'have the correct string value', | ||
actual: createDux().slice, | ||
expected: 'counter', | ||
given: "'autodux' is called with 'slice'", | ||
should: 'have the correct value', | ||
actual: createCounterDux().slice, | ||
expected: 'counter' | ||
}); | ||
}); | ||
describe('autodux().actions', async assert => { | ||
describe('autodux({ … }).initial', async assert => { | ||
assert({ | ||
given: "'autodux' is called with 'slice' and 'initial'", | ||
should: 'return valid initial state', | ||
actual: createCounterDux().initial, | ||
expected: 0 | ||
}); | ||
assert({ | ||
given: 'autodux is called with args', | ||
given: "'autodux' is called without 'initial'", | ||
should: 'return initial state as an empty string', | ||
actual: autodux({ slice: 'user' }).initial, | ||
expected: '' | ||
}); | ||
}); | ||
describe('autodux({ … }).actions', async assert => { | ||
assert({ | ||
given: "'autodux' is called with 'slice' and 'actions'", | ||
should: 'contain action creators', | ||
actual: Object.keys(createDux().actions), | ||
expected: ['setCounter', 'increment', 'decrement', 'multiply'] | ||
actual: Object.keys(createCounterDux().actions), | ||
expected: ['setCounter', 'increment', 'decrement', 'multiply', 'divide'] | ||
}); | ||
{ | ||
const { actions } = createDux(); | ||
const actual = [ | ||
actions.increment(), | ||
actions.decrement(), | ||
actions.multiply(2) | ||
]; | ||
const expected = [ | ||
{ type: 'counter/increment', payload: undefined }, | ||
{ type: 'counter/decrement', payload: undefined }, | ||
{ type: 'counter/multiply', payload: 2 }, | ||
]; | ||
const { actions } = createCounterDux(); | ||
assert({ | ||
given: 'autodux is called with args', | ||
should: 'produce correct action objects', | ||
actual, | ||
expected | ||
given: "'autodux' is called with 'slice' and 'actions'", | ||
should: 'contain action creators that return correct action objects', | ||
actual: [ | ||
actions.increment(), | ||
actions.decrement(), | ||
actions.multiply({ by: 2 }), | ||
actions.divide(1) | ||
], | ||
expected: [ | ||
{ type: 'counter/increment', payload: undefined }, | ||
{ type: 'counter/decrement', payload: undefined }, | ||
{ type: 'counter/multiply', payload: 2 }, | ||
{ type: 'counter/divide', payload: 1 } | ||
] | ||
}); | ||
@@ -69,57 +101,36 @@ } | ||
const { | ||
actions: { | ||
setCounter, | ||
increment, | ||
decrement, | ||
multiply | ||
} | ||
} = createDux(); | ||
actions: { setCounter, increment, decrement, multiply, divide } | ||
} = createCounterDux(); | ||
const actual = [ | ||
setCounter.type, | ||
increment.type, | ||
decrement.type, | ||
multiply.type | ||
]; | ||
const expected = [ | ||
'counter/setCounter', | ||
'counter/increment', | ||
'counter/decrement', | ||
'counter/multiply' | ||
]; | ||
assert({ | ||
given: 'autodux is called with args', | ||
should: 'produce namespaced action type constants', | ||
actual, | ||
expected | ||
given: "'autodux' is called with 'slice' and 'actions'", | ||
should: 'contain action creators with correct action type constants', | ||
actual: [ | ||
setCounter.type, | ||
increment.type, | ||
decrement.type, | ||
multiply.type, | ||
divide.type | ||
], | ||
expected: [ | ||
'counter/setCounter', | ||
'counter/increment', | ||
'counter/decrement', | ||
'counter/multiply', | ||
'counter/divide' | ||
] | ||
}); | ||
} | ||
}); | ||
describe('autodux().reducer', async assert => { | ||
{ | ||
const { | ||
actions: { | ||
increment, | ||
decrement | ||
}, | ||
reducer, | ||
initial | ||
} = createDux(); | ||
const { actions } = autodux({ | ||
slice: 'words' | ||
}); | ||
const actions = [ | ||
increment(), | ||
increment(), | ||
increment(), | ||
decrement() | ||
]; | ||
assert({ | ||
given: 'a reducer', | ||
should: 'switch correctly', | ||
actual: actions.reduce(reducer, initial), | ||
expected: 2 | ||
given: "'autodux' is called with 'slice' and without 'actions'", | ||
should: | ||
"contain a single action creator ('set${slice}') for setting the state of the slice", | ||
actual: Object.keys(actions).length, | ||
expected: 1 | ||
}); | ||
@@ -129,62 +140,27 @@ } | ||
{ | ||
const { | ||
actions: { | ||
increment, | ||
multiply | ||
}, | ||
reducer, | ||
initial | ||
} = createDux(); | ||
const { actions } = createCounterDux(); | ||
const actions = [ | ||
increment(), | ||
increment(), | ||
multiply(2) | ||
]; | ||
const value = 50; | ||
assert({ | ||
given: 'a reducer', | ||
should: 'deliver action payloads', | ||
actual: actions.reduce(reducer, initial), | ||
expected: 4 | ||
given: "'autodux' is called with 'slice' and 'actions'", | ||
should: | ||
"contain action creator ('set${slice}') for setting the state of the slice", | ||
actual: actions.setCounter(value), | ||
expected: { | ||
type: 'counter/setCounter', | ||
payload: value | ||
} | ||
}); | ||
} | ||
}); | ||
describe('autodux().selectors', async assert => { | ||
const { getValue } = createDux().selectors; | ||
{ | ||
assert({ | ||
given: 'a property and value', | ||
should: 'return a selector that knows its state slice', | ||
actual: getValue({ counter: 3 }), | ||
expected: 3 | ||
}); | ||
} | ||
const { actions, reducer, initial } = createCounterDux(128); | ||
{ | ||
const initial = { | ||
key1: 'value 1', | ||
key2: 'value 2' | ||
}; | ||
const store = { | ||
slice: initial | ||
}; | ||
const { selectors: { getKey1, getKey2 } } = autodux({ | ||
slice: 'slice', | ||
initial | ||
}); | ||
const actual = { | ||
key1: getKey1(store), | ||
key2: getKey2(store) | ||
}; | ||
assert({ | ||
given: 'a property and value', | ||
should: 'expose a selector for each key in the initial state', | ||
actual, | ||
expected: initial | ||
given: "'autodux' is called with 'slice' and 'actions'", | ||
should: | ||
'return action creator that maps parameters to action payload by default', | ||
actual: [actions.divide(2)].reduce(reducer, initial), | ||
expected: 64 | ||
}); | ||
@@ -194,14 +170,4 @@ } | ||
{ | ||
const initial = { | ||
userName: 'Anonymous', | ||
avatar: 'anonymous.png' | ||
}; | ||
const store = { | ||
user: initial | ||
}; | ||
const { | ||
selectors: { | ||
getUser | ||
} | ||
actions: { setUserName, setAge } | ||
} = autodux({ | ||
@@ -211,53 +177,63 @@ slice: 'user', | ||
userName: 'Anonymous', | ||
avatar: 'anon.png' | ||
age: 0 | ||
} | ||
}); | ||
const userName = 'Freddie'; | ||
const age = 23; | ||
assert({ | ||
given: 'selectors', | ||
should: 'expose a selector for the entire reducer state', | ||
actual: getUser(store), | ||
expected: initial | ||
given: "'autodux' is called with 'slice' and without 'actions'", | ||
should: | ||
'contain correct action creators for each key in the initial state', | ||
actual: [setUserName(userName), setAge(age)], | ||
expected: [ | ||
{ | ||
type: 'user/setUserName', | ||
payload: userName | ||
}, | ||
{ | ||
type: 'user/setAge', | ||
payload: age | ||
} | ||
] | ||
}); | ||
} | ||
}); | ||
describe('autodux({ … }).reducer', async assert => { | ||
{ | ||
const { getStore } = createDux().selectors; | ||
const { | ||
actions: { increment, decrement }, | ||
reducer, | ||
initial | ||
} = createCounterDux(); | ||
assert({ | ||
given: 'a store', | ||
should: 'pass entire store as a second parameter to selectors', | ||
actual: getStore({ counter: 3, foo: 'bar' }), | ||
expected: { counter: 3, foo: 'bar' } | ||
given: "'autodux' is called with 'slice' and 'actions'", | ||
should: 'return reducer that switches correctly', | ||
actual: [increment(), increment(), increment(), decrement()].reduce( | ||
reducer, | ||
initial | ||
), | ||
expected: 2 | ||
}); | ||
} | ||
}); | ||
describe('autodux() action creators', async assert => { | ||
{ | ||
const { | ||
actions: { | ||
setUserName | ||
} | ||
} = autodux({ | ||
slice: 'user', | ||
initial: { | ||
userName: 'Anonymous', | ||
} | ||
}); | ||
const userName = 'Foo'; | ||
actions: { increment, multiply }, | ||
reducer, | ||
initial | ||
} = createCounterDux(); | ||
const actual = setUserName(userName); | ||
const expected = { | ||
type: 'user/setUserName', | ||
payload: userName, | ||
}; | ||
assert({ | ||
given: 'no action for supplied initial state', | ||
should: 'return an action creator which returns an action with the correct type and payload', | ||
actual, | ||
expected, | ||
given: "'autodux' is called with 'slice' and 'actions'", | ||
should: | ||
'return reducer that receives action payload as the second parameter', | ||
actual: [increment(), increment(), multiply({ by: 2 })].reduce( | ||
reducer, | ||
initial | ||
), | ||
expected: 4 | ||
}); | ||
@@ -267,23 +243,14 @@ } | ||
{ | ||
const initial = { name: 'Jim' }; | ||
const value = 'UserName'; | ||
const { actions } = autodux({ | ||
slice: 'emptyCreator', | ||
actions: { | ||
nothing: { | ||
reducer: x => x | ||
} | ||
} | ||
const { reducer } = autodux({ | ||
slice: 'user', | ||
initial | ||
}); | ||
const expected = { | ||
type: 'emptyCreator/nothing', | ||
payload: value | ||
}; | ||
assert({ | ||
given: 'no action creators', | ||
should: 'should default missing action creators to identity', | ||
actual: actions.nothing(value), | ||
expected | ||
given: "'autodux' is called with 'slice' and without 'actions'", | ||
should: 'return reducer that returns valid default state', | ||
actual: reducer(), | ||
expected: initial | ||
}); | ||
@@ -293,39 +260,48 @@ } | ||
{ | ||
const value = 'UserName'; | ||
const { actions } = autodux({ | ||
slice: 'emptyCreator', | ||
actions: { | ||
nothing: { | ||
reducer: x => x | ||
} | ||
} | ||
}); | ||
const { | ||
actions: { setInfo }, | ||
reducer, | ||
initial | ||
} = autodux({ slice: 'info', initial: 'Some text goes here…' }); | ||
const expected = { | ||
type: 'emptyCreator/nothing', | ||
payload: value | ||
}; | ||
assert({ | ||
given: 'no reducer', | ||
should: 'default missing reducer to spread payload into state', | ||
actual: actions.nothing(value), | ||
expected | ||
given: "'autodux' is called with 'slice' and without 'actions'", | ||
should: | ||
'return reducer that changes the state to the primitive value of action payload', | ||
actual: [ | ||
[setInfo('Hi!')].reduce(reducer, initial), | ||
[setInfo(9)].reduce(reducer, initial), | ||
[setInfo(undefined)].reduce(reducer, initial), | ||
[setInfo(true)].reduce(reducer, initial), | ||
[setInfo(null)].reduce(reducer, initial) | ||
], | ||
expected: ['Hi!', 9, undefined, true, null] | ||
}); | ||
} | ||
}); | ||
describe('autodux({ … }).selectors', async assert => { | ||
{ | ||
const initial = { a: 'a' }; | ||
const rootState = { | ||
album: { | ||
name: 'The Works', | ||
year: 1984 | ||
} | ||
}; | ||
const { reducer } = autodux({ | ||
initial, | ||
actions: { | ||
reducer: x => x | ||
} | ||
const { | ||
selectors: { getName, getYear }, | ||
initial | ||
} = autodux({ | ||
slice: 'album', | ||
initial: rootState.album | ||
}); | ||
assert({ | ||
given: 'no args', | ||
should: 'return valid default state', | ||
actual: reducer(), | ||
given: "'autodux' is called with 'slice' and 'initial'", | ||
should: 'expose a selector for each key in the initial state', | ||
actual: { | ||
name: getName(rootState), | ||
year: getYear(rootState) | ||
}, | ||
expected: initial | ||
@@ -336,50 +312,46 @@ }); | ||
{ | ||
const rootState = { | ||
user: { | ||
userName: 'Anonymous', | ||
avatar: 'anonymous.png' | ||
} | ||
}; | ||
const { | ||
reducer, | ||
actions: { | ||
increment, | ||
decrement, | ||
multiply | ||
}, | ||
selectors: { | ||
getValue | ||
} | ||
selectors: { getUser }, | ||
initial | ||
} = autodux({ | ||
// the slice of state your reducer controls | ||
slice: 'counter', | ||
slice: 'user', | ||
initial: rootState.user | ||
}); | ||
// The initial value of your reducer state | ||
initial: 0, | ||
assert({ | ||
given: "'autodux' is called with 'slice' and 'initial'", | ||
should: 'expose a selector for the entire state of the slice', | ||
actual: getUser(rootState), | ||
expected: initial | ||
}); | ||
} | ||
// No need to implement switching logic -- it's | ||
// done for you. | ||
actions: { | ||
increment: state => state + 1, | ||
decrement: state => state - 1, | ||
multiply: { | ||
create: ({ by }) => by, | ||
reducer: (state, payload) => state * payload | ||
} | ||
}, | ||
{ | ||
const { getValue } = createCounterDux().selectors; | ||
// No need to select the state slice -- it's done for you. | ||
selectors: { | ||
getValue: id | ||
} | ||
assert({ | ||
given: "'autodux' is called with 'slice' and 'selectors'", | ||
should: 'return a selector that knows its state slice', | ||
actual: getValue({ counter: 3 }), | ||
expected: 3 | ||
}); | ||
} | ||
const state = [ | ||
increment(), | ||
increment(), | ||
increment(), | ||
decrement(), | ||
multiply({ by: 2 }) | ||
].reduce(reducer, undefined); | ||
{ | ||
const { getState } = createCounterDux().selectors; | ||
const rootState = { counter: 3, foo: 'bar' }; | ||
assert({ | ||
given: 'functions as action values', | ||
should: 'use function as a reducer', | ||
actual: getValue({ counter: state }), | ||
expected: 4 | ||
given: "'autodux' is called with 'slice' and 'selectors'", | ||
should: 'return a selector that can return the root state object', | ||
actual: getState(rootState), | ||
expected: rootState | ||
}); | ||
@@ -389,9 +361,29 @@ } | ||
describe('autodux/assign(key)', async assert => { | ||
describe('autodux()', async assert => { | ||
assert({ | ||
given: "'autodux' is called without an argument", | ||
should: 'throw an error', | ||
actual: Try(autodux).toString(), | ||
expected: new Error(SLICE_VALUE_ERROR).toString() | ||
}); | ||
}); | ||
describe("autodux({ …, slice: undefined | null | '' })", async assert => { | ||
const error = new Error(SLICE_VALUE_ERROR).toString(); | ||
assert({ | ||
given: "'autodux' is called with improper 'slice' value", | ||
should: 'throw an error', | ||
actual: [ | ||
Try(autodux, { slice: undefined }).toString(), | ||
Try(autodux, { slice: null }).toString(), | ||
Try(autodux, { slice: '' }).toString() | ||
], | ||
expected: [error, error, error] | ||
}); | ||
}); | ||
describe('assign(key)', async assert => { | ||
const { | ||
actions: { | ||
setUserName, | ||
setAvatar | ||
}, | ||
actions: { setUserName, setAvatar }, | ||
reducer | ||
@@ -409,90 +401,19 @@ } = autodux({ | ||
}); | ||
const userName = 'Foo'; | ||
const avatar = 'foo.png'; | ||
const actual = [ | ||
setUserName(userName), | ||
setAvatar(avatar) | ||
].reduce(reducer, undefined); | ||
const expected = { | ||
userName, | ||
avatar | ||
}; | ||
assert({ | ||
given: 'default actions (without action keys)', | ||
should: 'set the key in the state to the payload value', | ||
actual, | ||
expected, | ||
}); | ||
}); | ||
describe('autodux/default', async assert => { | ||
{ | ||
const { | ||
actions: { | ||
setUser | ||
}, | ||
reducer | ||
} = autodux({ | ||
slice: 'user', | ||
initial: { | ||
userName: 'Anonymous', | ||
avatar: 'anonymous.png' | ||
} | ||
}); | ||
const userName = 'Foo'; | ||
const avatar = 'foo.png'; | ||
const actual = reducer(undefined, setUser({ userName, avatar })); | ||
const expected = { | ||
given: 'a key', | ||
should: | ||
'return a reducer that sets the key in the state to the action payload value', | ||
actual: [setUserName(userName), setAvatar(avatar)].reduce( | ||
reducer, | ||
undefined | ||
), | ||
expected: { | ||
userName, | ||
avatar | ||
}; | ||
assert({ | ||
given: 'actions (without action keys)', | ||
should: 'create `set${slice}` to spread payload into state', | ||
actual, | ||
expected, | ||
}); | ||
} | ||
{ | ||
const { | ||
actions: { | ||
setUserName, | ||
setAvatar | ||
}, | ||
reducer | ||
} = autodux({ | ||
slice: 'user', | ||
initial: { | ||
userName: 'Anonymous', | ||
avatar: 'anonymous.png' | ||
} | ||
}); | ||
const userName = 'Foo'; | ||
const avatar = 'foo.png'; | ||
const actual = [ | ||
setUserName(userName), | ||
setAvatar(avatar) | ||
].reduce(reducer, undefined); | ||
const expected = { | ||
userName, | ||
avatar | ||
}; | ||
assert({ | ||
given: 'actions (without action keys)', | ||
should: 'create assignment actions for each key in initial', | ||
actual, | ||
expected, | ||
}); | ||
} | ||
} | ||
}); | ||
}); | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
66856
19
902
13
1