diff --git a/examples/reducers.spec.ts b/examples/reducers.spec.ts index ccbc3c9d..c744aed8 100644 --- a/examples/reducers.spec.ts +++ b/examples/reducers.spec.ts @@ -1,4 +1,9 @@ -import { actionCreator, reducer, combineReducers, ReducerEntry } from 'rxbeach'; +import { + actionCreator, + reducer, + combineReducers, + RegisteredReducer, +} from 'rxbeach'; import test from 'ava'; import { marbles } from 'rxjs-marbles/ava'; @@ -8,7 +13,7 @@ const incrementMany = actionCreator('[increment] many'); // Our reducers type CounterState = number; -type CounterReducer = ReducerEntry; +type CounterReducer = RegisteredReducer; // Style 1 - Define the state type on state argument const handleOne = reducer(incrementOne, (prev: CounterState) => prev + 1); diff --git a/src/index.ts b/src/index.ts index e00f9d1e..4c0554f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ export { reducer, combineReducers, Reducer, - ReducerEntry, + RegisteredReducer, } from 'rxbeach/reducer'; export { diff --git a/src/reducer.spec.ts b/src/reducer.spec.ts index 13419afc..eab0eb2b 100644 --- a/src/reducer.spec.ts +++ b/src/reducer.spec.ts @@ -1,6 +1,7 @@ import { reducer, combineReducers, actionCreator } from 'rxbeach'; import test from 'ava'; import { marbles } from 'rxjs-marbles/ava'; +import sinon from 'sinon'; const throwErrorFn = (): number => { throw 'error'; @@ -9,10 +10,8 @@ const incrementOne = actionCreator('[increment] one'); const decrement = actionCreator('[increment] decrement'); const incrementMany = actionCreator('[increment] many'); -const handleOne = reducer( - incrementOne, - (accumulator: number) => accumulator + 1 -); +const incrementOneHandler = sinon.spy((accumulator: number) => accumulator + 1); +const handleOne = reducer(incrementOne, incrementOneHandler); const handleMany = reducer( incrementMany, (accumulator: number, increment) => accumulator + increment @@ -32,7 +31,9 @@ const outputs = { }; test('reducer should store reducer function', t => { - t.deepEqual(handleDecrement, [[decrement], throwErrorFn]); + incrementOneHandler.resetHistory(); + handleOne(1); + t.assert(incrementOneHandler.called); }); test( diff --git a/src/reducer.ts b/src/reducer.ts index 1c63f108..e770f969 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -13,10 +13,14 @@ export type Reducer = ( payload: Payload ) => State; -export type ReducerEntry = [ - UnknownActionCreator[], - Reducer -]; +export type RegisteredReducer = Reducer< + State, + Payload +> & { + trigger: { + actions: UnknownActionCreator[]; + }; +}; type ReducerCreator = { /** @@ -30,13 +34,13 @@ type ReducerCreator = { * @template `Payload` - The payload of the action, fed to the reducer together * with the state. Should be automatically extracted from * the `actionCreator` parameter - * @returns A reducer entry; A tuple array of actions and reducer, for passing - * into `combineReducers` + * @returns A registered reducer that can be passed into `combineReducers`, or + * called directly as if it was the `reducer` parameter itself. */ ( actionCreator: UnknownActionCreatorWithPayload, reducer: Reducer - ): ReducerEntry; + ): RegisteredReducer; /** * Define a reducer for an action without payload @@ -46,13 +50,13 @@ type ReducerCreator = { * extract payload type from * @param reducer The reducer function * @template `State` - The state the reducer reduces to - * @returns A reducer entry; A tuple array of actions and reducer, for passing - * into `combineReducers` + * @returns A registered reducer that can be passed into `combineReducers`, or + * called directly as if it was the `reducer` parameter itself. */ ( actionCreator: UnknownActionCreator, reducer: Reducer - ): ReducerEntry; + ): RegisteredReducer; /** * Define a reducer for multiple actions with overlapping payload @@ -65,13 +69,13 @@ type ReducerCreator = { * @template `Payload` - The payload of the action, fed to the reducer together * with the state. Should be automatically extracted from * the `actionCreator` parameter - * @returns A reducer entry; A tuple array of actions and reducer, for passing - * into `combineReducers` + * @returns A registered reducer that can be passed into `combineReducers`, or + * called directly as if it was the `reducer` parameter itself. */ ( actionCreator: UnknownActionCreatorWithPayload[], reducer: Reducer - ): ReducerEntry; + ): RegisteredReducer; /** * Define a reducer for multiple actions without overlapping payload @@ -81,13 +85,13 @@ type ReducerCreator = { * extract payload type from * @param reducer The reducer function * @template `State` - The state the reducer reduces to - * @returns A reducer entry; A tuple array of actions and reducer, for passing - * into `combineReducers` + * @returns A registered reducer that can be passed into `combineReducers`, or + * called directly as if it was the `reducer` parameter itself. */ ( actionCreator: UnknownActionCreatorWithPayload<{}>[], reducer: Reducer - ): ReducerEntry; + ): RegisteredReducer; /** * Define a reducer for multiple actions without payloads @@ -97,22 +101,25 @@ type ReducerCreator = { * extract payload type from * @param reducer The reducer function * @template `State` - The state the reducer reduces to - * @returns A reducer entry; A tuple array of actions and reducer, for passing - * into `combineReducers` + * @returns A registered reducer that can be passed into `combineReducers`, or + * called directly as if it was the `reducer` parameter itself. */ ( actionCreator: UnknownActionCreator[], reducer: Reducer - ): ReducerEntry; + ): RegisteredReducer; }; export const reducer: ReducerCreator = ( actionCreator: UnknownActionCreator | UnknownActionCreator[], reducerFn: Reducer -): ReducerEntry => [ - Array.isArray(actionCreator) ? actionCreator : [actionCreator], - reducerFn, -]; +): RegisteredReducer => { + const wrapper = (state: State, payload: any) => reducerFn(state, payload); + wrapper.trigger = { + actions: Array.isArray(actionCreator) ? actionCreator : [actionCreator], + }; + return wrapper; +}; /** * Combine reducer entries into a stream operator @@ -128,23 +135,23 @@ export const reducer: ReducerCreator = ( */ export const combineReducers = ( seed: State, - reducers: ReducerEntry[] + reducers: RegisteredReducer[] ): OperatorFunction => { const reducersByActionType = new Map( - reducers.flatMap(([actions, reducerEntry]) => - actions.map(action => [action.type, reducerEntry]) + reducers.flatMap(reducerFn => + reducerFn.trigger.actions.map(action => [action.type, reducerFn]) ) ); return pipe( - ofType(...reducers.flatMap(([action]) => action)), + ofType(...reducers.flatMap(reducerFn => reducerFn.trigger.actions)), scan((state, { type, payload }: UnknownAction) => { - const reducerEntry = reducersByActionType.get(type); - if (reducerEntry === undefined) { + const RegisteredReducer = reducersByActionType.get(type); + if (RegisteredReducer === undefined) { // This shouldn't be possible return state; } - return reducerEntry(state, payload); + return RegisteredReducer(state, payload); }, seed) ); };