From 4f7209bd19918af4d7d43e3d9e0088defc7fc5cc Mon Sep 17 00:00:00 2001 From: Alex Holmes Date: Fri, 9 Feb 2018 15:48:12 +0000 Subject: [PATCH 1/3] Add support for injecting a custom argument into epics Raised in issue #19 but dismissed in favour of a more elegant HOF approach, after four months with no project activity, I've gone for the zero-impact pragmatic solution --- src/combineEpics.js | 4 ++-- src/createEpicMiddleware.js | 6 +++--- tests/combineEpics.test.js | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/combineEpics.js b/src/combineEpics.js index 8e59583..19adce7 100644 --- a/src/combineEpics.js +++ b/src/combineEpics.js @@ -1,7 +1,7 @@ import { mergeArray } from 'most' import { findIndex, map } from '@most/prelude' -export const combineEpics = epicsArray => (actions, store) => { +export const combineEpics = epicsArray => (actions, store, dependencies) => { if (!epicsArray || !Array.isArray(epicsArray)) { throw new TypeError('You must provide an array of Epics to combineEpics.') } @@ -15,7 +15,7 @@ export const combineEpics = epicsArray => (actions, store) => { throw new TypeError('The array passed to combineEpics must contain only Epics (functions).') } - const out = epic(actions, store) + const out = epic(actions, store, dependencies) if (!out || !out.source) { const epicIdentifier = epic.name diff --git a/src/createEpicMiddleware.js b/src/createEpicMiddleware.js index ff0c460..e4fb329 100644 --- a/src/createEpicMiddleware.js +++ b/src/createEpicMiddleware.js @@ -3,7 +3,7 @@ import { async } from 'most-subject' import { epicBegin, epicEnd } from './actions' import { STATE_STREAM_SYMBOL } from './constants' -export const createEpicMiddleware = epic => { +export const createEpicMiddleware = (epic, dependencies) => { if (typeof epic !== 'function') { throw new TypeError('You must provide an Epic (a function) to createEpicMiddleware.') } @@ -33,9 +33,9 @@ export const createEpicMiddleware = epic => { return isUsingStateStreamEnhancer // new style API (declarative only, no dispatch/getState) - ? nextEpic(actionsIn$, state$) + ? nextEpic(actionsIn$, state$, dependencies) // redux-observable style Epic API - : nextEpic(actionsIn$, middlewareApi) + : nextEpic(actionsIn$, middlewareApi, dependencies) } const actionsOut$ = switchLatest(map(callNextEpic, epic$)) diff --git a/tests/combineEpics.test.js b/tests/combineEpics.test.js index 1c2b4ac..4f622b2 100644 --- a/tests/combineEpics.test.js +++ b/tests/combineEpics.test.js @@ -9,14 +9,15 @@ test('combineEpics should combine an array of epics', t => { const DELEGATED_1 = 'DELEGATED_1' const DELEGATED_2 = 'DELEGATED_2' const MOCKED_STORE = { I: 'am', a: 'store' } + const DEPENDENCIES = 'deps' const epic1 = (actions$, store) => map( action => ({ type: DELEGATED_1, action, store }), select(ACTION_1, actions$) ) - const epic2 = (actions$, store) => map( - action => ({ type: DELEGATED_2, action, store }), + const epic2 = (actions$, store, deps) => map( + action => ({ type: DELEGATED_2, action, store, deps }), select(ACTION_2, actions$) ) @@ -27,7 +28,7 @@ test('combineEpics should combine an array of epics', t => { const store = MOCKED_STORE const actions$ = sync() - const result$ = epic(actions$, store) + const result$ = epic(actions$, store, DEPENDENCIES) const emittedActions = [] observe(emittedAction => emittedActions.push(emittedAction), result$) @@ -37,7 +38,7 @@ test('combineEpics should combine an array of epics', t => { const MOCKED_EMITTED_ACTIONS = [ { type: DELEGATED_1, action: { type: ACTION_1 }, store }, - { type: DELEGATED_2, action: { type: ACTION_2 }, store }, + { type: DELEGATED_2, action: { type: ACTION_2 }, store, deps: DEPENDENCIES }, ] t.deepEqual(MOCKED_EMITTED_ACTIONS, emittedActions) From 4aeaa29ddee71d9f2f31d32b7a1a13b8a91d8b5f Mon Sep 17 00:00:00 2001 From: Alex Holmes Date: Mon, 7 May 2018 22:44:49 +0100 Subject: [PATCH 2/3] Add documentation for dependencies argument --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bced753..9c73ec3 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,48 @@ const someOtherEpic = pipe( ) ``` +## Dependencies + +When creating an `EpicMiddleware` object using `createEpicMiddleware` or +`createStateStreamEnhancer`, you can pass an optional `dependencies` argument which +will be passed to each of your epics as the third argument. This can then be used +when testing your epics to avoid a more complex mocking approach. + +__Example__ + +```js +// redux/configureStore.js +... +const fetchJSON = url => fetch(url).then(r => r.json()); +const epicMiddleware = createEpicMiddleware(rootEpic, { fetchJSON }) +... + +// epics/user.js +import { FETCH_USER } from '../constants/ActionTypes' +import { storeUser } from '../actions' +import { select } from 'redux-most' + +const fetchUserEpic = (action$, store, { fetchJSON }) => + action$.thru(select(FETCH_USER)) + .chain(({ userId }) => fromPromise(fetchJSON(`/users/${userId}`))) + .map(storeUser); + +// tests/user.js +it('fetches a user then emits a STORE_USER action', () => { + const mockFetchJSON = jest + .fn() + .mockReturnValueOnce(Promise.resolve({ username: 'foo' })); + const actionsIn = of(fetchUser({ userId: 5 })); + return fetchUserEpic(actionsIn, mockStore, { fetchJSON: mockFetchJSON }) + .reduce(flip(append), []) + .then(actionsOut => { + expect(actionsOut).toHaveLength(1); + expect(actionsOut[0].type).toEqual(STORE_USER); + }); +}); + +``` + ## API Reference - [createEpicMiddleware](https://github.com/joshburgess/redux-most#createepicmiddleware-rootepic) @@ -190,14 +232,15 @@ const someOtherEpic = pipe( --- -### `createEpicMiddleware (rootEpic)` +### `createEpicMiddleware (rootEpic, dependencies)` `createEpicMiddleware` is used to create an instance of the actual `redux-most` middleware. -You provide a single root `Epic`. +You provide a single root `Epic` and optional `dependencies`. __Arguments__ 1. `rootEpic` _(`Epic`)_: The root Epic. +2. `dependencies` _(`any`)_: Optional dependencies for your epics. __Returns__ @@ -236,6 +279,7 @@ other middleware. __Arguments__ 1. `rootEpic` _(`Epic`)_: The root Epic. +2. `dependencies` _(`any`)_: Optional dependencies for your epics. __Returns__ From 1ffd0c19ea9a8df6c4ed5d0dcdd0069e9d01d160 Mon Sep 17 00:00:00 2001 From: Alex Holmes Date: Fri, 11 May 2018 13:54:20 +0100 Subject: [PATCH 3/3] Update the typings --- index.d.ts | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/index.d.ts b/index.d.ts index 2e70916..188b858 100644 --- a/index.d.ts +++ b/index.d.ts @@ -11,43 +11,47 @@ import { Stream } from 'most'; A = Action T = ActionType (a string or symbol) S = State + D = Dependency *****************************************/ // for the original, redux-observable style API -export declare interface Epic { +export declare interface Epic { ( actionStream: Stream, - middlewareApi: MiddlewareAPI + middlewareApi: MiddlewareAPI, + dependencies: D ): Stream; } // for the newer, declarative only API, which takes in a state stream // to sample via the withState utility instead of exposing dispatch/getState -export declare interface Epic { +export declare interface Epic { ( actionStream: Stream, - stateStream: Stream + stateStream: Stream, + dependencies: D ): Stream; } -export interface EpicMiddleware extends Middleware { +export interface EpicMiddleware extends Middleware { replaceEpic ( - nextEpic: Epic + nextEpic: Epic ): void; } -export declare function createEpicMiddleware ( - rootEpic: Epic -): EpicMiddleware; +export declare function createEpicMiddleware ( + rootEpic: Epic, + dependencies: D +): EpicMiddleware; -export declare function createStateStreamEnhancer ( - epicMiddleware: EpicMiddleware +export declare function createStateStreamEnhancer ( + epicMiddleware: EpicMiddleware ): StoreEnhancer; -export declare function combineEpics ( - epicsArray: Epic[] -): Epic; +export declare function combineEpics ( + epicsArray: Epic[], +): Epic; export declare type ActionType = string | symbol