Selaa lähdekoodia

Added reducerFactory and tests

Hugo Häggmark 6 vuotta sitten
vanhempi
commit
0ddaa95d0e

+ 1 - 1
public/app/core/redux/actionCreatorFactory.test.ts

@@ -11,7 +11,7 @@ interface Dummy {
   b: boolean;
 }
 
-const setup = payload => {
+const setup = (payload: Dummy) => {
   resetAllActionCreatorTypes();
   const actionCreator = actionCreatorFactory<Dummy>('dummy').create();
   const result = actionCreator(payload);

+ 99 - 0
public/app/core/redux/reducerFactory.test.ts

@@ -0,0 +1,99 @@
+import { reducerFactory } from './reducerFactory';
+import { actionCreatorFactory, GrafanaAction } from './actionCreatorFactory';
+
+interface DummyReducerState {
+  n: number;
+  s: string;
+  b: boolean;
+  o: {
+    n: number;
+    s: string;
+    b: boolean;
+  };
+}
+
+const dummyReducerIntialState: DummyReducerState = {
+  n: 1,
+  s: 'One',
+  b: true,
+  o: {
+    n: 2,
+    s: 'two',
+    b: false,
+  },
+};
+
+const dummyActionCreator = actionCreatorFactory<DummyReducerState>('dummy').create();
+
+const dummyReducer = reducerFactory(dummyReducerIntialState)
+  .addHandler({
+    creator: dummyActionCreator,
+    handler: ({ state, action }) => {
+      return { ...state, ...action.payload };
+    },
+  })
+  .create();
+
+describe('reducerFactory', () => {
+  describe('given it is created with a defined handler', () => {
+    describe('when reducer is called with no state', () => {
+      describe('and with an action that the handler can not handle', () => {
+        it('then the resulting state should be intial state', () => {
+          const result = dummyReducer(undefined as DummyReducerState, {} as GrafanaAction<any>);
+
+          expect(result).toEqual(dummyReducerIntialState);
+        });
+      });
+
+      describe('and with an action that the handler can handle', () => {
+        it('then the resulting state should correct', () => {
+          const payload = { n: 10, s: 'ten', b: false, o: { n: 20, s: 'twenty', b: true } };
+          const result = dummyReducer(undefined as DummyReducerState, dummyActionCreator(payload));
+
+          expect(result).toEqual(payload);
+        });
+      });
+    });
+
+    describe('when reducer is called with a state', () => {
+      describe('and with an action that the handler can not handle', () => {
+        it('then the resulting state should be intial state', () => {
+          const result = dummyReducer(dummyReducerIntialState, {} as GrafanaAction<any>);
+
+          expect(result).toEqual(dummyReducerIntialState);
+        });
+      });
+
+      describe('and with an action that the handler can handle', () => {
+        it('then the resulting state should correct', () => {
+          const payload = { n: 10, s: 'ten', b: false, o: { n: 20, s: 'twenty', b: true } };
+          const result = dummyReducer(dummyReducerIntialState, dummyActionCreator(payload));
+
+          expect(result).toEqual(payload);
+        });
+      });
+    });
+  });
+
+  describe('given a handler is added', () => {
+    describe('when a handler with the same creator is added', () => {
+      it('then is should throw', () => {
+        const faultyReducer = reducerFactory(dummyReducerIntialState).addHandler({
+          creator: dummyActionCreator,
+          handler: ({ state, action }) => {
+            return { ...state, ...action.payload };
+          },
+        });
+
+        expect(() => {
+          faultyReducer.addHandler({
+            creator: dummyActionCreator,
+            handler: ({ state }) => {
+              return state;
+            },
+          });
+        }).toThrow();
+      });
+    });
+  });
+});

+ 55 - 0
public/app/core/redux/reducerFactory.ts

@@ -0,0 +1,55 @@
+import { GrafanaAction, GrafanaActionCreator } from './actionCreatorFactory';
+import { Reducer } from 'redux';
+
+export interface ActionHandler<State, Payload> {
+  state: State;
+  action: GrafanaAction<Payload>;
+}
+
+export interface ActionHandlerConfig<State, Payload> {
+  creator: GrafanaActionCreator<Payload>;
+  handler: (handler: ActionHandler<State, Payload>) => State;
+}
+
+export interface AddActionHandler<State> {
+  addHandler: <Payload>(config: ActionHandlerConfig<State, Payload>) => CreateReducer<State>;
+}
+
+export interface CreateReducer<State> extends AddActionHandler<State> {
+  create: () => Reducer<State, GrafanaAction<any>>;
+}
+
+export const reducerFactory = <State>(initialState: State): AddActionHandler<State> => {
+  const allHandlerConfigs: Array<ActionHandlerConfig<State, any>> = [];
+
+  const addHandler = <Payload>(config: ActionHandlerConfig<State, Payload>): CreateReducer<State> => {
+    if (allHandlerConfigs.some(c => c.creator.type === config.creator.type)) {
+      throw new Error(`There is already a handlers defined with the type ${config.creator.type}`);
+    }
+
+    allHandlerConfigs.push(config);
+
+    return instance;
+  };
+
+  const create = (): Reducer<State, GrafanaAction<any>> => {
+    const reducer: Reducer<State, GrafanaAction<any>> = (state: State = initialState, action: GrafanaAction<any>) => {
+      const validHandlers = allHandlerConfigs
+        .filter(config => config.creator.type === action.type)
+        .map(config => config.handler);
+
+      return validHandlers.reduce((currentState, handler) => {
+        return handler({ state: currentState, action });
+      }, state || initialState);
+    };
+
+    return reducer;
+  };
+
+  const instance: CreateReducer<State> = {
+    addHandler,
+    create,
+  };
+
+  return instance;
+};