UNPKG

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

201 lines (180 loc) 7.52 kB
import Necto from './necto'; describe('Necto', () => { describe('Instantiation', () => { it('should instantiate successfully with a name', () => { const test = new Necto('test'); expect(test.name).toBe('test'); }); }); describe('createFlow', () => { it('should attach an action and a constant to the instance', () => { const test = new Necto('test'); test.createFlow('someFlow'); expect(test.Actions).toHaveProperty('someFlow', expect.any(Function)); expect(test.Constants).toHaveProperty('SOME_FLOW', 'TEST/SOME_FLOW'); }); it('should not create a reducer or saga if no flowPath is given', () => { const name = 'test'; const test = new Necto(name); const someFlow = test.createFlow('someFlow'); expect(someFlow.action).toBeDefined(); expect(someFlow.reducer).not.toBeDefined(); expect(someFlow.saga).not.toBeDefined(); }); it('should create a reducer if (state, action) is used as the flowPath', () => { const name = 'test'; const test = new Necto(name); const someFlow = test.createFlow('someFlow', (state, action) => ({ foo: 'bar', })); expect(someFlow.action).toBeDefined(); expect(someFlow.reducer).toBeDefined(); expect(someFlow.saga).not.toBeDefined(); expect(someFlow.reducer(null, { foo: 'bar' })).toEqual({ foo: 'bar', }); }); it('should create a saga if *(action) is used as the flowPath', () => { const name = 'test'; const test = new Necto(name); const someFlow = test.createFlow('someFlow', function*(action) {}); expect(someFlow.action).toBeDefined(); expect(someFlow.saga).toBeDefined(); expect(someFlow.reducer).not.toBeDefined(); }); describe('Setting the _async property', () => { it('should set _async to false when there is not a flow path', () => { const name = 'test'; const test = new Necto(name); const someFlow = test.createFlow('someFlow'); expect(someFlow.action('test')).toHaveProperty('_async', false); }); it('should set _async to false when a reducer is the flow path', () => { const name = 'test'; const test = new Necto(name); const someFlow = test.createFlow('someFlow', (state, action) => state); expect(someFlow.action('test')).toHaveProperty('_async', false); }); it('should set _async to true when a saga is the flow path', () => { const name = 'test'; const test = new Necto(name); const someFlow = test.createFlow('someFlow', function*(action) {}); expect(someFlow.action('test')).toHaveProperty('_async', true); }); }); }); describe('Using Flows', () => { beforeEach(() => { jest.clearAllMocks(); }); it('should handle error objects as the payload', () => { const test = new Necto('test'); test.createFlow('someFlow'); const errorName = 'SomeFlowError'; const error = new Error(errorName); expect(test.Actions.someFlow('description', error).payload.message).toBe( errorName ); }); it('should throw when an action is called without an interaction description', () => { const test = new Necto('test'); test.createFlow('someFlow'); expect(() => { test.Actions.someFlow(); }).toThrow(); }); it('should throw when an action is called with a non-string interaction description', () => { const test = new Necto('test'); test.createFlow('someFlow'); expect(() => { test.Actions.someFlow({ foo: 'bar' }); }).toThrow(); }); it('should not throw when an action is called without an interaction description and interactionRequired is false', () => { const test = new Necto('test'); test.createFlow('someFlow', () => ({}), { interactionRequired: false }); expect(() => { test.Actions.someFlow(); }).not.toThrow(); }); it('should return a plain object when a action is called', () => { const test = new Necto('test'); test.createFlow('someFlow'); const description = 'some interaction'; const action = test.Actions.someFlow(description); expect(action).toHaveProperty('payload', {}); expect(action).toHaveProperty('meta', {}); expect(action).toHaveProperty('_actionType', 'TEST/SOME_FLOW'); expect(action).toHaveProperty('_interaction', description); expect(action).toHaveProperty('_requiredParams', []); expect(action).toHaveProperty('_async', false); }); it('should use the interaction when interaction is not required', () => { const test = new Necto('test'); test.createFlow('someFlow', () => ({}), { interactionRequired: false }); const interaction = 'This is a test.'; const payload = { foo: 'bar' }; const meta = { bar: 'foo' }; const action = test.Actions.someFlow(interaction, payload, meta); expect(action.payload).toEqual(payload); expect(action.meta).toEqual(meta); expect(action._interaction).toEqual(interaction); expect(action.type).toEqual(expect.stringContaining(interaction)); expect(action.type).toEqual(`[TEST/SOME_FLOW] ${interaction}`); }); it('should reorder the interaction, payload, and meta arguments when interaction is not required and no interaction is used', () => { const test = new Necto('test'); test.createFlow('someFlow', () => ({}), { interactionRequired: false }); const payload = { foo: 'bar' }; const meta = { bar: 'foo' }; const action = test.Actions.someFlow(payload, meta); expect(action.payload).toEqual(payload); expect(action.meta).toEqual(meta); expect(action._interaction).toEqual(null); expect(action.type).toEqual('[TEST/SOME_FLOW]'); }); it('should throw a console warning if an error occurs in the reducer', () => { jest.spyOn(global.console, 'error').mockImplementation(() => {}); const name = 'test'; const test = new Necto(name); const someFlow = test.createFlow('someFlow', (state, action) => { return { ...state, foo: action.foo.bar, // bar does not exist here }; }); const initialState = { foo: 'bar' }; const action = someFlow.action('example'); const reducers = test.getReducers(); reducers[name](initialState, action); expect(console.error).toHaveBeenCalledTimes(1); expect(console.error).toHaveBeenCalledWith(expect.any(Error)); }); // it('should throw ', () => { // jest.spyOn(global.console, 'error').mockImplementation(() => {}); // const test = new Necto('test'); // test.createFlow('someFlow', (state, action) => ({ // ...state, // foo: action.foo // }), { // requiredParams: { // } // }); // }); }); }); describe('Extending Necto', () => { describe('getInitialFlows', () => { it('should create no default flows if getInitialFlows is overridden and returns nothing', () => { class ExtendedNecto extends Necto { getInitialFlows(createFlow, necto) { return []; } } const test = new ExtendedNecto('test'); expect(test).not.toHaveProperty('Actions.isLoading'); expect(test).not.toHaveProperty('Actions.isLoadingComplete'); expect(test).not.toHaveProperty('Actions.mergeDataFromApi'); }); }); });