-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Features: - "redux-actions" compatible actions (FSA) - custom separators or stages * Thanks to https://github.com/afitiskin for the implementation proposal
- Loading branch information
Showing
5 changed files
with
120 additions
and
163 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,5 +39,8 @@ | |
"jest": "^20.0.4", | ||
"rimraf": "^2.6.1", | ||
"standard": "^10.0.2" | ||
}, | ||
"dependencies": { | ||
"redux-actions": "^2.3.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,37 @@ | ||
/** | ||
* redux-routines | ||
*/ | ||
const identity = f => f | ||
|
||
export const TRIGGER = 'TRIGGER' | ||
export const REQUEST = 'REQUEST' | ||
export const SUCCESS = 'SUCCESS' | ||
export const FAILURE = 'FAILURE' | ||
export const FULFILL = 'FULFILL' | ||
import { createAction } from 'redux-actions' | ||
|
||
export function prefixType (prefix, type) { | ||
if (typeof prefix !== 'string') { | ||
throw new Error('Invalid routine prefix. It should be string.') | ||
} | ||
return `${prefix}_${type}` | ||
// Default routine settings | ||
export const DEFAULT_SETTINGS = { | ||
separator: '/', | ||
stages: ['TRIGGER', 'REQUEST', 'SUCCESS', 'FAILURE', 'FULFILL'] | ||
} | ||
|
||
export function createAction (type, payload, ...args) { | ||
return Object.assign({}, ...args, { type: type, payload: payload }) | ||
// Routine action type factory | ||
export function createActionType(prefix, stage, separator) { | ||
if (typeof prefix !== 'string' || typeof stage !== 'string') { | ||
throw new Error('Invalid routine prefix or stage. It should be string.') | ||
} | ||
return `${prefix}${separator}${stage}` | ||
} | ||
|
||
export function createRoutine (prefix, enhancer = identity) { | ||
const routine = { | ||
TRIGGER: prefixType(prefix, TRIGGER), | ||
REQUEST: prefixType(prefix, REQUEST), | ||
SUCCESS: prefixType(prefix, SUCCESS), | ||
FAILURE: prefixType(prefix, FAILURE), | ||
FULFILL: prefixType(prefix, FULFILL), | ||
state: { | ||
trigger: false, | ||
request: false, | ||
success: false, | ||
failure: false, | ||
fulfill: false | ||
}, | ||
trigger (payload, ...args) { | ||
routine.state.trigger = true | ||
return enhancer(createAction(routine.TRIGGER, payload, ...args)) | ||
}, | ||
request (payload, ...args) { | ||
routine.state.request = true | ||
return enhancer(createAction(routine.REQUEST, payload, ...args)) | ||
}, | ||
success (payload, ...args) { | ||
routine.state.success = true | ||
routine.state.failure = false | ||
return enhancer(createAction(routine.SUCCESS, payload, ...args)) | ||
}, | ||
failure (payload, ...args) { | ||
routine.state.success = false | ||
routine.state.failure = true | ||
return enhancer(createAction(routine.FAILURE, payload, ...args)) | ||
}, | ||
fulfill (payload, ...args) { | ||
routine.state.fulfill = true | ||
return enhancer(createAction(routine.FULFILL, payload, ...args)) | ||
} | ||
// Routine factory | ||
export function createRoutine(prefix, payloadCreator, metaCreator, settings) { | ||
const { stages, separator } = Object.assign({}, DEFAULT_SETTINGS, settings) | ||
const createRoutineAction = stage => { | ||
const type = createActionType(prefix, stage, separator) | ||
return createAction(type, payloadCreator, metaCreator) | ||
} | ||
function call (payload, ...args) { | ||
return routine.trigger(payload, ...args) | ||
} | ||
return Object.assign(call, routine) | ||
return stages.reduce((routine, stage) => { | ||
const actionCreator = createRoutineAction(stage) | ||
return Object.assign(routine, { | ||
[stage.toLowerCase()]: actionCreator, | ||
[stage.toUpperCase()]: actionCreator.toString() | ||
}) | ||
}, createRoutineAction(stages[0])) | ||
} | ||
|
||
export default createRoutine |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,95 @@ | ||
/* global describe, it, expect */ | ||
import { createAction, createRoutine, prefixType } from './index' | ||
import { createRoutine, createActionType, DEFAULT_SETTINGS } from './index' | ||
|
||
const { stages } = DEFAULT_SETTINGS | ||
|
||
describe('createRoutine', () => { | ||
it('should be a function', () => { | ||
const routine = createRoutine('test') | ||
expect(typeof routine).toBe('function') | ||
}) | ||
it('should have initial state', () => { | ||
const { state } = createRoutine('test') | ||
expect(state.trigger).toBe(false) | ||
expect(state.request).toBe(false) | ||
expect(state.success).toBe(false) | ||
expect(state.failure).toBe(false) | ||
expect(state.fulfill).toBe(false) | ||
}) | ||
it('should have trigger state', () => { | ||
const routine = createRoutine('test') | ||
const { state } = routine | ||
expect(routine.trigger().type).toContain('TRIGGER') | ||
expect(state.trigger).toBe(true) | ||
expect(state.request).toBe(false) | ||
expect(state.success).toBe(false) | ||
expect(state.failure).toBe(false) | ||
expect(state.fulfill).toBe(false) | ||
}) | ||
it('should have request state', () => { | ||
it('should have all routine properties', () => { | ||
const routine = createRoutine('test') | ||
const { state } = routine | ||
expect(routine.request().type).toContain('REQUEST') | ||
expect(state.trigger).toBe(false) | ||
expect(state.request).toBe(true) | ||
expect(state.success).toBe(false) | ||
expect(state.failure).toBe(false) | ||
expect(state.fulfill).toBe(false) | ||
}) | ||
it('should have success state', () => { | ||
const routine = createRoutine('test') | ||
const { state } = routine | ||
expect(routine.success().type).toContain('SUCCESS') | ||
expect(state.trigger).toBe(false) | ||
expect(state.request).toBe(false) | ||
expect(state.success).toBe(true) | ||
expect(state.failure).toBe(false) | ||
expect(state.fulfill).toBe(false) | ||
stages.forEach(stage => { | ||
expect(routine).toHaveProperty(stage.toUpperCase()) | ||
expect(routine).toHaveProperty(stage.toLowerCase()) | ||
}) | ||
}) | ||
it('should have failure state', () => { | ||
it('should create all action types with default stages', () => { | ||
const routine = createRoutine('test') | ||
const { state } = routine | ||
expect(routine.failure().type).toContain('FAILURE') | ||
expect(state.trigger).toBe(false) | ||
expect(state.request).toBe(false) | ||
expect(state.success).toBe(false) | ||
expect(state.failure).toBe(true) | ||
expect(state.fulfill).toBe(false) | ||
stages.forEach(stage => { | ||
const actionCreator = routine[stage.toLowerCase()] | ||
const actionType = routine[stage] | ||
expect(actionType).toBe(`test/${stage}`) | ||
expect(String(actionCreator)).toBe(actionType) | ||
}) | ||
}) | ||
it('should have fulfill state', () => { | ||
it('should create all action creators with default stages', () => { | ||
const routine = createRoutine('test') | ||
const { state } = routine | ||
expect(routine.fulfill().type).toContain('FULFILL') | ||
expect(state.trigger).toBe(false) | ||
expect(state.request).toBe(false) | ||
expect(state.success).toBe(false) | ||
expect(state.failure).toBe(false) | ||
expect(state.fulfill).toBe(true) | ||
}) | ||
it('should modify payload with enhancer', () => { | ||
const routine = createRoutine('test', action => { | ||
action.payload += 1 | ||
return action | ||
stages.forEach(stage => { | ||
const actionCreator = routine[stage.toLowerCase()] | ||
const actionType = routine[stage] | ||
expect(actionCreator()).toEqual({ type: `test/${stage}` }) | ||
expect(actionCreator()).toEqual({ type: actionType }) | ||
}) | ||
expect(routine.trigger(0).payload).toBe(1) | ||
expect(routine.request(0).payload).toBe(1) | ||
expect(routine.success(0).payload).toBe(1) | ||
expect(routine.failure(0).payload).toBe(1) | ||
expect(routine.fulfill(0).payload).toBe(1) | ||
}) | ||
it('should trigger on routine invocation', () => { | ||
const routine = createRoutine('test') | ||
const action = routine() | ||
expect(action.type).toContain('TRIGGER') | ||
expect(routine.state.trigger).toBe(true) | ||
it('should create routine with payloadCreator', () => { | ||
const routine = createRoutine('test', val => val + 1) | ||
stages.forEach(stage => { | ||
const actionCreator = routine[stage.toLowerCase()] | ||
expect(actionCreator(0).payload).toBe(1) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('createAction', () => { | ||
it('should create an action object', () => { | ||
const type = 'TEST' | ||
const payload = {} | ||
const action = createAction(type, payload) | ||
expect(action).toMatchObject({ | ||
type: type, | ||
payload: payload | ||
it('should create routine with metaCreator', () => { | ||
const routine = createRoutine('test', null, () => ({ extra: true })) | ||
stages.forEach(stage => { | ||
const actionCreator = routine[stage.toLowerCase()] | ||
expect(actionCreator().meta).toEqual({ extra: true }) | ||
}) | ||
}) | ||
it('should create an action object with additional properties', () => { | ||
const type = 'TEST' | ||
const payload = {} | ||
const props = { test: true } | ||
const action = createAction(type, payload, props) | ||
expect(action).toMatchObject({ | ||
type: type, | ||
payload: payload, | ||
test: true | ||
it('should create routine with different separator', () => { | ||
const routine = createRoutine('test', null, null, { separator: '+' }) | ||
stages.forEach(stage => { | ||
const actionCreator = routine[stage.toLowerCase()] | ||
expect(actionCreator()).toEqual({ type: `test+${stage}` }) | ||
}) | ||
}) | ||
it('should not mutate action with additional properties', () => { | ||
const type = 'TEST' | ||
const props = { test: true } | ||
const action1 = createAction('type1', null, props) | ||
const action2 = createAction('type2', null, props) | ||
expect(action1.type).toContain('type1') | ||
expect(action2.type).toContain('type2') | ||
it('should create routine with explicit stages', () => { | ||
const stages = ['REQUEST', 'SUCCESS', 'FAILURE'] | ||
const routine = createRoutine('test', null, null, { stages: stages }) | ||
stages.forEach(stage => { | ||
expect(routine).toHaveProperty(stage.toUpperCase()) | ||
expect(routine).toHaveProperty(stage.toLowerCase()) | ||
}) | ||
expect(routine).not.toHaveProperty('TRIGGER') | ||
expect(routine).not.toHaveProperty('trigger') | ||
expect(routine).not.toHaveProperty('FULFILL') | ||
expect(routine).not.toHaveProperty('fulfill') | ||
}) | ||
}) | ||
|
||
describe('prefixType', () => { | ||
describe('createActionType', () => { | ||
it('should throws if prefix is not specified', () => { | ||
expect(prefixType).toThrow() | ||
expect(() => createActionType()).toThrow() | ||
}) | ||
it('should throws if prefix is a not string', () => { | ||
expect(() => prefixType(0)).toThrow() | ||
expect(() => prefixType(1)).toThrow() | ||
expect(() => prefixType(true)).toThrow() | ||
expect(() => prefixType(false)).toThrow() | ||
expect(() => prefixType({})).toThrow() | ||
expect(() => createActionType(0)).toThrow() | ||
expect(() => createActionType(1)).toThrow() | ||
expect(() => createActionType(true)).toThrow() | ||
expect(() => createActionType(false)).toThrow() | ||
expect(() => createActionType({})).toThrow() | ||
}) | ||
it('should throws if stage is a not string', () => { | ||
expect(() => createActionType('', 0)).toThrow() | ||
expect(() => createActionType('', 1)).toThrow() | ||
expect(() => createActionType('', true)).toThrow() | ||
expect(() => createActionType('', false)).toThrow() | ||
expect(() => createActionType('', {})).toThrow() | ||
}) | ||
it('should prefix type', () => { | ||
expect(prefixType('TEST', 'TYPE')).toBe('TEST_TYPE') | ||
expect(createActionType('TEST', 'TYPE', '_')).toBe('TEST_TYPE') | ||
}) | ||
it('should not modify prefix to uppercase', () => { | ||
expect(prefixType('@@test/TEST', 'TYPE')).toBe('@@test/TEST_TYPE') | ||
expect(createActionType('@test/TEST', 'TYPE', '_')).toBe('@test/TEST_TYPE') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters