From 02547889018b5562983d7330b8fd31b8d20717c3 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Tue, 12 Mar 2019 14:33:09 +0100 Subject: [PATCH] Add action user-admin/fetchUsers Change-Id: I6a956d118ffd352f8155650fee17219cdcefebc6 --- src/features/user-admin/redux/actions.js | 1 + src/features/user-admin/redux/constants.js | 4 + src/features/user-admin/redux/fetchUsers.js | 89 +++++++++++++++++ src/features/user-admin/redux/initialState.js | 3 + src/features/user-admin/redux/reducer.js | 2 + .../user-admin/redux/fetchUsers.test.js | 97 +++++++++++++++++++ 6 files changed, 196 insertions(+) create mode 100644 src/features/user-admin/redux/fetchUsers.js create mode 100644 tests/features/user-admin/redux/fetchUsers.test.js diff --git a/src/features/user-admin/redux/actions.js b/src/features/user-admin/redux/actions.js index e69de29..f18e48b 100644 --- a/src/features/user-admin/redux/actions.js +++ b/src/features/user-admin/redux/actions.js @@ -0,0 +1 @@ +export { fetchUsers, dismissFetchUsersError } from './fetchUsers'; diff --git a/src/features/user-admin/redux/constants.js b/src/features/user-admin/redux/constants.js index e69de29..3b2ffa8 100644 --- a/src/features/user-admin/redux/constants.js +++ b/src/features/user-admin/redux/constants.js @@ -0,0 +1,4 @@ +export const USER_ADMIN_FETCH_USERS_BEGIN = 'USER_ADMIN_FETCH_USERS_BEGIN'; +export const USER_ADMIN_FETCH_USERS_SUCCESS = 'USER_ADMIN_FETCH_USERS_SUCCESS'; +export const USER_ADMIN_FETCH_USERS_FAILURE = 'USER_ADMIN_FETCH_USERS_FAILURE'; +export const USER_ADMIN_FETCH_USERS_DISMISS_ERROR = 'USER_ADMIN_FETCH_USERS_DISMISS_ERROR'; diff --git a/src/features/user-admin/redux/fetchUsers.js b/src/features/user-admin/redux/fetchUsers.js new file mode 100644 index 0000000..b0a1393 --- /dev/null +++ b/src/features/user-admin/redux/fetchUsers.js @@ -0,0 +1,89 @@ +import { + USER_ADMIN_FETCH_USERS_BEGIN, + USER_ADMIN_FETCH_USERS_SUCCESS, + USER_ADMIN_FETCH_USERS_FAILURE, + USER_ADMIN_FETCH_USERS_DISMISS_ERROR, +} from './constants'; + +export function fetchUsers(args = {}) { + return (dispatch, getState) => { + dispatch({ + type: USER_ADMIN_FETCH_USERS_BEGIN, + }); + + // Return a promise so that you could control UI flow without states in the store. + // For example: after submit a form, you need to redirect the page to another when succeeds or show some errors message if fails. + // It's hard to use state to manage it, but returning a promise allows you to easily achieve it. + // e.g.: handleSubmit() { this.props.actions.submitForm(data).then(()=> {}).catch(() => {}); } + const promise = new Promise((resolve, reject) => { + const mtx = getState().common.mtx; + const doRequest = mtx._http.authedRequest(undefined, "GET", "/admin/users/" + mtx.credentials.userId) + doRequest.then( + (res) => { + dispatch({ + type: USER_ADMIN_FETCH_USERS_SUCCESS, + data: res, + }); + resolve(res); + }, + // Use rejectHandler as the second argument so that render errors won't be caught. + (err) => { + dispatch({ + type: USER_ADMIN_FETCH_USERS_FAILURE, + data: { error: err }, + }); + reject(err); + }, + ); + }); + + return promise; + }; +} + +// Async action saves request error by default, this method is used to dismiss the error info. +// If you don't want errors to be saved in Redux store, just ignore this method. +export function dismissFetchUsersError() { + return { + type: USER_ADMIN_FETCH_USERS_DISMISS_ERROR, + }; +} + +export function reducer(state, action) { + switch (action.type) { + case USER_ADMIN_FETCH_USERS_BEGIN: + // Just after a request is sent + return { + ...state, + fetchUsersPending: true, + fetchUsersError: null, + }; + + case USER_ADMIN_FETCH_USERS_SUCCESS: + // The request is success + return { + ...state, + userList: action.data, + fetchUsersPending: false, + fetchUsersError: null, + }; + + case USER_ADMIN_FETCH_USERS_FAILURE: + // The request is failed + return { + ...state, + fetchUsersPending: false, + fetchUsersError: action.data.error, + }; + + case USER_ADMIN_FETCH_USERS_DISMISS_ERROR: + // Dismiss the request failure error + return { + ...state, + fetchUsersError: null, + }; + + default: + return state; + } +} diff --git a/src/features/user-admin/redux/initialState.js b/src/features/user-admin/redux/initialState.js index 9570e38..6284612 100644 --- a/src/features/user-admin/redux/initialState.js +++ b/src/features/user-admin/redux/initialState.js @@ -6,6 +6,9 @@ // NOTE: initialState constant is necessary so that Rekit could auto add initial state when creating async actions. const initialState = { + userList: [], + fetchUsersPending: false, + fetchUsersError: null, }; export default initialState; diff --git a/src/features/user-admin/redux/reducer.js b/src/features/user-admin/redux/reducer.js index c9b022b..aafa72a 100644 --- a/src/features/user-admin/redux/reducer.js +++ b/src/features/user-admin/redux/reducer.js @@ -7,8 +7,10 @@ // https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da. import initialState from './initialState'; +import { reducer as fetchUsersReducer } from './fetchUsers'; const reducers = [ + fetchUsersReducer, ]; export default function reducer(state = initialState, action) { diff --git a/tests/features/user-admin/redux/fetchUsers.test.js b/tests/features/user-admin/redux/fetchUsers.test.js new file mode 100644 index 0000000..1cd041c --- /dev/null +++ b/tests/features/user-admin/redux/fetchUsers.test.js @@ -0,0 +1,97 @@ +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import nock from 'nock'; + +import { + USER_ADMIN_FETCH_USERS_BEGIN, + USER_ADMIN_FETCH_USERS_SUCCESS, + USER_ADMIN_FETCH_USERS_FAILURE, + USER_ADMIN_FETCH_USERS_DISMISS_ERROR, +} from '../../../../src/features/user-admin/redux/constants'; + +import { + fetchUsers, + dismissFetchUsersError, + reducer, +} from '../../../../src/features/user-admin/redux/fetchUsers'; + +const middlewares = [thunk]; +const mockStore = configureMockStore(middlewares); + +describe('user-admin/redux/fetchUsers', () => { + afterEach(() => { + nock.cleanAll(); + }); + + it('dispatches success action when fetchUsers succeeds', () => { + const store = mockStore({}); + + return store.dispatch(fetchUsers()) + .then(() => { + const actions = store.getActions(); + expect(actions[0]).toHaveProperty('type', USER_ADMIN_FETCH_USERS_BEGIN); + expect(actions[1]).toHaveProperty('type', USER_ADMIN_FETCH_USERS_SUCCESS); + }); + }); + + it('dispatches failure action when fetchUsers fails', () => { + const store = mockStore({}); + + return store.dispatch(fetchUsers({ error: true })) + .catch(() => { + const actions = store.getActions(); + expect(actions[0]).toHaveProperty('type', USER_ADMIN_FETCH_USERS_BEGIN); + expect(actions[1]).toHaveProperty('type', USER_ADMIN_FETCH_USERS_FAILURE); + expect(actions[1]).toHaveProperty('data.error', expect.anything()); + }); + }); + + it('returns correct action by dismissFetchUsersError', () => { + const expectedAction = { + type: USER_ADMIN_FETCH_USERS_DISMISS_ERROR, + }; + expect(dismissFetchUsersError()).toEqual(expectedAction); + }); + + it('handles action type USER_ADMIN_FETCH_USERS_BEGIN correctly', () => { + const prevState = { listUsersPending: false }; + const state = reducer( + prevState, + { type: USER_ADMIN_FETCH_USERS_BEGIN } + ); + expect(state).not.toBe(prevState); // should be immutable + expect(state.listUsersPending).toBe(true); + }); + + it('handles action type USER_ADMIN_FETCH_USERS_SUCCESS correctly', () => { + const prevState = { listUsersPending: true }; + const state = reducer( + prevState, + { type: USER_ADMIN_FETCH_USERS_SUCCESS, data: {} } + ); + expect(state).not.toBe(prevState); // should be immutable + expect(state.listUsersPending).toBe(false); + }); + + it('handles action type USER_ADMIN_FETCH_USERS_FAILURE correctly', () => { + const prevState = { listUsersPending: true }; + const state = reducer( + prevState, + { type: USER_ADMIN_FETCH_USERS_FAILURE, data: { error: new Error('some error') } } + ); + expect(state).not.toBe(prevState); // should be immutable + expect(state.listUsersPending).toBe(false); + expect(state.listUsersError).toEqual(expect.anything()); + }); + + it('handles action type USER_ADMIN_FETCH_USERS_DISMISS_ERROR correctly', () => { + const prevState = { listUsersError: new Error('some error') }; + const state = reducer( + prevState, + { type: USER_ADMIN_FETCH_USERS_DISMISS_ERROR } + ); + expect(state).not.toBe(prevState); // should be immutable + expect(state.listUsersError).toBe(null); + }); +}); +