reduxTester.ts 4.25 KB
Newer Older
1
import { Dispatch, Middleware, MiddlewareAPI } from 'redux';
2 3
import thunk, { ThunkMiddleware } from 'redux-thunk';
import { AnyAction, configureStore, EnhancedStore, Reducer, getDefaultMiddleware } from '@reduxjs/toolkit';
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

import { StoreState } from '../../../app/types';
import { setStore } from '../../../app/store/store';

export interface ReduxTesterGiven<State> {
  givenRootReducer: (rootReducer: Reducer<State>) => ReduxTesterWhen<State>;
}

export interface ReduxTesterWhen<State> {
  whenActionIsDispatched: (
    action: any,
    clearPreviousActions?: boolean
  ) => ReduxTesterWhen<State> & ReduxTesterThen<State>;
  whenAsyncActionIsDispatched: (
    action: any,
    clearPreviousActions?: boolean
  ) => Promise<ReduxTesterWhen<State> & ReduxTesterThen<State>>;
}

export interface ReduxTesterThen<State> {
24 25
  thenDispatchedActionsShouldEqual: (...dispatchedActions: AnyAction[]) => ReduxTesterWhen<State>;
  thenDispatchedActionsPredicateShouldEqual: (
26 27
    predicate: (dispatchedActions: AnyAction[]) => boolean
  ) => ReduxTesterWhen<State>;
28
  thenNoActionsWhereDispatched: () => ReduxTesterWhen<State>;
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
}

export interface ReduxTesterArguments<State> {
  preloadedState?: State;
  debug?: boolean;
}

export const 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;

53 54 55 56 57 58
  const defaultMiddleware = getDefaultMiddleware<State>({
    thunk: false,
    serializableCheck: false,
    immutableCheck: false,
  } as any);

59 60 61
  const givenRootReducer = (rootReducer: Reducer<State>): ReduxTesterWhen<State> => {
    store = configureStore<State>({
      reducer: rootReducer,
62
      middleware: [...defaultMiddleware, logActionsMiddleWare, thunk] as [ThunkMiddleware<State>],
63 64
      preloadedState,
    });
65

66 67 68 69 70 71 72 73 74 75 76 77
    setStore(store as any);

    return instance;
  };

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

79 80 81
    if (store === null) {
      throw new Error('Store was not setup properly');
    }
82

83
    store.dispatch(action);
84 85 86 87 88 89 90 91 92 93 94
    return instance;
  };

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

95 96 97
    if (store === null) {
      throw new Error('Store was not setup properly');
    }
98

99
    await store.dispatch(action);
100 101 102
    return instance;
  };

103
  const thenDispatchedActionsShouldEqual = (...actions: AnyAction[]): ReduxTesterWhen<State> => {
104 105 106 107 108 109 110 111 112 113 114 115
    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;
  };

116
  const thenDispatchedActionsPredicateShouldEqual = (
117 118 119 120 121 122 123 124 125 126
    predicate: (dispatchedActions: AnyAction[]) => boolean
  ): ReduxTesterWhen<State> => {
    if (debug) {
      console.log('Dispatched Actions', JSON.stringify(dispatchedActions, null, 2));
    }

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

127 128 129 130 131 132 133 134 135
  const thenNoActionsWhereDispatched = (): ReduxTesterWhen<State> => {
    if (debug) {
      console.log('Dispatched Actions', JSON.stringify(dispatchedActions, null, 2));
    }

    expect(dispatchedActions.length).toBe(0);
    return instance;
  };

136 137 138 139
  const instance = {
    givenRootReducer,
    whenActionIsDispatched,
    whenAsyncActionIsDispatched,
140 141
    thenDispatchedActionsShouldEqual,
    thenDispatchedActionsPredicateShouldEqual,
142
    thenNoActionsWhereDispatched,
143 144 145 146
  };

  return instance;
};