diff --git a/src/index.ts b/src/index.ts index 8bdd6c4e7c..26881bebfa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import combineReducers from './combineReducers' import bindActionCreators from './bindActionCreators' import applyMiddleware from './applyMiddleware' import compose from './compose' +import isAction from './utils/isAction' import __DO_NOT_USE__ActionTypes from './utils/actionTypes' // types @@ -42,5 +43,6 @@ export { bindActionCreators, applyMiddleware, compose, + isAction, __DO_NOT_USE__ActionTypes } diff --git a/src/utils/isAction.ts b/src/utils/isAction.ts new file mode 100644 index 0000000000..878f4a8096 --- /dev/null +++ b/src/utils/isAction.ts @@ -0,0 +1,10 @@ +import { Action } from '../types/actions' +import isPlainObject from './isPlainObject' + +export default function isAction(action: unknown): action is Action { + return ( + isPlainObject(action) && + 'type' in action && + typeof (action as Record<'type', unknown>).type === 'string' + ) +} diff --git a/src/utils/isPlainObject.ts b/src/utils/isPlainObject.ts index 9d1956328a..de6a260af0 100644 --- a/src/utils/isPlainObject.ts +++ b/src/utils/isPlainObject.ts @@ -2,7 +2,7 @@ * @param obj The object to inspect. * @returns True if the argument appears to be a plain object. */ -export default function isPlainObject(obj: any): boolean { +export default function isPlainObject(obj: any): obj is object { if (typeof obj !== 'object' || obj === null) return false let proto = obj diff --git a/test/utils/isAction.spec.ts b/test/utils/isAction.spec.ts new file mode 100644 index 0000000000..165495e902 --- /dev/null +++ b/test/utils/isAction.spec.ts @@ -0,0 +1,23 @@ +import isAction from '@internal/utils/isAction' + +describe('isAction', () => { + it('should only return true for plain objects with a string type property', () => { + const actionCreator = () => ({ type: 'anAction' }) + class Action { + type = 'totally an action' + } + const testCases: [action: unknown, expected: boolean][] = [ + [{ type: 'an action' }, true], + [{ type: 'more props', extra: true }, true], + [{ type: 0 }, false], + [actionCreator(), true], + [actionCreator, false], + [Promise.resolve({ type: 'an action' }), false], + [new Action(), false], + ['a string', false] + ] + for (const [action, expected] of testCases) { + expect(isAction(action)).toBe(expected) + } + }) +})