diff --git a/Changes b/Changes index cc6f3f0..efc262f 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,10 @@ # Revision history for Updux +1.2.0 2019-11-06 + - The middleware's 'getState' returns the local state of its updux, + instead of the root state. Plus we add `getRootState` to get + the root state. + 1.1.0 2019-11-05 - Document mapping behavior of the '*' subdux. - add subduxUpreducer. diff --git a/package.json b/package.json index 4b050d4..0a28315 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "build": "tsc && typedoc", "test": "jest" }, - "version": "1.1.0", + "version": "1.2.0", "repository": { "type": "git", "url": "git+https://github.com/yanick/updux.git" diff --git a/src/buildMiddleware/index.ts b/src/buildMiddleware/index.ts index 228a8cd..4aaceaa 100644 --- a/src/buildMiddleware/index.ts +++ b/src/buildMiddleware/index.ts @@ -1,7 +1,7 @@ import fp from 'lodash/fp'; import { Middleware, MiddlewareAPI, Dispatch } from 'redux'; -import { Dictionary, ActionCreator, Action, UpduxDispatch } from '../types'; +import { Dictionary, ActionCreator, Action, UpduxDispatch, UpduxMiddleware } from '../types'; const MiddlewareFor = (type: any, mw: Middleware ): Middleware => api => next => action => { if (type !== '*' && action.type !== type) return next(action); @@ -11,12 +11,28 @@ const MiddlewareFor = (type: any, mw: Middleware ): Middleware => api => next => type Next = (action: Action) => any; +function sliceMw( slice: string, mw: Middleware ): Middleware { + return (api) => { + const getSliceState = () => fp.get(slice, api.getState() ); + const getRootState = (api as any).getRootState || api.getState; + return mw({...api, getState: getSliceState, getRootState} as any ) + }; +} + function buildMiddleware( effects : Dictionary>= {}, actions : Dictionary= {}, - subMiddlewares :Middleware<{},S,UpduxDispatch>[] = [], -): Middleware<{},S,UpduxDispatch> + subduxes :any = {}, +): UpduxMiddleware { + + const subMiddlewares = fp.flow( + fp.mapValues( fp.get('middleware') ), + fp.toPairs, + fp.filter(x=>x[1]), + fp.map( ([ slice, mw ]: [ string, Middleware]) => sliceMw(slice,mw) ) + )( subduxes ); + return (api: MiddlewareAPI) => { for (let type in actions) { diff --git a/src/middleware.test.ts b/src/middleware.test.ts index db04bea..d3478ec 100644 --- a/src/middleware.test.ts +++ b/src/middleware.test.ts @@ -1,110 +1,142 @@ import Updux from '.'; import u from 'updeep'; -test( 'simple effect', () => { +test('simple effect', () => { + const tracer = jest.fn(); - const tracer = jest.fn(); - - const store = (new Updux({ - effects: { - foo: (api:any) => (next:any) => (action:any) => { - tracer(); - next(action); - }, - }, - })).createStore(); - - expect(tracer).not.toHaveBeenCalled(); - - store.dispatch({ type: 'bar' }); - - expect(tracer).not.toHaveBeenCalled(); - - store.dispatch.foo(); - - expect(tracer).toHaveBeenCalled(); - -}); - -test( 'effect and sub-effect', () => { - - const tracer = jest.fn(); - - const tracerEffect = ( signature: string ) => ( api:any ) => (next:any) => ( action: any ) => { - tracer(signature); + const store = new Updux({ + effects: { + foo: (api: any) => (next: any) => (action: any) => { + tracer(); next(action); - }; + }, + }, + }).createStore(); - const store = (new Updux({ - effects: { - foo: tracerEffect('root'), - }, - subduxes: { - zzz: {effects: { - foo: tracerEffect('child'), - } - } - }, - })).createStore(); + expect(tracer).not.toHaveBeenCalled(); - expect(tracer).not.toHaveBeenCalled(); + store.dispatch({type: 'bar'}); - store.dispatch({ type: 'bar' }); + expect(tracer).not.toHaveBeenCalled(); - expect(tracer).not.toHaveBeenCalled(); - - store.dispatch.foo(); - - expect(tracer).toHaveBeenNthCalledWith(1,'root'); - expect(tracer).toHaveBeenNthCalledWith(2,'child'); + store.dispatch.foo(); + expect(tracer).toHaveBeenCalled(); }); -test( '"*" effect', () => { +test('effect and sub-effect', () => { + const tracer = jest.fn(); - const tracer = jest.fn(); + const tracerEffect = (signature: string) => (api: any) => (next: any) => ( + action: any, + ) => { + tracer(signature); + next(action); + }; - const store = (new Updux({ + const store = new Updux({ + effects: { + foo: tracerEffect('root'), + }, + subduxes: { + zzz: { effects: { - '*': api => next => action => { - tracer(); - next(action); - }, + foo: tracerEffect('child'), }, - })).createStore(); + }, + }, + }).createStore(); - expect(tracer).not.toHaveBeenCalled(); + expect(tracer).not.toHaveBeenCalled(); - store.dispatch({ type: 'bar' }); + store.dispatch({type: 'bar'}); - expect(tracer).toHaveBeenCalled(); + expect(tracer).not.toHaveBeenCalled(); + + store.dispatch.foo(); + + expect(tracer).toHaveBeenNthCalledWith(1, 'root'); + expect(tracer).toHaveBeenNthCalledWith(2, 'child'); }); -test( 'async effect', async () => { +test('"*" effect', () => { + const tracer = jest.fn(); - function timeout(ms:number) { - return new Promise(resolve => setTimeout(resolve, ms)); - } + const store = new Updux({ + effects: { + '*': api => next => action => { + tracer(); + next(action); + }, + }, + }).createStore(); - const tracer = jest.fn(); + expect(tracer).not.toHaveBeenCalled(); - const store = (new Updux({ - effects: { - foo: api => next => async action => { - next(action); - await timeout(1000); - tracer(); - }, - }, - })).createStore(); + store.dispatch({type: 'bar'}); - expect(tracer).not.toHaveBeenCalled(); - - store.dispatch.foo(); - - expect(tracer).not.toHaveBeenCalled(); - - await timeout(1000); - - expect(tracer).toHaveBeenCalled(); + expect(tracer).toHaveBeenCalled(); +}); + +test('async effect', async () => { + function timeout(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + const tracer = jest.fn(); + + const store = new Updux({ + effects: { + foo: api => next => async action => { + next(action); + await timeout(1000); + tracer(); + }, + }, + }).createStore(); + + expect(tracer).not.toHaveBeenCalled(); + + store.dispatch.foo(); + + expect(tracer).not.toHaveBeenCalled(); + + await timeout(1000); + + expect(tracer).toHaveBeenCalled(); +}); + +test('getState is local', () => { + let childState; + let rootState; + let rootFromChild; + + const child = new Updux({ + initial: {alpha: 12}, + effects: { + doIt: ({getState,getRootState}) => next => action => { + childState = getState(); + rootFromChild = getRootState(); + next(action); + }, + }, + }); + + const root = new Updux({ + initial: {beta: 24}, + subduxes: {child}, + effects: { + doIt: ({getState}) => next => action => { + rootState = getState(); + next(action); + }, + }, + }); + + const store = root.createStore(); + store.dispatch.doIt(); + + expect(rootState).toEqual({beta: 24, child: {alpha: 12}}); + expect(rootFromChild).toEqual({beta: 24, child: {alpha: 12}}); + expect(childState).toEqual({alpha: 12}); }); diff --git a/src/types.ts b/src/types.ts index bd09f2c..b33cac4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -178,7 +178,16 @@ export type UpduxConfig = { * ``` * */ - effects?: Dictionary>; + effects?: Dictionary>; }; export type Upreducer = (action: Action) => (state: S) => S; + +export interface UpduxMiddlewareAPI { + dispatch: UpduxDispatch, + getState(): any, + getRootState(): S + +} +export type UpduxMiddleware = (api: UpduxMiddlewareAPI ) => ( next: UpduxDispatch ) => ( action: Action ) => any; + diff --git a/src/updux.ts b/src/updux.ts index 6443db2..c3b7d4d 100644 --- a/src/updux.ts +++ b/src/updux.ts @@ -132,12 +132,15 @@ export class Updux { * A middleware aggregating all the effects defined in the * updux and its subduxes. Effects of the updux itself are * done before the subduxes effects. + * Note that `getState` will always return the state of the + * local updux. The function `getRootState` is provided + * alongside `getState` to get the root state. */ @computed get middleware(): Middleware<{}, S, UpduxDispatch> { return buildMiddleware( this.localEffects, this.actions, - Object.values(this.subduxes).map(sd => sd.middleware), + this.subduxes, ); }