redux#Middleware TypeScript Examples

The following examples show how to use redux#Middleware. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts    From shippo with MIT License 6 votes vote down vote up
createStore = (compose?: (...middleware: Middleware[]) => StoreEnhancer) => {
  compose = compose || applyMiddleware
  const middleware: Array<Middleware> = [thunkMiddleware]
  const stores = create(reducers, compose(...middleware))
  const thunkDispatch: ThunkDispatch = (action) => stores.dispatch<any>(action)
  const dispatch: Dispatch = (action) => stores.dispatch<any>(action)
  const selector = <T>(selector: (store: IStores) => T) => selector(stores.getState())
  return {
    stores,
    thunkDispatch,
    dispatch,
    selector,
  }
}
Example #2
Source File: application.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
toggleLogActionsMiddleware: Middleware<{}, StoreState> = (store: MiddlewareAPI<Dispatch, StoreState>) => (
  next: Dispatch
) => (action: AnyAction) => {
  const isLogActionsAction = action.type === toggleLogActions.type;
  if (isLogActionsAction) {
    return next(action);
  }

  const logActionsTrue =
    window && window.location && window.location.search && window.location.search.indexOf('logActions=true') !== -1;
  const logActionsFalse =
    window && window.location && window.location.search && window.location.search.indexOf('logActions=false') !== -1;
  const logActions = store.getState().application.logActions;

  if (logActionsTrue && !logActions) {
    store.dispatch(toggleLogActions());
  }

  if (logActionsFalse && logActions) {
    store.dispatch(toggleLogActions());
  }

  return next(action);
}
Example #3
Source File: delayMiddleware.ts    From react-js-tutorial with MIT License 6 votes vote down vote up
delayMiddleware: Middleware = () => (next) => (action) => {
  if (
    "meta" in action &&
    "delay" in action.meta &&
    typeof action.meta.delay === "number"
  ) {
    const timeout = setTimeout(() => next(action), action.meta.delay);

    return () => clearTimeout(timeout);
  }

  return next(action);
}
Example #4
Source File: index.ts    From idena-pocket with MIT License 6 votes vote down vote up
asyncAction: Middleware = () => next => (action: any) => {
	if (
		action.result &&
		action.result.then !== undefined &&
		action.result.catch !== undefined
	) {
		action.result
			.then((result: any) => {
				next({
					type: action.type,
					result
				})
			})
			.catch((error: any) => {
				next({
					type: `${action.type}_CATCH`,
					result: error
				})
			})
		const { type, result, ...rest } = action
		return next({ type: `${action.type}_REQUESTED`, ...rest })
	}
	return next(action)
}
Example #5
Source File: WebcontentsPreloadMiddleware.ts    From YoutubeLiveApp with MIT License 6 votes vote down vote up
function WebcontentsPreloadMiddleware(): Middleware {
  return (store) => (next) => (action: Action) => {
    if (ipcRenderer.eventNames().every((name) => name !== IPCEvent.StateChanged.CHANNEL_NAME_FROM_MAIN)) {
      ipcRenderer.on(IPCEvent.StateChanged.CHANNEL_NAME_FROM_MAIN, (_, action: Action) => {
        console.log({ type: action.type });
        next(action);
        console.log(action);
      });
    }
    next(action);
    ipcRenderer.send(IPCEvent.StateChanged.CHANNEL_NAME_FROM_PRELOAD, action);
  };
}
Example #6
Source File: MainProcessMiddleware.ts    From YoutubeLiveApp with MIT License 6 votes vote down vote up
export default function MainProcessMiddleware(): Middleware {
  return (store) => (next) => (action: Action) => {
    // イベントリスナが未登録なら登録する
    if (!ipcMain.eventNames().some((name) => name === IPCEvent.InitialState.CHANNEL_NAME_FROM_PRELOAD)) {
      // state の初期値要求
      ipcMain.on(IPCEvent.InitialState.CHANNEL_NAME_FROM_PRELOAD, (event) => {
        const state = store.getState();
        event.sender.send(IPCEvent.StateChanged.CHANNEL_NAME_FROM_MAIN, {
          type: IPCEvent.InitialState.CHANNEL_NAME_FROM_MAIN,
          payload: state,
        });
      });
    }
    // イベントリスナが未登録なら登録する
    if (!ipcMain.eventNames().some((name) => name === IPCEvent.StateChanged.CHANNEL_NAME_FROM_PRELOAD)) {
      ipcMain.on(IPCEvent.StateChanged.CHANNEL_NAME_FROM_PRELOAD, (event, action: Action) => {
        console.log({ MainProcessMiddleWare: action });
        App.childWindows.forEach(
          (value) => event.sender != value.webContents && value.webContents.send(IPCEvent.StateChanged.CHANNEL_NAME_FROM_MAIN, action)
        );
        next(action);
      });
    }

    next(action);
    App.childWindows.forEach((value) => value.webContents.send(IPCEvent.StateChanged.CHANNEL_NAME_FROM_MAIN, action));
    console.log({ action });
  };
}
Example #7
Source File: modelInterceptor.ts    From foca with MIT License 6 votes vote down vote up
modelInterceptor: Middleware<{}, Record<string, object>> =
  (api) => (dispatch) => (action: AnyAction) => {
    if (!isPreModelAction(action)) {
      return dispatch(action);
    }

    const prev = api.getState()[action.model]!;
    const next = immer.produce(prev, (draft) => {
      return action.consumer(draft, action);
    });

    if (deepEqual(prev, next)) {
      return action;
    }

    return dispatch<PostModelAction>({
      type: action.type,
      model: action.model,
      postModel: true,
      next: next,
    });
  }
Example #8
Source File: loadingInterceptor.ts    From foca with MIT License 6 votes vote down vote up
loadingInterceptor = (
  loadingStore: LoadingStore,
): Middleware<{}, LoadingStoreState> => {
  return () => (dispatch) => (action: AnyAction) => {
    if (!isLoadingAction(action)) {
      return dispatch(action);
    }

    const {
      model,
      method,
      payload: { category, loading },
    } = action;

    if (!loadingStore.isActive(model, method)) {
      return;
    }

    const record = loadingStore.getItem(model, method);

    if (!record || record.loadings.data[category] !== loading) {
      return dispatch(action);
    }

    return action;
  };
}
Example #9
Source File: destroyLoadingInterceptor.ts    From foca with MIT License 6 votes vote down vote up
destroyLoadingInterceptor: Middleware =
  (api) => (dispatch) => (action: AnyAction) => {
    if (
      !isDestroyLoadingAction(action) ||
      api.getState().hasOwnProperty(action.model)
    ) {
      return dispatch(action);
    }

    return action;
  }
Example #10
Source File: EditorCategory.ts    From Teyvat.moe with GNU General Public License v3.0 6 votes vote down vote up
emptyCategoryMiddleware: Middleware<unknown, AppState> = ({ dispatch, getState }) => (
  next
) => (action) => {
  switch (action.type) {
    case setTab.type:
      const currentState = getState();
      const setTabAction: PayloadAction<UIControlsTab> = action;
      switch (setTabAction.payload) {
        case 'features':
          handleSetTabFeatures(currentState, dispatch);

          // Pass on the original action without replacing or modifying it.
          break;
        case 'routes':
          handleSetTabRoutes(currentState, dispatch);

          // Pass on the action without replacing or modifying it.
          break;
      }
      break;
  }

  // Pass on the action without replacing or modifying it.
  return next(action);
}
Example #11
Source File: middleware.ts    From msteams-meetings-template with MIT License 6 votes vote down vote up
export function createMeetingMiddleware(): Middleware {
  const service = createMeetingService();

  return store => next => action => {
    if (action.type === CREATE_MEETING_COMMAND) {
      service
        .createMeeting(action.meeting)
        .then(meeting => {
          store.dispatch({
            type: MEETING_CREATED_EVENT,
            meeting
          });
        })
        .catch(error => {
          console.error('Create meeting failed: ', error);
          store.dispatch(push('/error'));
        });
    }

    if (action.type === MEETING_CREATED_EVENT) {
      store.dispatch(push('/copyMeeting'));
    }
    next(action);
  };
}
Example #12
Source File: store.ts    From bad-cards-game with GNU Affero General Public License v3.0 5 votes vote down vote up
createCustomStore = (middlewares: Middleware[] = []) => {
  return createStore(gameReducer, composeWithDevTools(applyMiddleware(...middlewares)));
}
Example #13
Source File: redux-undo-redo.d.ts    From diagram-maker with Apache License 2.0 5 votes vote down vote up
export declare function createUndoMiddleware<StateType>(config: UndoMiddlewareConfig<StateType>): Middleware;
Example #14
Source File: Loading.ts    From Teyvat.moe with GNU General Public License v3.0 5 votes vote down vote up
initializationMiddleware: Middleware<unknown, AppState> = ({ dispatch, getState }) => (
  next
) => (action) => {
  switch (action.type) {
    case setLoading.type: {
      const setLoadingAction: ReturnType<typeof setLoading> = action;
      const currentState = getState();

      if (
        setLoadingAction.payload.loadingKey != undefined &&
        setLoadingAction.payload.newValue === true
      ) {
        switch (setLoadingAction.payload.loadingKey) {
          case 'loadMapFeatures':
            // Start counting the map features.
            countMapMarkers();
            break;
          case 'loadMapRoutes':
            countMapRoutes();
            break;
          case null:
            console.warn('Unknown loading state!');
            console.warn(setLoadingAction);
            break;
          default:
            // Pass on the action without replacing or modifying it.
            break;
        }
      }

      if (
        selectLoading(currentState, 'loadMapFeatures') === true &&
        selectLoading(currentState, 'loadMapRoutes') === true &&
        selectLoading(currentState, 'handlePermalinks') === false
      ) {
        const permalinkId = selectPermalinkID(currentState);
        if (permalinkId == null) {
          dispatch(setLoading('handlePermalinks', 'skip'));
        } else {
          navigateToMarkerByID(permalinkId);
        }
      }

      break;
    }
    case setMapMarkerCount.type: {
      dispatch(setLoading('countMapFeatures', true));

      break;
    }
    case setMapRouteCount.type: {
      dispatch(setLoading('countMapRoutes', true));

      break;
    }
    case setPermalinkId.type: {
      dispatch(setLoading('handlePermalinks', false));

      break;
    }
    // No default
  }

  // Pass on the action without replacing or modifying it.
  return next(action);
}
Example #15
Source File: middleware.ts    From msteams-meetings-template with MIT License 5 votes vote down vote up
export function createAuthMiddleware(): Middleware {
  return store => next => action => {
    if (action.type === CHECK_FOR_SIGNEDIN_USER_COMMAND) {
      if (!msalApp.getAccount()) {
        store.dispatch(replace('/signin'));
      }
    }

    if (action.type === OPEN_SIGNIN_DIALOG_COMMAND) {
      msalApp
        .loginPopup({
          scopes: ['OnlineMeetings.ReadWrite']
        })
        .then(response => {
          console.log('Login succeeded');
          store.dispatch({
            type: SIGNIN_COMPLETE_EVENT,
            idToken: response.idToken
          } as SigninCompleteEvent);
        })
        .catch(error => {
          console.log('Login failed:', error);
        });
    }

    if (action.type === SIGNIN_COMPLETE_EVENT) {
      store.dispatch(replace('/'));
    }

    if (action.type === SIGNOUT_COMMAND) {
      msalApp.logout();
      store.dispatch({
        type: SIGNOUT_COMPLETE_EVENT
      });
      console.log('logged out?');
    }

    next(action);
  };
}
Example #16
Source File: index.ts    From idena-pocket with MIT License 5 votes vote down vote up
reactRouterProps: Middleware = () => next => (action: any) => next(action)
Example #17
Source File: store.ts    From nextclade with MIT License 5 votes vote down vote up
export function configureStore() {
  const middlewares: Middleware<string>[] = [thunk].filter(Boolean)
  const enhancer = applyMiddleware(...middlewares)
  const store = createStore(createRootReducer(), {}, enhancer)
  return { store }
}
Example #18
Source File: index.d.ts    From shippo with MIT License 5 votes vote down vote up
createStore: (compose?: ((...middleware: Middleware[]) => StoreEnhancer) | undefined) => {
    stores: import("redux").Store<import("redux").EmptyObject & {
        user: import("./user/user-reducer").IUserStore;
    }, import("@kazura/react-store").StoreAction<import("./user/user-reducer").IUserStore>>;
    thunkDispatch: ThunkDispatch<AnyAction>;
    dispatch: Dispatch<AnyAction>;
    selector: <T>(selector: (store: IStores) => T) => T;
}
Example #19
Source File: create-store.ts    From anthem with Apache License 2.0 5 votes vote down vote up
middleware: ReadonlyArray<Middleware> = [epicMiddleware]
Example #20
Source File: applyMiddleware.ts    From reactant with MIT License 5 votes vote down vote up
applyMiddleware = (...args: Middleware[]) => {
  return class extends PluginModule {
    readonly [storeKey]?: Store;

    // eslint-disable-next-line prefer-spread
    enhancer = applyMiddlewareWithRedux.apply(null, args);
  };
}
Example #21
Source File: plugin.ts    From reactant with MIT License 5 votes vote down vote up
/** inject middleware for Redux */
  middleware?: Middleware;
Example #22
Source File: createApp.tsx    From redux-with-domain with MIT License 4 votes vote down vote up
export default function createApp(createOpt: CreateOpt = {}) {
  let app: App
  const { initialReducer, onError } = createOpt

  // internal map for modules
  const _modules: Modules = {}

  let _router = <div />

  const _middleWares: Middleware<{}>[] = [...defaultMiddleWares]
  const sagaMiddleware: SagaMiddleware<{}> = createSagaMiddleware()

  _middleWares.push(sagaMiddleware)

  function hasSubModule(module: Module) {
    let flag = false
    forEach(module, value => {
      if (isModule(value)) {
        flag = true
      }
    })
    return flag
  }

  function _addModule(m: Module) {
    invariant(!_modules[m.namespace], `kop nodule ${m.namespace} exists`)

    if (!isEmpty(m.entities)) {
      forEach(m.entities, e => {
        _addModule(e)
      })
    }
    _modules[m.namespace] = m
  }

  // create reducer
  // http://redux.js.org/docs/recipes/reducers/RefactoringReducersExample.html
  function createReducer(
    initialState = {},
    handlers: ReducerHandler,
    defaultReducer?: DefaultReducer<any>
  ) {
    return (state = initialState, action: Action) => {
      if (
        Object.prototype.hasOwnProperty.call(handlers, action.type) &&
        isFunction(handlers[action.type])
      ) {
        const handler = handlers[action.type]
        return handler(state, action)
      }
      if (defaultReducer && isFunction(defaultReducer)) {
        return defaultReducer(state, action)
      }
      return state
    }
  }

  function loopCombineReducer(
    tree: any,
    combineRootreducer = true,
    parentNode?: string | ParentNode
  ) {
    const childReducer: any = mapValues(tree, node => {
      if (!isModule(node)) {
        return loopCombineReducer(node)
      }

      if (hasSubModule(node)) {
        const subModuleMap = pickBy(node, value => isModule(value))
        return loopCombineReducer(subModuleMap, true, node)
      }

      return createReducer(
        node._initialState,
        node._reducers,
        node._defaultReducer
      )
    })

    let result

    if (isEmpty(parentNode)) {
      result = {
        ...childReducer
      }
    } else if (parentNode === 'root') {
      invariant(
        !initialReducer || isPlainObject(initialReducer),
        'initialReducer should be object'
      )
      const noDuplicatedKeys = !hasDuplicatedKeys(
        initialReducer,
        childReducer,
        'router'
      )
      invariant(
        noDuplicatedKeys,
        'initialReducer has reduplicate keys with other reducers'
      )

      result = {
        ...initialReducer,
        ...childReducer
      }
    } else {
      result = {
        base: createReducer(
          (parentNode as ParentNode)._initialState,
          (parentNode as ParentNode)._reducers,
          (parentNode as ParentNode)._defaultReducer
        ),
        ...childReducer
      }
    }

    if (parentNode === 'root' && !combineRootreducer) return result

    return combineReducers(result)
  }

  function addModule(module: Module | Module[]) {
    if (isArray(module)) {
      module.forEach(m => {
        _addModule(m)
      })
    } else {
      _addModule(module)
    }
  }

  function initInjectModules(presenter: Presenter) {
    forEach(presenter.injectModules, (name: string) => {
      invariant(_modules[name], `please check the kop-module ${name} is added`)
      extend(_modules[name].presenter, {
        loaded: true, // 标记已被装载,在module中会注入 presentor 的 seletor
        selectors: presenter.selectors,
        actions: presenter.actions
      })
    })
  }

  function createRootReducer(combineRootreducer = true) {
    const moduleData = cloneDeepWith(_modules)
    const moduleTree = reduce(
      moduleData,
      (result, value, key) => {
        const module = get(result, toStorePath(key))

        if (isModule(value)) {
          if (module) {
            return set(result, `${toStorePath(key)}.base`, value)
          }
          return set(result, toStorePath(key), value)
        }

        return result
      },
      {}
    )

    return loopCombineReducer(moduleTree, combineRootreducer, 'root')
  }

  function addPage(pageModule: Module, opt: PageOption = {}) {
    const { containers } = opt

    if (containers && containers.length > 0) {
      addModule(containers)
    }

    if (pageModule.injectModules && pageModule.injectModules.length > 0) {
      initInjectModules(pageModule)
    }

    addModule(pageModule)
  }

  function addDomain(module: Module) {
    addModule(module)
  }

  const addGlobal = _addModule

  function removeModule(module: Module | Module[]) {
    const _remove = (m: Module) => {
      invariant(
        _modules[m.namespace],
        `error: the kop-module - ${m.namespace} is not existed`
      )

      // hack redux-devtools's bug
      if (
        m &&
        m.actions &&
        !isPresenter(m) &&
        m.type !== DOMAIN_MODULE &&
        (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
      ) {
        _store.dispatch(m.actions.__reset())
      }

      delete _modules[m.namespace]
      _store.dispatch({ type: `${m.namespace}/@@KOP_CANCEL_EFFECTS` })
    }

    if (isArray(module)) {
      module.forEach(m => {
        _remove(m)
      })
      _store.replaceReducer(createRootReducer() as Reducer)
    } else if (module) {
      _remove(module)
      _store.replaceReducer(createRootReducer() as Reducer)
    }
  }

  function addRouter(r: JSX.Element) {
    _router = r
  }

  function addMiddleWare(middleWare: Middleware) {
    const add = (m: Middleware) => {
      _middleWares.push(m)
    }

    add(middleWare)
  }

  function getStore() {
    return _store
  }

  function createAppStore() {
    // inject chrome redux devtools
    let composeEnhancers

    if (
      process.env.NODE_ENV !== 'production' &&
      (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ) {
      composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    } else {
      composeEnhancers = compose
    }

    const enhancer = composeEnhancers(applyMiddleware(..._middleWares))
    const rootReducer = createRootReducer()
    return createStore(rootReducer as Reducer, enhancer)
  }

  function getArguments(...args: any[]) {
    let domSelector: string | null = null
    let callback = noop

    if (args.length === 1) {
      // app.start(false) means jump render phase and return early
      if (args[0] === false) {
        return {
          domSelector: '',
          callback: noop,
          shouldRender: false
        }
      }
      if (isString(args[0])) {
        domSelector = args[0]
      }
      if (isFunction(args[0])) {
        callback = args[0]
      }
    }

    if (args.length === 2) {
      domSelector = args[0]
      callback = args[1]
    }

    return {
      domSelector,
      callback,
      shouldRender: true
    }
  }

  function renderAppElement(
    domSelector: string | null,
    callback: Function,
    shouldRender: boolean
  ) {
    const $elem = <Provider store={_store}>{_router}</Provider>

    // skip render when shouldRender is false
    if (shouldRender) {
      if (isString(domSelector)) {
        render($elem, document.querySelector(domSelector), () => {
          callback()
        })
      } else if (isElement(domSelector)) {
        render($elem, domSelector, () => {
          callback()
        })
      } else {
        callback()
      }
    }

    return $elem
  }

  function _onError(e: Error) {
    if (!onError) {
      console.error(e)
    } else {
      onError(e)
    }
  }

  // create root saga
  function createSaga(module: Module) {
    return function*() {
      if (isArray(module.effects)) {
        for (let i = 0, k = module.effects.length; i < k; i++) {
          const task = yield effects.fork(function*(): any {
            try {
              if (module.effects) {
                yield module.effects[i]()
              }
            } catch (e) {
              _onError(e)
            }
          })

          yield effects.fork(function*() {
            yield effects.take(`${module.namespace}/@@KOP_CANCEL_EFFECTS`)
            yield effects.cancel(task)
          })
        }
      }

      if (isArray(module.watchers)) {
        for (let i = 0, k = module.watchers.length; i < k; i++) {
          const task = yield effects.fork(function*(): any {
            try {
              if (module.watchers) {
                yield module.watchers[i]()
              }
            } catch (e) {
              _onError(e)
            }
          })
          yield effects.fork(function*() {
            yield effects.take(`${module.namespace}/@@KOP_CANCEL_EFFECTS`)
            yield effects.cancel(task)
          })
        }
      }
    }
  }

  function start(...args: any[]) {
    const { domSelector, callback, shouldRender } = getArguments(...args)

    _store = createAppStore()

    initSelectorHelper(_store)

    app._store = _store

    window[KOP_GLOBAL_STORE_REF] = _store

    app._modules = _modules

    forEach(app._modules, (m: Module) => {
      if (m.type === ENTITY_MODULE) {
        m._store = _store // 提供domainModel的遍历action
      }
    })

    app._middleWares = _middleWares

    app._router = _router

    forEach(_modules, module => {
      sagaMiddleware.run(createSaga(module))
    })

    return renderAppElement(domSelector, callback, shouldRender)
  }

  // 集成模式初始化,返回 saga,reducer 等等
  function _init() {
    const rootReducer = createRootReducer(false)
    return {
      rootReducer,
      sagaMiddleware
    }
  }

  // 集成模式启动,由外部 Redux 控制 App 渲染流程
  function _run(store: Store<any, AnyAction>) {
    initSelectorHelper(store)
    app._store = store
    window[KOP_GLOBAL_STORE_REF] = store
    app._modules = _modules
    forEach(app._modules, (m: Module) => {
      if (m.type === ENTITY_MODULE) {
        m._store = store
      }
    })
    app._router = _router
    forEach(_modules, module => {
      sagaMiddleware.run(createSaga(module))
    })
  }

  app = {
    addModule, // register container module
    addPage, // register page module
    addDomain, // register domain module
    addGlobal, // register global module
    addRouter,
    addMiddleWare,
    start,
    getStore,
    removeModule,
    _init,
    _run
  }

  return app
}
Example #23
Source File: reduxTester.ts    From grafana-chinese with Apache License 2.0 4 votes vote down vote up
reduxTester = <State>(args?: ReduxTesterArguments<State>): ReduxTesterGiven<State> => {
  const dispatchedActions: AnyAction[] = [];
  const logActionsMiddleWare: Middleware<{}, Partial<StoreState>> = (
    store: MiddlewareAPI<Dispatch, Partial<StoreState>>
  ) => (next: Dispatch) => (action: AnyAction) => {
    // filter out thunk actions
    if (action && typeof action !== 'function') {
      dispatchedActions.push(action);
    }

    return next(action);
  };

  const preloadedState = args?.preloadedState ?? (({} as unknown) as State);
  const debug = args?.debug ?? false;
  let store: EnhancedStore<State> | null = null;

  const givenRootReducer = (rootReducer: Reducer<State>): ReduxTesterWhen<State> => {
    store = configureStore<State>({
      reducer: rootReducer,
      middleware: [logActionsMiddleWare, thunk],
      preloadedState,
    });
    setStore(store as any);

    return instance;
  };

  const whenActionIsDispatched = (
    action: any,
    clearPreviousActions?: boolean
  ): ReduxTesterWhen<State> & ReduxTesterThen<State> => {
    if (clearPreviousActions) {
      dispatchedActions.length = 0;
    }
    store.dispatch(action);

    return instance;
  };

  const whenAsyncActionIsDispatched = async (
    action: any,
    clearPreviousActions?: boolean
  ): Promise<ReduxTesterWhen<State> & ReduxTesterThen<State>> => {
    if (clearPreviousActions) {
      dispatchedActions.length = 0;
    }

    await store.dispatch(action);

    return instance;
  };

  const thenDispatchedActionShouldEqual = (...actions: AnyAction[]): ReduxTesterWhen<State> => {
    if (debug) {
      console.log('Dispatched Actions', JSON.stringify(dispatchedActions, null, 2));
    }

    if (!actions.length) {
      throw new Error('thenDispatchedActionShouldEqual has to be called with at least one action');
    }

    expect(dispatchedActions).toEqual(actions);
    return instance;
  };

  const thenDispatchedActionPredicateShouldEqual = (
    predicate: (dispatchedActions: AnyAction[]) => boolean
  ): ReduxTesterWhen<State> => {
    if (debug) {
      console.log('Dispatched Actions', JSON.stringify(dispatchedActions, null, 2));
    }

    expect(predicate(dispatchedActions)).toBe(true);
    return instance;
  };

  const instance = {
    givenRootReducer,
    whenActionIsDispatched,
    whenAsyncActionIsDispatched,
    thenDispatchedActionShouldEqual,
    thenDispatchedActionPredicateShouldEqual,
  };

  return instance;
}
Example #24
Source File: createStore.test.ts    From reactant with MIT License 4 votes vote down vote up
describe('createStore', () => {
  test('basic parameters', () => {
    @injectable()
    class Counter {
      @state
      count = 0;
    }
    const ServiceIdentifiers = new Map();
    const modules = [Counter];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    container.get(Counter);
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
    });
    expect(Object.values(store.getState())).toEqual([{ count: 0 }]);
  });

  test('preloadedState', () => {
    @injectable({
      name: 'counter',
    })
    class Counter {
      @state
      count = 0;
    }
    const ServiceIdentifiers = new Map();
    const modules = [Counter];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    container.get(Counter);
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
      preloadedState: {
        counter: { count: 18 },
      },
    });
    expect(Object.values(store.getState())).toEqual([{ count: 18 }]);
  });

  test('middleware module', () => {
    @injectable({
      name: 'counter',
    })
    class Counter {
      @state
      count = 0;

      @action
      increase() {
        this.count += 1;
      }
    }
    const actionFn = jest.fn();
    class Logger extends PluginModule {
      middleware: Middleware = (store) => (next) => (_action) => {
        actionFn(_action);
        const result = next(_action);
        actionFn(store.getState());
        return result;
      };
    }

    const ServiceIdentifiers = new Map();
    const modules = [Counter, Logger];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    const counter = container.get(Counter);
    createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
    });
    counter.increase();
    expect(actionFn.mock.calls.length).toBe(2);
    expect(actionFn.mock.calls[0]).toEqual([
      {
        _reactant: actionIdentifier,
        method: 'increase',
        params: [],
        state: {
          counter: { count: 1 },
        },
        type: 'counter',
      },
    ]);
    expect(actionFn.mock.calls[1]).toEqual([{ counter: { count: 1 } }]);
  });

  test('providers', () => {
    const Provider = () => null;

    @injectable()
    class CounterPlugin extends PluginModule {
      provider = Provider;
    }

    @injectable()
    class Counter {
      constructor(public counterPlugin: CounterPlugin) {}

      state = {
        count: 0,
      };
    }
    const ServiceIdentifiers = new Map();
    const modules = [Counter];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    container.get(Counter);
    const providers: React.FunctionComponent[] = [];
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: providers,
      },
    });
    expect(providers.length).toBe(1);
    expect(providers[0]({}) === null).toBeTruthy();
  });

  test('providers - be depended on', () => {
    const fooProvider = () => null;
    const barProvider = () => null;

    @injectable()
    class FooPlugin extends PluginModule {
      provider = fooProvider;
    }

    @injectable()
    class BarPlugin extends PluginModule {
      provider = barProvider;
    }

    @injectable()
    class Counter {}
    const ServiceIdentifiers = new Map();
    const modules = [Counter, FooPlugin, BarPlugin];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    container.get(Counter);
    const providers: React.FunctionComponent[] = [];
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: providers,
      },
    });
    expect(providers.map(({ name }) => name)).toEqual(
      [fooProvider, barProvider].map(({ name }) => `bound ${name}`)
    );
  });

  test('devOptions', () => {
    @injectable({
      name: 'counter',
    })
    class Counter {
      @state
      count = 0;

      @state
      sum = { count: 0 };

      @action
      increase() {
        this.sum.count += 1;
      }

      increase1() {
        this.sum.count += 1;
      }

      increase2() {
        this.count += 1;
      }
    }
    const ServiceIdentifiers = new Map();
    const modules = [Counter];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    const counter = container.get(Counter);
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
      devOptions: { autoFreeze: true },
    });
    expect(() => {
      store.getState().counter.sum.count = 1;
    }).toThrowError(/Cannot assign to read only property/);
    counter.increase();
    for (const fn of [
      () => {
        store.getState().counter.sum.count = 1;
      },
      () => counter.increase1(),
      () => counter.increase2(),
    ]) {
      expect(fn).toThrowError(/Cannot assign to read only property/);
    }
  });
});
Example #25
Source File: action.test.ts    From reactant with MIT License 4 votes vote down vote up
describe('@action', () => {
  test('base', () => {
    @injectable()
    class Counter {
      @state
      count = 0;

      @action
      increase() {
        this.count += 1;
      }
    }
    const ServiceIdentifiers = new Map();
    const modules = [Counter];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    const counter = container.get(Counter);
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
    });
    counter.increase();
    expect(counter.count).toBe(1);
    expect((counter as any)[enablePatchesKey]).toBe(false);
    expect(Object.values(store.getState())).toEqual([{ count: 1 }]);
  });

  test('enable `autoFreeze` in devOptions', () => {
    @injectable({
      name: 'counter',
    })
    class Counter {
      @state
      count = 0;

      @state
      sum = { count: 0 };

      @action
      increase() {
        this.sum.count += 1;
      }

      increase1() {
        this.sum.count += 1;
      }

      increase2() {
        this.count += 1;
      }
    }
    const ServiceIdentifiers = new Map();
    const modules = [Counter];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    const counter = container.get(Counter);
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
      devOptions: { autoFreeze: true },
    });
    expect(() => {
      store.getState().counter.sum.count = 1;
    }).toThrowError(/Cannot assign to read only property/);
    counter.increase();
    for (const fn of [
      () => {
        store.getState().counter.sum.count = 1;
      },
      () => counter.increase1(),
      () => counter.increase2(),
    ]) {
      expect(fn).toThrowError(/Cannot assign to read only property/);
    }
  });

  test('inherited module with stagedState about more effects', () => {
    @injectable({
      name: 'foo0',
    })
    class Foo0 {
      @state
      count0 = 1;

      @state
      count1 = 1;

      @action
      increase() {
        this.count0 += 1;
      }

      @action
      decrease() {
        this.count0 -= 1;
      }

      @action
      decrease1() {
        this.count0 -= 1;
      }
    }

    @injectable({
      name: 'foo',
    })
    class Foo extends Foo0 {
      count = 1;

      add(count: number) {
        this.count += count;
      }

      @action
      increase() {
        // inheritance
        super.increase();
        // change state
        this.count0 += 1;
        // call other action function
        this.increase1();
        // call unwrapped `@action` function
        this.add(this.count);
      }

      @action
      increase1() {
        this.count1 += 1;
      }

      @action
      decrease() {
        super.decrease();
        this.count0 -= 1;
      }

      decrease1() {
        super.decrease1();
      }
    }

    @injectable()
    class FooBar {
      constructor(public foo: Foo, public foo0: Foo0) {}
    }

    const ServiceIdentifiers = new Map();
    const modules = [FooBar];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    const fooBar = container.get(FooBar);
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
    });
    const subscribe = jest.fn();
    store.subscribe(subscribe);
    fooBar.foo.increase();
    expect(fooBar.foo.count0).toBe(3);
    expect(fooBar.foo.count1).toBe(2);
    expect(fooBar.foo.count).toBe(2);
    fooBar.foo0.increase();
    expect(fooBar.foo.count0).toBe(3);
    expect(fooBar.foo.count1).toBe(2);
    expect(fooBar.foo.count).toBe(2);
    // merge the multi-actions changed states as one redux dispatch.
    expect(subscribe.mock.calls.length).toBe(2);
    // inheritance
    fooBar.foo.decrease();
    expect(fooBar.foo.count0).toBe(1);
    fooBar.foo.decrease1();
    expect(fooBar.foo.count0).toBe(0);
  });

  test('across module changing state', () => {
    @injectable()
    class Foo {
      @state
      textList: string[] = [];

      @action
      addText(text: string) {
        this.textList.push(text);
      }
    }

    @injectable()
    class Counter {
      constructor(public foo: Foo) {}

      @state
      count = 0;

      @action
      increase() {
        this.foo.addText(`test${this.count}`);
        this.count += 1;
        this.foo.addText(`test${this.count}`);
        this.count += 1;
        this.foo.addText(`test${this.count}`);
      }
    }

    const ServiceIdentifiers = new Map();
    const modules = [Counter];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    const counter = container.get(Counter);
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
    });
    const subscribeFn = jest.fn();
    store.subscribe(subscribeFn);
    counter.increase();
    expect(subscribeFn.mock.calls.length).toBe(1);
    expect(counter.count).toEqual(2);
    expect(counter.foo.textList).toEqual(['test0', 'test1', 'test2']);
  });

  test('across module changing state with error', () => {
    @injectable({
      name: 'foo',
    })
    class Foo {
      @state
      list: number[] = [];

      @action
      addItem(num: number) {
        if (num === 1) {
          // eslint-disable-next-line no-throw-literal
          throw 'something error';
        } else {
          this.list.push(num);
        }
      }
    }

    @injectable({
      name: 'counter',
    })
    class Counter {
      constructor(public foo: Foo) {}

      @state
      count = 0;

      @action
      increase() {
        this.foo.addItem(this.count);
        this.count += 1;
      }

      @action
      increase1() {
        this.foo.addItem(this.count + 1);
        this.count += 1;
      }
    }

    const ServiceIdentifiers = new Map();
    const modules = [Counter];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    const counter = container.get(Counter);
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
    });
    const subscribeFn = jest.fn();
    store.subscribe(subscribeFn);
    counter.increase();
    expect(subscribeFn.mock.calls.length).toBe(1);

    expect(() => {
      counter.increase();
    }).toThrowError('something error');
    expect(getStagedState()).toBeUndefined();
    expect(subscribeFn.mock.calls.length).toBe(1);

    counter.increase1();
    expect(subscribeFn.mock.calls.length).toBe(2);
    expect(counter.count).toBe(2);
    expect(counter.foo.list).toEqual([0, 2]);
    expect(store.getState()).toEqual({
      counter: { count: 2 },
      foo: { list: [0, 2] },
    });
  });

  test('base with `enablePatches`', () => {
    interface Todo {
      text: string;
    }
    @injectable({
      name: 'todo',
    })
    class TodoList {
      @state
      list: Todo[] = [
        {
          text: 'foo',
        },
      ];

      @action
      add(text: string) {
        this.list.slice(-1)[0].text = text;
        this.list.push({ text });
      }
    }

    const actionFn = jest.fn();

    const middleware: Middleware = (store) => (next) => (_action) => {
      actionFn(_action);
      return next(_action);
    };

    const ServiceIdentifiers = new Map();
    const modules = [TodoList, applyMiddleware(middleware)];
    const container = createContainer({
      ServiceIdentifiers,
      modules,
      options: {
        defaultScope: 'Singleton',
      },
    });
    const todoList = container.get(TodoList);
    const store = createStore({
      modules,
      container,
      ServiceIdentifiers,
      loadedModules: new Set(),
      load: (...args: any[]) => {
        //
      },
      pluginHooks: {
        middleware: [],
        beforeCombineRootReducers: [],
        afterCombineRootReducers: [],
        enhancer: [],
        preloadedStateHandler: [],
        afterCreateStore: [],
        provider: [],
      },
      devOptions: {
        enablePatches: true,
      },
    });
    const originalTodoState = store.getState();
    expect(Object.values(store.getState())).toEqual([
      { list: [{ text: 'foo' }] },
    ]);
    expect(actionFn.mock.calls.length).toBe(0);
    todoList.add('test');
    expect(Object.values(store.getState())).toEqual([
      { list: [{ text: 'test' }, { text: 'test' }] },
    ]);
    expect(actionFn.mock.calls.length).toBe(1);
    expect(actionFn.mock.calls[0][0]._patches).toEqual([
      {
        op: 'replace',
        path: ['todo', 'list', 0, 'text'],
        value: 'test',
      },
      {
        op: 'add',
        path: ['todo', 'list', 1],
        value: {
          text: 'test',
        },
      },
    ]);
    expect(actionFn.mock.calls[0][0]._inversePatches).toEqual([
      {
        op: 'replace',
        path: ['todo', 'list', 0, 'text'],
        value: 'foo',
      },
      {
        op: 'replace',
        path: ['todo', 'list', 'length'],
        value: 1,
      },
    ]);
    expect(
      applyPatches(originalTodoState, actionFn.mock.calls[0][0]._patches)
    ).toEqual(store.getState());
    expect(
      applyPatches(originalTodoState, actionFn.mock.calls[0][0]._patches) ===
        store.getState()
    ).toBe(false);
    expect(
      applyPatches(store.getState(), actionFn.mock.calls[0][0]._inversePatches)
    ).toEqual(originalTodoState);
  });
});