Rework afterEach callbacks of generateReducer. NEXT: add `beforeFilter`.

pull/1348/head
Rick Carlino 2019-07-30 16:30:07 -05:00
parent 0b956f28d9
commit 5c068ad158
5 changed files with 45 additions and 28 deletions

View File

@ -1,6 +1,7 @@
jest.mock("../maybe_start_tracking", () => {
return { maybeStartTracking: jest.fn() };
});
jest.mock("../../read_only_mode", () => ({ appIsReadonly: jest.fn() }));
const mockBody: Partial<TaggedUser["body"]> = { id: 23 };
jest.mock("axios", () => {

View File

@ -1,7 +1,17 @@
const mockResource: { kind: string, body: { id: number | undefined } }
= { kind: "Regimen", body: { id: 1 } };
interface MockRespone {
kind: string;
body: {
id: number | undefined;
}
}
const mockResource: MockRespone = { kind: "Regimen", body: { id: 1 } };
let mockDelete: Promise<{} | void> = Promise.resolve({});
jest.mock("../../resources/reducer_support", () => ({
findByUuid: () => (mockResource)
findByUuid: () => (mockResource),
afterEach: (s: {}) => s
}));
jest.mock("../../resources/actions", () => ({
@ -13,11 +23,12 @@ jest.mock("../maybe_start_tracking", () => ({
maybeStartTracking: jest.fn()
}));
let mockDelete: Promise<{} | void> = Promise.resolve({});
jest.mock("axios", () => ({
delete: jest.fn(() => mockDelete)
}));
jest.mock("../../read_only_mode", () => ({ appIsReadonly: jest.fn() }));
import { destroy, destroyAll } from "../crud";
import { API } from "../api";
import axios from "axios";

View File

@ -79,7 +79,8 @@ export let initialState = (): BotState => ({
}
});
export let botReducer = generateReducer<BotState>(initialState(), afterEach)
export let botReducer = generateReducer<BotState>(initialState())
.afterEach(afterEach)
.add<boolean>(Actions.SET_CONSISTENCY, (s, a) => {
s.consistent = a.payload;
s.hardware.informational_settings.sync_status = maybeNegateStatus({

View File

@ -5,48 +5,51 @@ import { Dictionary } from "farmbot";
/** A function that responds to a particular action from within a
* generated reducer. */
export interface ActionHandler<State, Payl = unknown> {
(state: State, action: ReduxAction<Payl>): State;
export interface ActionHandler<State, Payload = unknown> {
(state: State, action: ReduxAction<Payload>): State;
}
export function generateReducer<State, U = unknown>(initialState: State,
/** For passing state down to children. */
afterEach?: (s: State, a: ReduxAction<U>) => State) {
type ActionHandlerDict = Dictionary<ActionHandler<State>>;
export function generateReducer<State>(initialState: State) {
interface GeneratedReducer extends ActionHandler<State> {
/** Adds action handler for current reducer. */
add: <T>(name: Actions, fn: ActionHandler<State, T>) => GeneratedReducer;
// Calms the type checker.
afterEach(handler: ActionHandler<State>): GeneratedReducer;
beforeFilter(): GeneratedReducer;
}
const actionHandlers: ActionHandlerDict = {};
interface PrivateStuff {
actionHandlers: ActionHandlerDict;
afterEach: ActionHandler<State>;
}
type ActionHandlerDict = Dictionary<ActionHandler<State>>;
const NOOP: ActionHandler<State> = (s) => s;
const priv: PrivateStuff =
({ actionHandlers: {}, afterEach: NOOP });
const reducer: GeneratedReducer =
// tslint:disable-next-line:no-any
((state = initialState, action: ReduxAction<any>): State => {
((state = initialState, action: ReduxAction<unknown>): State => {
// Find the handler in the dictionary, or use the NOOP.
const handler = (actionHandlers[action.type] || NOOP);
const handler = (priv.actionHandlers[action.type] || NOOP);
// Defensively clone the action and state to avoid accidental mutations.
const clonedState = defensiveClone(state);
const clonedAction = defensiveClone(action);
// Run the reducer.
let result: State = handler(clonedState, clonedAction);
// Run main action handler
const state1 = handler(clonedState, clonedAction);
// Give the "afterEach" reducer a chance to run.
result = (afterEach || NOOP)(defensiveClone(result), action);
return result;
// Run `afterEach` (if any). Else, just return the state object as-is.
return priv.afterEach(state1, action);
}) as GeneratedReducer;
reducer.add = <X>(name: string, fn: ActionHandler<State, X>) => {
actionHandlers[name] = fn;
priv.actionHandlers[name] = fn;
return reducer;
};
reducer.afterEach = (handler) => {
priv.afterEach = handler;
return reducer;
};

View File

@ -77,7 +77,8 @@ export const emptyState = (): RestResources => {
/** Responsible for all RESTful resources. */
export let resourceReducer =
generateReducer<RestResources>(emptyState(), (s, a) => afterEach(s, a))
generateReducer<RestResources>(emptyState())
.afterEach(afterEach)
.add<TaggedResource>(Actions.SAVE_RESOURCE_OK, (s, { payload }) => {
indexUpsert(s.index, [payload], "ongoing");
mutateSpecialStatus(payload.uuid, s.index, SpecialStatus.SAVED);