Rework afterEach callbacks of generateReducer. NEXT: add `beforeFilter`.
parent
0b956f28d9
commit
5c068ad158
|
@ -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", () => {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue