zoukankan      html  css  js  c++  java
  • [React] Advanced Epic RxJS pattern with testing

    Epic:

    import { ofType } from 'redux-observable'
    import { of, concat, merge, fromEvent, race, forkJoin } from 'rxjs'
    
    const buildAPI = (apiBase, perPage, searchContent) =>
      `${apiBase}?beer_name=${encodeURIComponent(
        searchContent,
      )}&per_page=${perPage}`
    
    const randomApi = (apiBase) => `${apiBase}/random`// getJSON is passing from the dependeniences
    export function fetchBeersEpic(action$, state$, { getJSON }) {
      return action$.pipe(
        ofType(SEARCH),
        // avoid too many request to server
        debounceTime(500),
        // Filter out empty search
        filter(({ payload }) => payload.trim() !== ''),
        // Avoid sending the same request payload to server
        distinctUntilChanged(),
        // Get Config State
        withLatestFrom(state$.pipe(pluck('config'))),
        // Ignore the previous request's response
        switchMap(([{ payload }, config]) => {
          // Network reqest
          // This observable can be cancelled by blockers$
          const ajax$ = getJSON(
            buildAPI(config.apiBase, config.perPage, payload),
          ).pipe(
            // Dispatch fulfilled action
            map((resp) => fetchFulfilled(resp)),
            catchError((err) => {
              // If error, dispatch fail action
              return of(fetchFailed(err.response.message))
            }),
          )
    
          // Canceller
          // Used to cancel the network request when press "Esc" key
          // Or Cancel button was clicked
          // Or this observable can be cancelled by ajax$
          const blockers$ = merge(
            action$.pipe(ofType(CANCEL)),
            fromEvent(document, 'keyup').pipe(
              filter((e) => e.key === 'Escape' || e.key === 'Esc'),
            ),
          ).pipe(
            // Dispatch reset action
            mapTo(reset()),
          )
    
          // Dispatch setStatus action
          // and wait ajax$ or blockers$, depends on which is faster
          // Faster one will cancel the slower one
          return concat(of(setStatus('pending')), race(ajax$, blockers$))
        }),
      )
    }

    Testing:

    import { TestScheduler } from 'rxjs/testing'...
    
    it('produces correct actions (success)', function() {
      const testScheduler = new TestScheduler((actual, expected) => {
        expect(actual).toEqual(expected)
      })
    
      testScheduler.run(({ hot, cold, expectObservable }) => {
        const action$ = hot('a', {
          a: search('ship'),
        })
        const state$ = of({
          config: initialState,
        })
        const dependencies = {
          getJSON: (url) => {
            return cold('---a', {
              a: [{ name: 'Beer 1' }],
            })
          },
        }
        const output$ = fetchBeersEpic(action$, state$, dependencies)
        // a: 500ms
        // -: 501ms,
        // b: 502ms
        expectObservable(output$).toBe('500ms a--b', {
          a: setStatus('pending'),
          b: fetchFulfilled([{ name: 'Beer 1' }]),
        })
      })
    })
    
    it('produces correct actions (error)', function() {
      const testScheduler = new TestScheduler((actual, expected) => {
        expect(actual).toEqual(expected)
      })
    
      testScheduler.run(({ hot, cold, expectObservable }) => {
        const action$ = hot('a', {
          a: search('ship'),
        })
        const state$ = of({
          config: initialState,
        })
        const dependencies = {
          getJSON: (url) => {
            return cold('---#', null, {
              response: {
                message: 'oops!',
              },
            })
          },
        }
        const output$ = fetchBeersEpic(action$, state$, dependencies)
    
        expectObservable(output$).toBe('500ms a--b', {
          a: setStatus('pending'),
          b: fetchFailed('oops!'),
        })
      })
    })
    
    it('produces correct actions (reset)', function() {
      const testScheduler = new TestScheduler((actual, expected) => {
        expect(actual).toEqual(expected)
      })
    
      testScheduler.run(({ hot, cold, expectObservable }) => {
        const action$ = hot('a 500ms -b', {
          a: search('ship'),
          b: cancel(),
        })
        const state$ = of({
          config: initialState,
        })
        const dependencies = {
          getJSON: (url) => {
            return cold('---a', [{ name: 'Beer 1' }])
          },
        }
        const output$ = fetchBeersEpic(action$, state$, dependencies)
        expectObservable(output$).toBe('500ms a-b', {
          a: setStatus('pending'),
          b: reset(),
        })
      })
    })

    Config:

    import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
    import { combineEpics, createEpicMiddleware } from 'redux-observable'
    import { fetchBeersEpic, randomFetchEpic } from './epics/fetchBeers'
    import { beersReducers } from './reducers/beerReducer'
    import { configReducer } from './reducers/configReducer'
    import { persistEpic, hydrateEpic } from './epics/persist'
    import { ajax } from 'rxjs/ajax'
    export function configureStore(dependencies = {}) {
      const rootEpic = combineEpics(
        randomFetchEpic,
        fetchBeersEpic,
        persistEpic,
        hydrateEpic,
      )
    
      // Provide platform dependency
      // this make testing easier
      const epicMiddleware = createEpicMiddleware({
        dependencies: {
          getJSON: ajax.getJSON,
          document: document,
          ...dependencies,
        },
      })
    
      // compose reducers into a single root reducer
      const rootReducer = combineReducers({
        beers: beersReducers,
        config: configReducer,
      })
    
      // Enable redux devtools
      const composeEnhancers =
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
    
      const store = createStore(
        rootReducer,
        composeEnhancers(applyMiddleware(epicMiddleware)),
      )
    
      epicMiddleware.run(rootEpic)
      return store
    }
    

      

  • 相关阅读:
    [DOJ练习] 无向图的邻接矩阵表示法验证程序
    [DOJ练习] 求无向图中某顶点的度
    [邻接表形式]有向图的建立与深度,广度遍历
    [DOJ练习] 有向图的邻接表表示法验证程序(两种写法)
    [Java 学习笔记] 异常处理
    [总结]单源最短路(朴素Dijkstra)与最小生成树(Prim,Kruskal)
    时间选择插件jquery.timepickr
    页面值传入后台出现中文乱码
    CheckTreecheckbox树形控件
    JQuery EasyUI DataGrid
  • 原文地址:https://www.cnblogs.com/Answer1215/p/12806290.html
Copyright © 2011-2022 走看看