Skip to content

Commit

Permalink
feat: default handler in createReducer
Browse files Browse the repository at this point in the history
related to #152
  • Loading branch information
Jazzmanpw authored and the-dr-lazy committed Oct 1, 2020
1 parent 53ab4d9 commit 0b3dfcf
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
exports[`createHandlerMap handle([increment, increase], (state: number) => state + 1) (type) should match snapshot 1`] = `"HandlerMap<number, { type: \\"[Counter] increment\\"; } | { type: \\"[Counter] increase\\"; }, number>"`;

exports[`createHandlerMap handle(increment, (state: number) => state + 1) (type) should match snapshot 1`] = `"HandlerMap<number, { type: \\"[Counter] increment\\"; }, number>"`;

exports[`createHandlerMap handle.default((state: number) => state + 1) (type) should match snapshot 1`] = `"{ default: Handler<number, any, number>; }"`;
2 changes: 2 additions & 0 deletions src/__tests__/__snapshots__/create-reducer.dts.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

exports[`createReducer counter scenario (type) should match snapshot 1`] = `"(state: number | undefined, action: { type: string; } | { type: \\"INCREMENT\\"; } | { type: \\"DECREMENT\\"; } | { type: \\"RESET\\"; payload: number; }) => number"`;

exports[`createReducer counter scenario (type) should match snapshot 2`] = `"(state: number | undefined, action: { type: string; } | { type: \\"INCREMENT\\"; }) => number"`;

exports[`createReducer immutable items list scenario (type) should match snapshot 1`] = `"(state: DeepImmutableArray<Item> | undefined, action: { type: string; } | { type: \\"IDENTITY\\"; } | { type: \\"ADD_ITEM\\"; payload: string; } | { type: \\"REMOVE_ITEM\\"; payload: string; }) => DeepImmutableArray<Item> | DeepImmutableObject<Item>[]"`;
exports[`createReducer mutable items list scenario (type) should match snapshot 1`] = `"(state: Item[] | undefined, action: { type: string; } | { type: \\"IDENTITY\\"; } | { type: \\"ADD_ITEM\\"; payload: string; } | { type: \\"REMOVE_ITEM\\"; payload: string; }) => Item[]"`;
3 changes: 3 additions & 0 deletions src/__tests__/create-handler-map.dts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ handle(increment, (state: number) => state + 1)

// @dts-jest:pass:snap
handle([increment, increase], (state: number) => state + 1)

// @dts-jest:pass:snap
handle.default((state: number) => state + 1)
5 changes: 5 additions & 0 deletions src/__tests__/create-handler-map.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ describe('createHandlerMap', () => {
handle([increment, increase], (state: number) => state + 1)
).toMatchSnapshot()
})

it('should put the handler by "default" key', () => {
const reducer = (state: number) => state + 1
expect(handle.default(reducer)).toEqual({ default: reducer });
})
})
10 changes: 10 additions & 0 deletions src/__tests__/create-reducer.dts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,13 @@ import { DeepImmutable } from '../types'
),
])
}

{
const increment = createActionCreator('INCREMENT')

// @dts-jest:pass:snap counter scenario
createReducer(0, handleAction => [
handleAction(increment, state => state + 1),
handleAction.default(() => 0),
])
}
127 changes: 88 additions & 39 deletions src/__tests__/create-reducer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,100 @@
import { createReducer } from '../create-reducer'
import { createActionCreator } from '../create-action-creator'
import { Action } from '../create-action'

describe('createReducer', () => {
const increment = createActionCreator('[Counter] increment')
const decrement = createActionCreator('[Counter] decrement')
const reset = createActionCreator(
'[Counter] reset',
resolve => (value: number) => resolve(value)
)

const handleIncrement = jest.fn((state: number) => state + 1)
const handleDecrement = jest.fn((state: number) => state - 1)
const handleReset = jest.fn(
(_: number, { payload }: ReturnType<typeof reset>) => payload
)

const defaultCounterState = 0
const counterReducer = createReducer(defaultCounterState, handleAction => [
handleAction(increment, handleIncrement),
handleAction(decrement, handleDecrement),
handleAction(reset, handleReset),
])

beforeEach(() => {
handleIncrement.mockReset()
handleDecrement.mockReset()
handleReset.mockReset()
})

it('should initiate with default state when state is undefined', () => {
expect(counterReducer(undefined, increment())).toBe(
counterReducer(defaultCounterState, increment())
describe('no default handler', () => {
const increment = createActionCreator('[Counter] increment')
const decrement = createActionCreator('[Counter] decrement')
const reset = createActionCreator(
'[Counter] reset',
resolve => (value: number) => resolve(value)
)
})

it('should pass through state when there is no proper handler', () => {
expect(counterReducer(defaultCounterState, { type: 'NOT DEFINED' })).toBe(
defaultCounterState
const handleIncrement = jest.fn((state: number) => state + 1)
const handleDecrement = jest.fn((state: number) => state - 1)
const handleReset = jest.fn(
(_: number, { payload }: ReturnType<typeof reset>) => payload
)

const defaultCounterState = 0
const counterReducer = createReducer(defaultCounterState, handleAction => [
handleAction(increment, handleIncrement),
handleAction(decrement, handleDecrement),
handleAction(reset, handleReset),
])

beforeEach(() => {
handleIncrement.mockReset()
handleDecrement.mockReset()
handleReset.mockReset()
})

it('should initiate with default state when state is undefined', () => {
expect(counterReducer(undefined, increment())).toBe(
counterReducer(defaultCounterState, increment())
)
})

it('should pass through state when there is no proper handler', () => {
expect(counterReducer(defaultCounterState, { type: 'NOT DEFINED' })).toBe(
defaultCounterState
)
})

it('should calls related handler of the given action', () => {
expect(counterReducer(defaultCounterState, increment)).toBe(
handleIncrement(defaultCounterState)
)
expect(handleIncrement).toBeCalledTimes(2)
expect(handleDecrement).not.toBeCalled()
expect(handleReset).not.toBeCalled()
})
})

it('should calls related handler of the given action', () => {
expect(counterReducer(defaultCounterState, increment)).toBe(
handleIncrement(defaultCounterState)
describe('default handler', () => {
const resetCounter = createActionCreator('[Counter] reset')
const drawNumber = createActionCreator(
'[Paint] draw number',
resolve => (payload: number) => resolve(payload)
)
expect(handleIncrement).toBeCalledTimes(2)
expect(handleDecrement).not.toBeCalled()
expect(handleReset).not.toBeCalled()

const defaultCounterState = 0

const handleReset = jest.fn(() => defaultCounterState);
const handleDefault = jest.fn((state: number, { payload }: Action<string, number>) => (
payload % 2 === 0 ? state + 1 : state
))

const evenNumbersCounterReducer = createReducer(defaultCounterState, handleAction => [
handleAction(resetCounter, handleReset),
handleAction.default(handleDefault)
])

beforeEach(() => {
handleReset.mockReset()
handleDefault.mockReset()
})

it('should initiate with default state when state is undefined', () => {
expect(evenNumbersCounterReducer(undefined, drawNumber(4))).toBe(
evenNumbersCounterReducer(defaultCounterState, drawNumber(4))
)
})

it('should calls related handler of the given action', () => {
expect(evenNumbersCounterReducer(5, resetCounter())).toBe(handleReset())

expect(handleReset).toBeCalledTimes(2)
expect(handleDefault).not.toBeCalled()
})

it('should calls the default handler when there is no proper handler', () => {
const action = drawNumber(6)

expect(evenNumbersCounterReducer(defaultCounterState, action)).toBe(
handleDefault(defaultCounterState, action)
)
})
})
})
51 changes: 39 additions & 12 deletions src/create-handler-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type InferNextStateFromHandlerMap<
THandlerMap extends HandlerMap<any, any>
> = THandlerMap extends HandlerMap<any, any, infer T> ? T : never

export type CreateHandlerMap<TPrevState> = <
export type CreateHandlerMap<TPrevState> = (<
TActionCreator extends ActionCreator<any>,
TNextState extends TPrevState,
TAction extends AnyAction = TActionCreator extends (...args: any[]) => infer T
Expand All @@ -26,24 +26,26 @@ export type CreateHandlerMap<TPrevState> = <
>(
actionCreators: TActionCreator | TActionCreator[],
handler: Handler<TPrevState, TAction, TNextState>
) => HandlerMap<TPrevState, TAction, TNextState>
) => HandlerMap<TPrevState, TAction, TNextState>) & {
default: <
TActionCreator extends ActionCreator<any>,
TNextState extends TPrevState,
TAction extends AnyAction = TActionCreator extends (...args: any[]) => infer T
? T
: never
>(
handler: Handler<TPrevState, TAction, TNextState>
) => { default: Handler<TPrevState, TAction, TNextState> }
}

/**
* Handler map factory
* @description create an action(s) to reducer map
* @example
* createHandlerMap(increment, (state: number) => state + 1)
* @example
* createHandlerMap([increment, increase], (state: number) => state + 1)
*/
export function createHandlerMap<
function handle<
TActionCreator extends ActionCreator<any>,
TPrevState,
TNextState extends TPrevState,
TAction extends AnyAction = TActionCreator extends (...args: any[]) => infer T
? T
: never
>(
>(
actionCreators: TActionCreator | TActionCreator[],
handler: Handler<TPrevState, TAction, TNextState>
) {
Expand All @@ -54,3 +56,28 @@ export function createHandlerMap<
return acc
}, {} as any)
}

handle.default = <
TActionCreator extends ActionCreator<any>,
TPrevState,
TNextState extends TPrevState,
TAction extends AnyAction = TActionCreator extends (...args: any[]) => infer T
? T
: never
>(
handler: Handler<TPrevState, TAction, TNextState>
) => ({ default: handler })

/**
* Handler map factory
* @description create an action(s) to reducer map
* @example
* createHandlerMap(increment, (state: number) => state + 1)
* @example
* createHandlerMap([increment, increase], (state: number) => state + 1)
* @example
* createHandlerMap.default((state: number) => state + 1)
*/
export const createHandlerMap = handle as typeof handle & { default: typeof handle.default }


4 changes: 3 additions & 1 deletion src/create-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export function createReducer<
): InferNextStateFromHandlerMap<THandlerMap> => {
const handler = handlerMap[(<any>action).type]

return handler ? handler(<any>state, action) : state
return handler ? handler(<any>state, action) :
handlerMap.default ? handlerMap.default(<any>state, action) :
state
}
}

0 comments on commit 0b3dfcf

Please sign in to comment.