reduxTester.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import {
  2. AnyAction,
  3. configureStore,
  4. EnhancedStore,
  5. Reducer,
  6. getDefaultMiddleware,
  7. CombinedState,
  8. PreloadedState,
  9. } from '@reduxjs/toolkit';
  10. import { NoInfer } from '@reduxjs/toolkit/dist/tsHelpers';
  11. import { Dispatch, Middleware, MiddlewareAPI } from 'redux';
  12. import thunk, { ThunkMiddleware } from 'redux-thunk';
  13. import { setStore } from '../../../app/store/store';
  14. import { StoreState } from '../../../app/types';
  15. export interface ReduxTesterGiven<State> {
  16. givenRootReducer: (rootReducer: Reducer<State>) => ReduxTesterWhen<State>;
  17. }
  18. export interface ReduxTesterWhen<State> {
  19. whenActionIsDispatched: (
  20. action: any,
  21. clearPreviousActions?: boolean
  22. ) => ReduxTesterWhen<State> & ReduxTesterThen<State>;
  23. whenAsyncActionIsDispatched: (
  24. action: any,
  25. clearPreviousActions?: boolean
  26. ) => Promise<ReduxTesterWhen<State> & ReduxTesterThen<State>>;
  27. }
  28. export interface ReduxTesterThen<State> {
  29. thenDispatchedActionsShouldEqual: (...dispatchedActions: AnyAction[]) => ReduxTesterWhen<State>;
  30. thenDispatchedActionsPredicateShouldEqual: (
  31. predicate: (dispatchedActions: AnyAction[]) => boolean
  32. ) => ReduxTesterWhen<State>;
  33. thenNoActionsWhereDispatched: () => ReduxTesterWhen<State>;
  34. }
  35. export interface ReduxTesterArguments<State> {
  36. preloadedState?: PreloadedState<CombinedState<NoInfer<State>>>;
  37. debug?: boolean;
  38. }
  39. export const reduxTester = <State>(args?: ReduxTesterArguments<State>): ReduxTesterGiven<State> => {
  40. const dispatchedActions: AnyAction[] = [];
  41. const logActionsMiddleWare: Middleware<{}, Partial<StoreState>> =
  42. (store: MiddlewareAPI<Dispatch, Partial<StoreState>>) => (next: Dispatch) => (action: AnyAction) => {
  43. // filter out thunk actions
  44. if (action && typeof action !== 'function') {
  45. dispatchedActions.push(action);
  46. }
  47. return next(action);
  48. };
  49. const preloadedState = args?.preloadedState ?? ({} as unknown as PreloadedState<CombinedState<NoInfer<State>>>);
  50. const debug = args?.debug ?? false;
  51. let store: EnhancedStore<State, AnyAction, []> | null = null;
  52. const defaultMiddleware = getDefaultMiddleware<State>({
  53. thunk: false,
  54. serializableCheck: false,
  55. immutableCheck: false,
  56. } as any);
  57. const givenRootReducer = (rootReducer: Reducer<State>): ReduxTesterWhen<State> => {
  58. store = configureStore<State, AnyAction, Array<Middleware<State>>>({
  59. reducer: rootReducer,
  60. middleware: [...defaultMiddleware, logActionsMiddleWare, thunk] as unknown as [ThunkMiddleware<State>],
  61. preloadedState,
  62. });
  63. setStore(store as any);
  64. return instance;
  65. };
  66. const whenActionIsDispatched = (
  67. action: any,
  68. clearPreviousActions?: boolean
  69. ): ReduxTesterWhen<State> & ReduxTesterThen<State> => {
  70. if (clearPreviousActions) {
  71. dispatchedActions.length = 0;
  72. }
  73. if (store === null) {
  74. throw new Error('Store was not setup properly');
  75. }
  76. store.dispatch(action);
  77. return instance;
  78. };
  79. const whenAsyncActionIsDispatched = async (
  80. action: any,
  81. clearPreviousActions?: boolean
  82. ): Promise<ReduxTesterWhen<State> & ReduxTesterThen<State>> => {
  83. if (clearPreviousActions) {
  84. dispatchedActions.length = 0;
  85. }
  86. if (store === null) {
  87. throw new Error('Store was not setup properly');
  88. }
  89. await store.dispatch(action);
  90. return instance;
  91. };
  92. const thenDispatchedActionsShouldEqual = (...actions: AnyAction[]): ReduxTesterWhen<State> => {
  93. if (debug) {
  94. console.log('Dispatched Actions', JSON.stringify(dispatchedActions, null, 2));
  95. }
  96. if (!actions.length) {
  97. throw new Error('thenDispatchedActionShouldEqual has to be called with at least one action');
  98. }
  99. expect(dispatchedActions).toEqual(actions);
  100. return instance;
  101. };
  102. const thenDispatchedActionsPredicateShouldEqual = (
  103. predicate: (dispatchedActions: AnyAction[]) => boolean
  104. ): ReduxTesterWhen<State> => {
  105. if (debug) {
  106. console.log('Dispatched Actions', JSON.stringify(dispatchedActions, null, 2));
  107. }
  108. expect(predicate(dispatchedActions)).toBe(true);
  109. return instance;
  110. };
  111. const thenNoActionsWhereDispatched = (): ReduxTesterWhen<State> => {
  112. if (debug) {
  113. console.log('Dispatched Actions', JSON.stringify(dispatchedActions, null, 2));
  114. }
  115. expect(dispatchedActions.length).toBe(0);
  116. return instance;
  117. };
  118. const instance = {
  119. givenRootReducer,
  120. whenActionIsDispatched,
  121. whenAsyncActionIsDispatched,
  122. thenDispatchedActionsShouldEqual,
  123. thenDispatchedActionsPredicateShouldEqual,
  124. thenNoActionsWhereDispatched,
  125. };
  126. return instance;
  127. };