diff --git a/src/__tests__/camelCase-test.js b/src/__tests__/camelCase-test.js new file mode 100644 index 00000000..3bc7408e --- /dev/null +++ b/src/__tests__/camelCase-test.js @@ -0,0 +1,16 @@ +import camelCase from '../camelCase'; +import { expect } from 'chai'; + +describe('camelCase', () => { + it('should camel case a conventional action type', () => { + expect(camelCase('MY_ACTION')).to.equal('myAction'); + }); + + it('should include forward slashes in words', () => { + expect(camelCase('NAMESPACE/MY_ACTION')).to.equal('namespace/myAction'); + }); + + it('should do nothing to an already camel-cased action type', () => { + expect(camelCase('myAction')).to.equal('myAction'); + }); +}); diff --git a/src/__tests__/createActions-test.js b/src/__tests__/createActions-test.js index 8f6014f4..2f9c23f3 100644 --- a/src/__tests__/createActions-test.js +++ b/src/__tests__/createActions-test.js @@ -105,6 +105,22 @@ describe('createActions', () => { }); }); + it('should honor special delimiters in action types', () => { + const { 'p/actionOne': pActionOne, 'q/actionTwo': qActionTwo } = createActions({ + 'P/ACTION_ONE': (key, value) => ({ [key]: value }), + 'Q/ACTION_TWO': (first, second) => ([first, second]) + }); + + expect(pActionOne('value', 1)).to.deep.equal({ + type: 'P/ACTION_ONE', + payload: { value: 1 } + }); + expect(qActionTwo('value', 2)).to.deep.equal({ + type: 'Q/ACTION_TWO', + payload: ['value', 2] + }); + }); + it('should use the identity if the payload creator is undefined in array form', () => { const { action1, action2 } = createActions({ ACTION_1: [ diff --git a/src/camelCase.js b/src/camelCase.js new file mode 100644 index 00000000..d8214897 --- /dev/null +++ b/src/camelCase.js @@ -0,0 +1,14 @@ +// based on https://github.com/lodash/lodash/blob/4.17.2/lodash.js#L14100 +// eslint-disable-next-line max-len +const wordPattern = /[A-Z\xc0-\xd6\xd8-\xde]?[a-z\xdf-\xf6\xf8-\xff]+(?:['’](?:d|ll|m|re|s|t|ve))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde]|$)|(?:[A-Z\xc0-\xd6\xd8-\xde]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:D|LL|M|RE|S|T|VE))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde](?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])|$)|[A-Z\xc0-\xd6\xd8-\xde]?(?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:d|ll|m|re|s|t|ve))?|[A-Z\xc0-\xd6\xd8-\xde]+(?:['’](?:D|LL|M|RE|S|T|VE))?|\d*(?:(?:1ST|2ND|3RD|(?![123])\dTH)\b)|\d*(?:(?:1st|2nd|3rd|(?![123])\dth)\b)|\d+|(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff]|\ud83c[\udffb-\udfff])?)*/g; +const namespacer = '/'; + +function camelCase(string) { + return string.match(wordPattern).reduce((camelCased, word, index) => + camelCased + (index === 0 + ? word.toLowerCase() + : word.charAt(0).toUpperCase() + word.substring(1).toLowerCase()) + , ''); +} + +export default actionType => actionType.split(namespacer).map(camelCase).join(namespacer); diff --git a/src/createActions.js b/src/createActions.js index 509fb00d..7557cbc0 100644 --- a/src/createActions.js +++ b/src/createActions.js @@ -1,8 +1,7 @@ import identity from 'lodash/identity'; -import camelCase from 'lodash/camelCase'; +import camelCase from './camelCase'; import isPlainObject from 'lodash/isPlainObject'; import isArray from 'lodash/isArray'; -import reduce from 'lodash/reduce'; import isString from 'lodash/isString'; import isFunction from 'lodash/isFunction'; import createAction from './createAction'; @@ -32,7 +31,8 @@ function isValidActionsMapValue(actionsMapValue) { } function fromActionsMap(actionsMap) { - return reduce(actionsMap, (actionCreatorsMap, actionsMapValue, type) => { + return Object.keys(actionsMap).reduce((actionCreatorsMap, type) => { + const actionsMapValue = actionsMap[type]; invariant( isValidActionsMapValue(actionsMapValue), 'Expected function, undefined, or array with payload and meta ' + @@ -41,7 +41,6 @@ function fromActionsMap(actionsMap) { const actionCreator = isArray(actionsMapValue) ? createAction(type, ...actionsMapValue) : createAction(type, actionsMapValue); - return { ...actionCreatorsMap, [camelCase(type)]: actionCreator }; }, {}); }