redux-easy-connect
v0.0.18
Published
Test
Downloads
23
Maintainers
Readme
Redux easy connect contains:
- ReduxComponent - Base class that provide redux CONNECT action on easy way (ONLY REACT)
- reduxExtend - Same like base class, but it's implemented using class prototype. (REACT AND REACT NATIVE)
- TestHelper - Provide functions that allowing access to component directly. This include redux actions, state, reducers and public functions
- setType - Init rule for all action types
How to use
import {reduxExtend,
ReduxComponent,
TestHelper,
setType} from 'redux-easy-connect';
// Export component (ReactJS, ReactNative)
export default reduxExtend(Component);
// Export component (ReactJS)
class Example extend ReduxComponent
export default Example.CONNECT
// Include component
<Example
actions={ExampleActions}
reducers={{userStore:["user"]}}
services={{AuthService}}
/>
// set action type
export const EXAMPLE_GET_USER = setType("example/GET_USER");
// use action type with dispatch
dispatch(EXAMPLE_GET_USER({/* props that will be provided to reducers */}))
// use action type in reducers
switch(type)
case EXAMPLE_GET_USER.type :
/* props that will be provided to component */
break;
// Call actions
this.Actions.getUser(1);
// Call services
this.Services.AuthService.getUser(1)
// Reducers
this.props.user
// Use test helper
const component = TestHelper.getComponent(rendered, Example);
// rendered created by react-test-renderer
component.setState(/**/);
component.Actions.getUser(1);
await component.waitForAllState();
await component.waitForAllDispatch();
expect(component.props.user).toBeInstanceOf(Object);
// Use test helper for load external javascript library''s
const window = await TestHelper.loadScript(
jsdom.JSDOM,
'path to script',
{jsdom options} ||
DEFAULT_JSDOM_OPTIONS /* isn''t required */);
// Use mock''s with global fetch
it('should expose global fetch object', TestHelper.isFetchGlobal);
const output = await TestHelper.mock([{
url:'http://localhost:3000/test',
body:{output:"example"},
status: 200}]);
// DEFAULT_JSDOM_OPTIONS
{
resources: 'usable',
runScripts: "dangerously",
url: "http://localhost:8080",
referrer: "http://localhost:8080",
contentType: "text/html",
userAgent: "Mellblomenator/9000"
}
Example
npm install
npm test
npm start
http://localhost:3000
reduxExtend / ReduxComponent
| Props | Type | Include | Example | Description | | --- | --- | --- | --- | --- | | actions | prop | actions={UserActions} | this.Actions.getUser() | Include list of actions that can be accessible in component | | services | prop | services={{UserService}} | this.UserService.getUser() | Include list of services that can be accessible in component | | reducers | prop | reducers={{userStore:["user"], authStore:true}} | this.props.user, this.props. "all props from auth store" | Include list of reducers and props that can be accessible in component | | waitForAllDispatch | func | | component. waitForAllDispatch() | Continue to execute code after all actions finished with executing. ONLY FOR TESTS | | waitForAllState | func | | component. waitForAllState() | Continue to execute code after all state actions finished with executing. ONLY FOR TESTS | | onDispatchEnd | event | | onDispatchEnd(args){ } | * | | onStateEnd | event | | onStateEnd(args){ } | * |
TestHelper
| Func | Arguments | Example | Description | | --- | --- | --- | --- | --- | | getComponent | RenderedComponent, Class component name | TestHelper. getComponent(rendered, Example): return Example; | Find and return react component | | loadScript | JSDOM, javascript path, options | TestHelper.loadScript(JSDOM, 'script path') : return window | Loading external javascript libs like a google maps | | isFetchGlobal | | it('should expose global fetch object', TestHelper.isFetchGlobal); | Test you api calls with global fetch | | mock | [{url, body, status}] | TestHelper. mock([{ url: "http://localhost/test", body: {example:"test"}, status: 200}]) : return Promise | Mock http requests made using fetch |
Full example
Action types
import {setType} from 'redux-easy-connect';
export const EXAMPLE_GET_USER = setType('example/GET_USER');
export const EXAMPLE_SAVE_USER = setType("example/SAVE_USER");
export const EXAMPLE_DELETE_USER = setType("example/DELETE_USER");
Redux store - Include services directly in actions
function configureStore(initialState) {
return createStore(rootReducer, initialState, compose(
applyMiddleware(
thunk.withExtraArgument(Services) // Include services like last params,
routerMiddleware(history),
reduxImmutableStateInvariant(),
),
window.devToolsExtension ? window.devToolsExtension() : f => f));
}
const store = configureStore({});
export default store;
Action example
import {EXAMPLE_GET_USER, EXAMPLE_DELETE_USER, EXAMPLE_SAVE_USER} from '../actionTypes';
export default class ExampleActions {
static getUser(id) {
return async (dispatch, getState, {UserService}) => {
const user = UserService.getUser(id);
dispatch(EXAMPLE_GET_USER({user}));
}
}
static saveUser(user) {
return async (dispatch, getState, {UserService}) => {
const output = UserService.saveUser(user);
dispatch(EXAMPLE_SAVE_USER({user:output}));
}
}
static deleteUser(user) {
return async (dispatch, getState, {UserService}) => {
const user = UserService.deleteUser(user);
dispatch(EXAMPLE_DELETE_USER({user}));
}
}
}
Reducer example
import {EXAMPLE_GET_USER, EXAMPLE_DELETE_USER, EXAMPLE_SAVE_USER} from '../actionTypes';
const initialState = {
user: null
};
export default function userReducer(state = initialState, action) {
switch (action.type) {
case EXAMPLE_GET_USER.type:
return Object.assign({}, {user:action.user});
case EXAMPLE_SAVE_USER.type:
return Object.assign({}, {user:action.user});
case EXAMPLE_DELETE_USER.type:
return Object.assign({}, {user:action.user});
default:
return state;
}
}
Component example
import React from 'react';
import {reduxExtend, ReduxComponent} from 'redux-easy-connect';
// export class Example extends ReduxComponent
export class Example extends React.Component{
constructor(props){
super(props);
this.handleAddUser = this.handleAddUser.bind(this);
this.handleGetUser = this.handleGetUser.bind(this);
this.handleDeleteUser = this.handleDeleteUser.bind(this);
}
handleAddUser(){
const user = {id:1,name:"Example",email:"[email protected]"};
this.Actions.saveUser(user);
}
handleGetUser(){
this.Actions.getUser(1);
}
handleDeleteUser(){
this.Actions.deleteUser(1);
}
handleExampleService(){
this.Services.AuthService.login(/* props */);
}
render(){
return (
<div>
<button onClick={this.handleAddUser}>Add user</button>
<button onClick={this.handleGetUser}>Get user</button>
<button onClick={this.handleDeleteUser}>Delete user</button>
<div>{this.props.user && JSON.stringify(this.props.user)}</div>
</div>
);
}
}
export default reduxExtend(Example);
// export default Example.CONNECT;
Test example
import React from 'react';
import renderer from 'react-test-renderer';
import {TestHelper} from 'redux-easy-connect';
import store from '../store'
import ExampleActions from '../example/actions';
import CONNECT, {Example} from '../example';
it('renders example with action button add user', async () => {
const rendered = renderer.create(
<CONNECT
store={store}
actions={ExampleActions}
reducers={{userStore:["user"]}}
/>
);
const component = TestHelper.getComponent(rendered.getInstance(), Example);
component.handleAddUser();
await component.waitForAllDispatch();
const json = rendered.toJSON();
const messageContainer = json.children[json.children.length - 1];
expect(messageContainer).toBeInstanceOf(Object);
expect(messageContainer.children).not.toBeNull();
expect(messageContainer.children.length).toEqual(1);
expect(messageContainer.children[0]).toEqual('{\"id\":1,\"name\":\"Example\",\"email\":\"[email protected]\",\"action\":\"SAVE\"}');
expect(json).toBeTruthy();
});