From b825de3058f5fa749b50594faa6f04560e20c1b4 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 30 Jul 2018 15:29:23 +0200 Subject: [PATCH] use pending and error module for auth, me and logout --- scm-ui/src/containers/App.js | 34 +++-- scm-ui/src/containers/Login.js | 31 +++-- scm-ui/src/containers/Logout.js | 33 +++-- scm-ui/src/modules/auth.js | 189 +++++++++++++------------- scm-ui/src/modules/auth.test.js | 209 +++++++++++++++-------------- scm-ui/src/modules/pending.js | 24 ++-- scm-ui/src/modules/pending.test.js | 10 ++ scm-ui/src/types/Action.js | 3 +- scm-ui/src/types/Me.js | 2 +- 9 files changed, 295 insertions(+), 240 deletions(-) diff --git a/scm-ui/src/containers/App.js b/scm-ui/src/containers/App.js index d44771fd7f..47b5d7e811 100644 --- a/scm-ui/src/containers/App.js +++ b/scm-ui/src/containers/App.js @@ -3,7 +3,13 @@ import Main from "./Main"; import { connect } from "react-redux"; import { translate } from "react-i18next"; import { withRouter } from "react-router-dom"; -import { fetchMe } from "../modules/auth"; +import { + fetchMe, + isAuthenticated, + getMe, + isFetchMePending, + getFetchMeFailure +} from "../modules/auth"; import "./App.css"; import "../components/modals/ConfirmAlert.css"; @@ -14,11 +20,15 @@ import { Footer, Header } from "../components/layout"; type Props = { me: Me, + authenticated: boolean, error: Error, loading: boolean, - authenticated?: boolean, - t: string => string, - fetchMe: () => void + + // dispatcher functions + fetchMe: () => void, + + // context props + t: string => string }; class App extends Component { @@ -62,15 +72,15 @@ const mapDispatchToProps = (dispatch: any) => { }; const mapStateToProps = state => { - let mapped = state.auth.me || {}; - - if (state.auth.login) { - mapped.authenticated = state.auth.login.authenticated; - } - + const authenticated = isAuthenticated(state); + const me = getMe(state); + const loading = isFetchMePending(state); + const error = getFetchMeFailure(state); return { - ...mapped, - me: mapped.entry + authenticated, + me, + loading, + error }; }; diff --git a/scm-ui/src/containers/Login.js b/scm-ui/src/containers/Login.js index 7f0b129e7e..d58a892af6 100644 --- a/scm-ui/src/containers/Login.js +++ b/scm-ui/src/containers/Login.js @@ -3,7 +3,12 @@ import React from "react"; import { Redirect, withRouter } from "react-router-dom"; import injectSheet from "react-jss"; import { translate } from "react-i18next"; -import { login } from "../modules/auth"; +import { + login, + isAuthenticated, + isLoginPending, + getLoginFailure +} from "../modules/auth"; import { connect } from "react-redux"; import { InputField } from "../components/forms"; @@ -32,17 +37,18 @@ const styles = { }; type Props = { - authenticated?: boolean, - loading?: boolean, - error?: Error, + authenticated: boolean, + loading: boolean, + error: Error, + // dispatcher props + login: (username: string, password: string) => void, + + // context props t: string => string, classes: any, - from: any, - location: any, - - login: (username: string, password: string) => void + location: any }; type State = { @@ -135,7 +141,14 @@ class Login extends React.Component { } const mapStateToProps = state => { - return state.auth.login || {}; + const authenticated = isAuthenticated(state); + const loading = isLoginPending(state); + const error = getLoginFailure(state); + return { + authenticated, + loading, + error + }; }; const mapDispatchToProps = dispatch => { diff --git a/scm-ui/src/containers/Logout.js b/scm-ui/src/containers/Logout.js index e4eb4e6bfe..787d6d4301 100644 --- a/scm-ui/src/containers/Logout.js +++ b/scm-ui/src/containers/Logout.js @@ -4,16 +4,25 @@ import { connect } from "react-redux"; import { translate } from "react-i18next"; import { Redirect } from "react-router-dom"; -import { logout, isAuthenticated } from "../modules/auth"; +import { + logout, + isAuthenticated, + isLogoutPending, + getLogoutFailure +} from "../modules/auth"; import ErrorPage from "../components/ErrorPage"; import Loading from "../components/Loading"; type Props = { - t: string => string, - loading: boolean, authenticated: boolean, - error?: Error, - logout: () => void + loading: boolean, + error: Error, + + // dispatcher functions + logout: () => void, + + // context props + t: string => string }; class Logout extends React.Component { @@ -22,8 +31,7 @@ class Logout extends React.Component { } render() { - const { authenticated, loading, t, error } = this.props; - // TODO logout is called twice + const { authenticated, loading, error, t } = this.props; if (error) { return ( { } const mapStateToProps = state => { - let mapped = state.auth.logout || {}; - mapped.authenticated = isAuthenticated(state); - return mapped; + const authenticated = isAuthenticated(state); + const loading = isLogoutPending(state); + const error = getLogoutFailure(state); + return { + authenticated, + loading, + error + }; }; const mapDispatchToProps = dispatch => { diff --git a/scm-ui/src/modules/auth.js b/scm-ui/src/modules/auth.js index 8868fa4a36..d0f8e0a246 100644 --- a/scm-ui/src/modules/auth.js +++ b/scm-ui/src/modules/auth.js @@ -1,102 +1,50 @@ // @flow import type { Me } from "../types/Me"; +import * as types from "./types"; import { apiClient, UNAUTHORIZED_ERROR } from "../apiclient"; +import { isPending } from "./pending"; +import { getFailure } from "./failure"; // Action -export const LOGIN_REQUEST = "scm/auth/LOGIN_REQUEST"; -export const LOGIN_SUCCESS = "scm/auth/LOGIN_SUCCESS"; -export const LOGIN_FAILURE = "scm/auth/LOGIN_FAILURE"; +export const LOGIN = "scm/auth/LOGIN"; +export const LOGIN_PENDING = `${LOGIN}_${types.PENDING_SUFFIX}`; +export const LOGIN_SUCCESS = `${LOGIN}_${types.SUCCESS_SUFFIX}`; +export const LOGIN_FAILURE = `${LOGIN}_${types.FAILURE_SUFFIX}`; -export const FETCH_ME_REQUEST = "scm/auth/FETCH_ME_REQUEST"; -export const FETCH_ME_SUCCESS = "scm/auth/FETCH_ME_SUCCESS"; -export const FETCH_ME_FAILURE = "scm/auth/FETCH_ME_FAILURE"; -export const FETCH_ME_UNAUTHORIZED = "scm/auth/FETCH_ME_UNAUTHORIZED"; +export const FETCH_ME = "scm/auth/FETCH_ME"; +export const FETCH_ME_PENDING = `${FETCH_ME}_${types.PENDING_SUFFIX}`; +export const FETCH_ME_SUCCESS = `${FETCH_ME}_${types.SUCCESS_SUFFIX}`; +export const FETCH_ME_FAILURE = `${FETCH_ME}_${types.FAILURE_SUFFIX}`; +export const FETCH_ME_UNAUTHORIZED = `${FETCH_ME}_UNAUTHORIZED`; -export const LOGOUT_REQUEST = "scm/auth/LOGOUT_REQUEST"; -export const LOGOUT_SUCCESS = "scm/auth/LOGOUT_SUCCESS"; -export const LOGOUT_FAILURE = "scm/auth/LOGOUT_FAILURE"; +export const LOGOUT = "scm/auth/LOGOUT"; +export const LOGOUT_PENDING = `${LOGOUT}_${types.PENDING_SUFFIX}`; +export const LOGOUT_SUCCESS = `${LOGOUT}_${types.SUCCESS_SUFFIX}`; +export const LOGOUT_FAILURE = `${LOGOUT}_${types.FAILURE_SUFFIX}`; // Reducer -const initialState = { - me: { loading: true } -}; +const initialState = {}; -export default function reducer(state: any = initialState, action: any = {}) { +export default function reducer(state: Object = initialState, action: Object) { switch (action.type) { - case LOGIN_REQUEST: - return { - ...state, - login: { - loading: true - } - }; case LOGIN_SUCCESS: - return { - ...state, - login: { - authenticated: true - } - }; - case LOGIN_FAILURE: - return { - ...state, - login: { - error: action.payload - } - }; - - case FETCH_ME_REQUEST: - return { - ...state, - me: { - loading: true - } - }; case FETCH_ME_SUCCESS: return { ...state, - me: { - entry: action.payload - }, - login: { - authenticated: true - } + me: action.payload, + authenticated: true }; case FETCH_ME_UNAUTHORIZED: return { - ...state, me: {}, - login: { - authenticated: false - } - }; - case FETCH_ME_FAILURE: - return { - ...state, - me: { - error: action.payload - } - }; - - case LOGOUT_REQUEST: - return { - ...state, - logout: { - loading: true - } + authenticated: false }; case LOGOUT_SUCCESS: return initialState; - case LOGOUT_FAILURE: - return { - ...state, - logout: { - error: action.payload - } - }; + default: return state; } @@ -104,15 +52,16 @@ export default function reducer(state: any = initialState, action: any = {}) { // Action Creators -export const loginRequest = () => { +export const loginPending = () => { return { - type: LOGIN_REQUEST + type: LOGIN_PENDING }; }; -export const loginSuccess = () => { +export const loginSuccess = (me: Me) => { return { - type: LOGIN_SUCCESS + type: LOGIN_SUCCESS, + payload: me }; }; @@ -123,9 +72,9 @@ export const loginFailure = (error: Error) => { }; }; -export const logoutRequest = () => { +export const logoutPending = () => { return { - type: LOGOUT_REQUEST + type: LOGOUT_PENDING }; }; @@ -142,9 +91,9 @@ export const logoutFailure = (error: Error) => { }; }; -export const fetchMeRequest = () => { +export const fetchMePending = () => { return { - type: FETCH_ME_REQUEST + type: FETCH_ME_PENDING }; }; @@ -157,7 +106,8 @@ export const fetchMeSuccess = (me: Me) => { export const fetchMeUnauthenticated = () => { return { - type: FETCH_ME_UNAUTHORIZED + type: FETCH_ME_UNAUTHORIZED, + resetPending: true }; }; @@ -175,6 +125,17 @@ const LOGIN_URL = "/auth/access_token"; // side effects +const callFetchMe = (): Promise => { + return apiClient + .get(ME_URL) + .then(response => { + return response.json(); + }) + .then(json => { + return { name: json.name, displayName: json.displayName }; + }); +}; + export const login = (username: string, password: string) => { const login_data = { cookie: true, @@ -183,12 +144,14 @@ export const login = (username: string, password: string) => { password }; return function(dispatch: any) { - dispatch(loginRequest()); + dispatch(loginPending()); return apiClient .post(LOGIN_URL, login_data) .then(response => { - dispatch(fetchMe()); - dispatch(loginSuccess()); + return callFetchMe(); + }) + .then(me => { + dispatch(loginSuccess(me)); }) .catch(err => { dispatch(loginFailure(err)); @@ -198,16 +161,10 @@ export const login = (username: string, password: string) => { export const fetchMe = () => { return function(dispatch: any) { - dispatch(fetchMeRequest()); - return apiClient - .get(ME_URL) - .then(response => { - return response.json(); - }) + dispatch(fetchMePending()); + return callFetchMe() .then(me => { - dispatch( - fetchMeSuccess({ userName: me.name, displayName: me.displayName }) - ); + dispatch(fetchMeSuccess(me)); }) .catch((error: Error) => { if (error === UNAUTHORIZED_ERROR) { @@ -221,12 +178,11 @@ export const fetchMe = () => { export const logout = () => { return function(dispatch: any) { - dispatch(logoutRequest()); + dispatch(logoutPending()); return apiClient .delete(LOGIN_URL) .then(() => { dispatch(logoutSuccess()); - dispatch(fetchMe()); }) .catch(error => { dispatch(logoutFailure(error)); @@ -236,6 +192,41 @@ export const logout = () => { // selectors -export const isAuthenticated = (state: any): boolean => { - return state.auth && state.auth.login && state.auth.login.authenticated; +const stateAuth = (state: Object): Object => { + return state.auth || {}; +}; + +export const isAuthenticated = (state: Object) => { + if (stateAuth(state).authenticated) { + return true; + } + return false; +}; + +export const getMe = (state: Object): Me => { + return stateAuth(state).me; +}; + +export const isFetchMePending = (state: Object) => { + return isPending(state, FETCH_ME); +}; + +export const getFetchMeFailure = (state: Object) => { + return getFailure(state, FETCH_ME); +}; + +export const isLoginPending = (state: Object) => { + return isPending(state, LOGIN); +}; + +export const getLoginFailure = (state: Object) => { + return getFailure(state, LOGIN); +}; + +export const isLogoutPending = (state: Object) => { + return isPending(state, LOGOUT); +}; + +export const getLogoutFailure = (state: Object) => { + return getFailure(state, LOGOUT); }; diff --git a/scm-ui/src/modules/auth.test.js b/scm-ui/src/modules/auth.test.js index 831bed8d4b..f994e8f657 100644 --- a/scm-ui/src/modules/auth.test.js +++ b/scm-ui/src/modules/auth.test.js @@ -3,109 +3,69 @@ import reducer, { logout, logoutSuccess, loginSuccess, - fetchMeRequest, - loginRequest, - logoutRequest, - fetchMeFailure, fetchMeUnauthenticated, - loginFailure, - logoutFailure, - LOGIN_REQUEST, - FETCH_ME_REQUEST, LOGIN_SUCCESS, login, LOGIN_FAILURE, LOGOUT_FAILURE, + LOGOUT_SUCCESS, FETCH_ME_SUCCESS, fetchMe, FETCH_ME_FAILURE, FETCH_ME_UNAUTHORIZED, - isAuthenticated + isAuthenticated, + LOGIN_PENDING, + FETCH_ME_PENDING, + LOGOUT_PENDING, + getMe, + isFetchMePending, + isLoginPending, + isLogoutPending, + getFetchMeFailure, + LOGIN, + FETCH_ME, + LOGOUT, + getLoginFailure, + getLogoutFailure } from "./auth"; import configureMockStore from "redux-mock-store"; import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; -import { LOGOUT_REQUEST, LOGOUT_SUCCESS } from "./auth"; + +const me = { name: "tricia", displayName: "Tricia McMillian" }; describe("auth reducer", () => { - it("should initialize in loading state ", () => { - const state = reducer(); - expect(state.me.loading).toBeTruthy(); - }); - it("should set me and login on successful fetch of me", () => { - const state = reducer(undefined, fetchMeSuccess({ username: "tricia" })); - expect(state.me.loading).toBeFalsy(); - expect(state.me.entry.username).toBe("tricia"); - expect(state.login.authenticated).toBeTruthy(); + const state = reducer(undefined, fetchMeSuccess(me)); + expect(state.me).toBe(me); + expect(state.authenticated).toBe(true); }); it("should set authenticated to false", () => { const initialState = { - login: { - authenticated: true - }, - me: { - username: "tricia" - } + authenticated: true, + me }; const state = reducer(initialState, fetchMeUnauthenticated()); - expect(state.me.username).toBeUndefined(); - expect(state.login.authenticated).toBeFalsy(); + expect(state.me.name).toBeUndefined(); + expect(state.authenticated).toBe(false); }); it("should reset the state after logout", () => { const initialState = { - login: { - authenticated: true - }, - me: { - username: "tricia" - } + authenticated: true, + me }; const state = reducer(initialState, logoutSuccess()); - expect(state.me.loading).toBeTruthy(); - expect(state.me.entry).toBeFalsy(); - expect(state.login).toBeUndefined(); + expect(state.me).toBeUndefined(); + expect(state.authenticated).toBeUndefined(); }); - it("should set state authenticated after login", () => { - const state = reducer(undefined, loginSuccess()); - expect(state.login.authenticated).toBeTruthy(); - }); - - it("should set me to loading", () => { - const state = reducer({ me: { loading: false } }, fetchMeRequest()); - expect(state.me.loading).toBeTruthy(); - }); - - it("should set login to loading", () => { - const state = reducer({ login: { loading: false } }, loginRequest()); - expect(state.login.loading).toBeTruthy(); - }); - - it("should set logout to loading", () => { - const state = reducer({ logout: { loading: false } }, logoutRequest()); - expect(state.logout.loading).toBeTruthy(); - }); - - it("should set me to error", () => { - const error = new Error("failed"); - const state = reducer(undefined, fetchMeFailure(error)); - expect(state.me.error).toBe(error); - }); - - it("should set login to error", () => { - const error = new Error("failed"); - const state = reducer(undefined, loginFailure(error)); - expect(state.login.error).toBe(error); - }); - - it("should set logout to error", () => { - const error = new Error("failed"); - const state = reducer(undefined, logoutFailure(error)); - expect(state.logout.error).toBe(error); + it("should set state authenticated and me after login", () => { + const state = reducer(undefined, loginSuccess(me)); + expect(state.me).toBe(me); + expect(state.authenticated).toBe(true); }); }); @@ -129,16 +89,13 @@ describe("auth actions", () => { }); fetchMock.getOnce("/scm/api/rest/v2/me", { - body: { - username: "tricia" - }, + body: me, headers: { "content-type": "application/json" } }); const expectedActions = [ - { type: LOGIN_REQUEST }, - { type: FETCH_ME_REQUEST }, - { type: LOGIN_SUCCESS } + { type: LOGIN_PENDING }, + { type: LOGIN_SUCCESS, payload: me } ]; const store = mockStore({}); @@ -156,7 +113,7 @@ describe("auth actions", () => { const store = mockStore({}); return store.dispatch(login("tricia", "secret123")).then(() => { const actions = store.getActions(); - expect(actions[0].type).toEqual(LOGIN_REQUEST); + expect(actions[0].type).toEqual(LOGIN_PENDING); expect(actions[1].type).toEqual(LOGIN_FAILURE); expect(actions[1].payload).toBeDefined(); }); @@ -164,15 +121,15 @@ describe("auth actions", () => { it("should dispatch fetch me success", () => { fetchMock.getOnce("/scm/api/rest/v2/me", { - body: { name: "sorbot", displayName: "Sorbot" }, + body: me, headers: { "content-type": "application/json" } }); const expectedActions = [ - { type: FETCH_ME_REQUEST }, + { type: FETCH_ME_PENDING }, { type: FETCH_ME_SUCCESS, - payload: { userName: "sorbot", displayName: "Sorbot" } + payload: me } ]; @@ -191,7 +148,7 @@ describe("auth actions", () => { const store = mockStore({}); return store.dispatch(fetchMe()).then(() => { const actions = store.getActions(); - expect(actions[0].type).toEqual(FETCH_ME_REQUEST); + expect(actions[0].type).toEqual(FETCH_ME_PENDING); expect(actions[1].type).toEqual(FETCH_ME_FAILURE); expect(actions[1].payload).toBeDefined(); }); @@ -203,8 +160,8 @@ describe("auth actions", () => { }); const expectedActions = [ - { type: FETCH_ME_REQUEST }, - { type: FETCH_ME_UNAUTHORIZED } + { type: FETCH_ME_PENDING }, + { type: FETCH_ME_UNAUTHORIZED, resetPending: true } ]; const store = mockStore({}); @@ -225,9 +182,8 @@ describe("auth actions", () => { }); const expectedActions = [ - { type: LOGOUT_REQUEST }, - { type: LOGOUT_SUCCESS }, - { type: FETCH_ME_REQUEST } + { type: LOGOUT_PENDING }, + { type: LOGOUT_SUCCESS } ]; const store = mockStore({}); @@ -245,7 +201,7 @@ describe("auth actions", () => { const store = mockStore({}); return store.dispatch(logout()).then(() => { const actions = store.getActions(); - expect(actions[0].type).toEqual(LOGOUT_REQUEST); + expect(actions[0].type).toEqual(LOGOUT_PENDING); expect(actions[1].type).toEqual(LOGOUT_FAILURE); expect(actions[1].payload).toBeDefined(); }); @@ -253,18 +209,71 @@ describe("auth actions", () => { }); describe("auth selectors", () => { - it("should be false", () => { - expect(isAuthenticated({})).toBeFalsy(); - expect(isAuthenticated({ auth: {} })).toBeFalsy(); - expect(isAuthenticated({ auth: { login: {} } })).toBeFalsy(); - expect( - isAuthenticated({ auth: { login: { authenticated: false } } }) - ).toBeFalsy(); + const error = new Error("yo it failed"); + + it("should be false, if authenticated is undefined or false", () => { + expect(isAuthenticated({})).toBe(false); + expect(isAuthenticated({ auth: {} })).toBe(false); + expect(isAuthenticated({ auth: { authenticated: false } })).toBe(false); }); - it("shuld be true", () => { - expect( - isAuthenticated({ auth: { login: { authenticated: true } } }) - ).toBeTruthy(); + it("should be true, if authenticated is true", () => { + expect(isAuthenticated({ auth: { authenticated: true } })).toBe(true); + }); + + it("should return me", () => { + expect(getMe({ auth: { me } })).toBe(me); + }); + + it("should return undefined, if me is not set", () => { + expect(getMe({})).toBeUndefined(); + }); + + it("should return true, if FETCH_ME is pending", () => { + expect(isFetchMePending({ pending: { [FETCH_ME]: true } })).toBe(true); + }); + + it("should return false, if FETCH_ME is not in pending state", () => { + expect(isFetchMePending({ pending: {} })).toBe(false); + }); + + it("should return true, if LOGIN is pending", () => { + expect(isLoginPending({ pending: { [LOGIN]: true } })).toBe(true); + }); + + it("should return false, if LOGIN is not in pending state", () => { + expect(isLoginPending({ pending: {} })).toBe(false); + }); + + it("should return true, if LOGOUT is pending", () => { + expect(isLogoutPending({ pending: { [LOGOUT]: true } })).toBe(true); + }); + + it("should return false, if LOGOUT is not in pending state", () => { + expect(isLogoutPending({ pending: {} })).toBe(false); + }); + + it("should return the error, if failure state is set for FETCH_ME", () => { + expect(getFetchMeFailure({ failure: { [FETCH_ME]: error } })).toBe(error); + }); + + it("should return unknown, if failure state is not set for FETCH_ME", () => { + expect(getFetchMeFailure({})).toBeUndefined(); + }); + + it("should return the error, if failure state is set for LOGIN", () => { + expect(getLoginFailure({ failure: { [LOGIN]: error } })).toBe(error); + }); + + it("should return unknown, if failure state is not set for LOGIN", () => { + expect(getLoginFailure({})).toBeUndefined(); + }); + + it("should return the error, if failure state is set for LOGOUT", () => { + expect(getLogoutFailure({ failure: { [LOGOUT]: error } })).toBe(error); + }); + + it("should return unknown, if failure state is not set for LOGOUT", () => { + expect(getLogoutFailure({})).toBeUndefined(); }); }); diff --git a/scm-ui/src/modules/pending.js b/scm-ui/src/modules/pending.js index d30093bbfe..04abf65c8c 100644 --- a/scm-ui/src/modules/pending.js +++ b/scm-ui/src/modules/pending.js @@ -1,8 +1,13 @@ // @flow import type { Action } from "../types/Action"; +import * as types from "./types"; -const PENDING_SUFFIX = "_PENDING"; -const RESET_PATTERN = /^(.*)_(SUCCESS|FAILURE|RESET)$/; +const PENDING_SUFFIX = "_" + types.PENDING_SUFFIX; +const RESET_ACTIONTYPES = [ + types.SUCCESS_SUFFIX, + types.FAILURE_SUFFIX, + types.RESET_SUFFIX +]; function removeFromState(state: Object, identifier: string) { let newState = {}; @@ -32,13 +37,16 @@ export default function reducer(state: Object = {}, action: Action): Object { [identifier]: true }; } else { - const matches = RESET_PATTERN.exec(type); - if (matches) { - let identifier = matches[1]; - if (action.itemId) { - identifier += "/" + action.itemId; + const index = type.lastIndexOf("_"); + if (index > 0) { + const actionType = type.substring(index + 1); + if (RESET_ACTIONTYPES.indexOf(actionType) >= 0 || action.resetPending) { + let identifier = type.substring(0, index); + if (action.itemId) { + identifier += "/" + action.itemId; + } + return removeFromState(state, identifier); } - return removeFromState(state, identifier); } } return state; diff --git a/scm-ui/src/modules/pending.test.js b/scm-ui/src/modules/pending.test.js index 7a15f3e957..a50ed37400 100644 --- a/scm-ui/src/modules/pending.test.js +++ b/scm-ui/src/modules/pending.test.js @@ -47,6 +47,16 @@ describe("pending reducer", () => { expect(newState["FETCH_ITEMS"]).toBeFalsy(); }); + it("should reset pending state for FETCH_ITEMS, if resetPending prop is available", () => { + const newState = reducer( + { + FETCH_ITEMS: true + }, + { type: "FETCH_ITEMS_SOMETHING", resetPending: true } + ); + expect(newState["FETCH_ITEMS"]).toBeFalsy(); + }); + it("should reset pending state for FETCH_ITEMS after FETCH_ITEMS_SUCCESS, but should not affect others", () => { const newState = reducer( { diff --git a/scm-ui/src/types/Action.js b/scm-ui/src/types/Action.js index faabaee78e..20023f60b0 100644 --- a/scm-ui/src/types/Action.js +++ b/scm-ui/src/types/Action.js @@ -2,5 +2,6 @@ export type Action = { type: string, payload?: any, - itemId?: string | number + itemId?: string | number, + resetPending?: boolean }; diff --git a/scm-ui/src/types/Me.js b/scm-ui/src/types/Me.js index 18594ebe7a..65e4fc8336 100644 --- a/scm-ui/src/types/Me.js +++ b/scm-ui/src/types/Me.js @@ -1,6 +1,6 @@ // @flow export type Me = { - userName: string, + name: string, displayName: string };