necto
Version:
Necto compliments Redux by providing a composable, declarative api to create flows through redux (Action -> Reducer or Action -> Saga). The intent of Necto is to reduce boilerplate, simplify and standardize action creators, and group action logic so that
117 lines (101 loc) • 4.27 kB
JavaScript
import createReducer, { callHandler, validateHandler } from './create_reducer';
describe('Create Reducer', () => {
describe('No Handlers', () => {
const handlers = {};
const initialState = { foo: 'bar' };
const reducer = createReducer(initialState, handlers);
it('should return a reducer function when given zero handlers', () => {
expect(reducer).toEqual(expect.any(Function));
});
it('should return the initial state when no handlers are given and the reducer is called without a new state', () => {
const action = { type: 'TEST' };
expect(reducer(undefined, action)).toEqual({ ...initialState });
});
it('should return the next state when no handlers are given', () => {
const nextState = { bar: 'foo' };
const action = { type: 'TEST' };
expect(reducer(nextState, action)).toEqual({ ...nextState });
});
});
describe('Calling Handlers', () => {
const initialState = { foo: 'bar' };
const handlers = {
A: jest.fn(),
B: jest.fn(),
};
const reducer = createReducer(initialState, handlers);
beforeEach(() => {
jest.clearAllMocks();
});
it('should return the state when _actionType and type are not in the action', () => {
const nextState = { bar: 'foo' };
const action = { payload: 'test' };
expect(reducer(nextState, action)).toEqual({ ...nextState });
});
it('should use _actiontype over type when both are present in the action', () => {
const nextState = { bar: 'foo' };
const action = { _actionType: 'A', type: 'B' };
reducer(nextState, action);
expect(handlers.A).toHaveBeenCalledWith(nextState, action);
expect(handlers.B).not.toHaveBeenCalled();
});
it('should use type when _actionType is not present in the action', () => {
const nextState = { bar: 'foo' };
const action = { type: 'B' };
reducer(nextState, action);
expect(handlers.A).not.toHaveBeenCalled();
expect(handlers.B).toHaveBeenCalledWith(nextState, action);
});
});
describe('Using callHandler', () => {
const initialState = { foo: 'bar' };
const handlers = {
A: jest.fn().mockImplementation((state, action) => ({
...state,
A: true,
})),
B: jest.fn().mockImplementation((state, action) => ({
...state,
B: true,
})),
C: jest.fn().mockImplementation(() => ({
C: true,
})),
failure: jest.fn().mockImplementation((state, action) => ({
fail: action.should.fail.the.test,
})),
};
const reducer = createReducer(initialState, handlers);
beforeEach(() => {
jest.clearAllMocks();
});
it('should call the correct handler and return the value of the handler implementation', () => {
const nextState = { bar: 'foo' };
const action = { _actionType: 'A', type: 'B' };
const doCallHandler = callHandler(handlers, nextState, action);
expect(doCallHandler('_actionType')).toEqual({ ...nextState, A: true });
});
it('should not modify the return value of the handler', () => {
const nextState = { bar: 'foo' };
const action = { _actionType: 'C', type: 'B', actionType: 'A' };
const doCallHandler = callHandler(handlers, nextState, action);
expect(doCallHandler('_actionType')).toEqual({ C: true });
});
it('should console.error an error when reducer handler fails to run', () => {
jest.spyOn(global.console, 'error').mockImplementation(() => {});
const nextState = { bar: 'foo' };
const action = { _actionType: 'failure' };
const doCallHandler = callHandler(handlers, nextState, action);
doCallHandler('_actionType');
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(expect.any(Error));
});
it('should still return the state value when a reducer handler errors', () => {
jest.spyOn(global.console, 'error').mockImplementation(() => {});
const nextState = { bar: 'foo' };
const action = { _actionType: 'failure' };
const doCallHandler = callHandler(handlers, nextState, action);
expect(doCallHandler('_actionType')).toEqual({ ...nextState });
});
});
});