mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 06:25:45 +01:00
use pending and error module for auth, me and logout
This commit is contained in:
@@ -3,7 +3,13 @@ import Main from "./Main";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { withRouter } from "react-router-dom";
|
import { withRouter } from "react-router-dom";
|
||||||
import { fetchMe } from "../modules/auth";
|
import {
|
||||||
|
fetchMe,
|
||||||
|
isAuthenticated,
|
||||||
|
getMe,
|
||||||
|
isFetchMePending,
|
||||||
|
getFetchMeFailure
|
||||||
|
} from "../modules/auth";
|
||||||
|
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import "../components/modals/ConfirmAlert.css";
|
import "../components/modals/ConfirmAlert.css";
|
||||||
@@ -14,11 +20,15 @@ import { Footer, Header } from "../components/layout";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
me: Me,
|
me: Me,
|
||||||
|
authenticated: boolean,
|
||||||
error: Error,
|
error: Error,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
authenticated?: boolean,
|
|
||||||
t: string => string,
|
// dispatcher functions
|
||||||
fetchMe: () => void
|
fetchMe: () => void,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
t: string => string
|
||||||
};
|
};
|
||||||
|
|
||||||
class App extends Component<Props> {
|
class App extends Component<Props> {
|
||||||
@@ -62,15 +72,15 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
let mapped = state.auth.me || {};
|
const authenticated = isAuthenticated(state);
|
||||||
|
const me = getMe(state);
|
||||||
if (state.auth.login) {
|
const loading = isFetchMePending(state);
|
||||||
mapped.authenticated = state.auth.login.authenticated;
|
const error = getFetchMeFailure(state);
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...mapped,
|
authenticated,
|
||||||
me: mapped.entry
|
me,
|
||||||
|
loading,
|
||||||
|
error
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import React from "react";
|
|||||||
import { Redirect, withRouter } from "react-router-dom";
|
import { Redirect, withRouter } from "react-router-dom";
|
||||||
import injectSheet from "react-jss";
|
import injectSheet from "react-jss";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { login } from "../modules/auth";
|
import {
|
||||||
|
login,
|
||||||
|
isAuthenticated,
|
||||||
|
isLoginPending,
|
||||||
|
getLoginFailure
|
||||||
|
} from "../modules/auth";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { InputField } from "../components/forms";
|
import { InputField } from "../components/forms";
|
||||||
@@ -32,17 +37,18 @@ const styles = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
authenticated?: boolean,
|
authenticated: boolean,
|
||||||
loading?: boolean,
|
loading: boolean,
|
||||||
error?: Error,
|
error: Error,
|
||||||
|
|
||||||
|
// dispatcher props
|
||||||
|
login: (username: string, password: string) => void,
|
||||||
|
|
||||||
|
// context props
|
||||||
t: string => string,
|
t: string => string,
|
||||||
classes: any,
|
classes: any,
|
||||||
|
|
||||||
from: any,
|
from: any,
|
||||||
location: any,
|
location: any
|
||||||
|
|
||||||
login: (username: string, password: string) => void
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -135,7 +141,14 @@ class Login extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
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 => {
|
const mapDispatchToProps = dispatch => {
|
||||||
|
|||||||
@@ -4,16 +4,25 @@ import { connect } from "react-redux";
|
|||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { Redirect } from "react-router-dom";
|
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 ErrorPage from "../components/ErrorPage";
|
||||||
import Loading from "../components/Loading";
|
import Loading from "../components/Loading";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
|
||||||
loading: boolean,
|
|
||||||
authenticated: boolean,
|
authenticated: boolean,
|
||||||
error?: Error,
|
loading: boolean,
|
||||||
logout: () => void
|
error: Error,
|
||||||
|
|
||||||
|
// dispatcher functions
|
||||||
|
logout: () => void,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
t: string => string
|
||||||
};
|
};
|
||||||
|
|
||||||
class Logout extends React.Component<Props> {
|
class Logout extends React.Component<Props> {
|
||||||
@@ -22,8 +31,7 @@ class Logout extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { authenticated, loading, t, error } = this.props;
|
const { authenticated, loading, error, t } = this.props;
|
||||||
// TODO logout is called twice
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<ErrorPage
|
<ErrorPage
|
||||||
@@ -41,9 +49,14 @@ class Logout extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
let mapped = state.auth.logout || {};
|
const authenticated = isAuthenticated(state);
|
||||||
mapped.authenticated = isAuthenticated(state);
|
const loading = isLogoutPending(state);
|
||||||
return mapped;
|
const error = getLogoutFailure(state);
|
||||||
|
return {
|
||||||
|
authenticated,
|
||||||
|
loading,
|
||||||
|
error
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
|
|||||||
@@ -1,102 +1,50 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import type { Me } from "../types/Me";
|
import type { Me } from "../types/Me";
|
||||||
|
import * as types from "./types";
|
||||||
|
|
||||||
import { apiClient, UNAUTHORIZED_ERROR } from "../apiclient";
|
import { apiClient, UNAUTHORIZED_ERROR } from "../apiclient";
|
||||||
|
import { isPending } from "./pending";
|
||||||
|
import { getFailure } from "./failure";
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
|
|
||||||
export const LOGIN_REQUEST = "scm/auth/LOGIN_REQUEST";
|
export const LOGIN = "scm/auth/LOGIN";
|
||||||
export const LOGIN_SUCCESS = "scm/auth/LOGIN_SUCCESS";
|
export const LOGIN_PENDING = `${LOGIN}_${types.PENDING_SUFFIX}`;
|
||||||
export const LOGIN_FAILURE = "scm/auth/LOGIN_FAILURE";
|
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 = "scm/auth/FETCH_ME";
|
||||||
export const FETCH_ME_SUCCESS = "scm/auth/FETCH_ME_SUCCESS";
|
export const FETCH_ME_PENDING = `${FETCH_ME}_${types.PENDING_SUFFIX}`;
|
||||||
export const FETCH_ME_FAILURE = "scm/auth/FETCH_ME_FAILURE";
|
export const FETCH_ME_SUCCESS = `${FETCH_ME}_${types.SUCCESS_SUFFIX}`;
|
||||||
export const FETCH_ME_UNAUTHORIZED = "scm/auth/FETCH_ME_UNAUTHORIZED";
|
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 = "scm/auth/LOGOUT";
|
||||||
export const LOGOUT_SUCCESS = "scm/auth/LOGOUT_SUCCESS";
|
export const LOGOUT_PENDING = `${LOGOUT}_${types.PENDING_SUFFIX}`;
|
||||||
export const LOGOUT_FAILURE = "scm/auth/LOGOUT_FAILURE";
|
export const LOGOUT_SUCCESS = `${LOGOUT}_${types.SUCCESS_SUFFIX}`;
|
||||||
|
export const LOGOUT_FAILURE = `${LOGOUT}_${types.FAILURE_SUFFIX}`;
|
||||||
|
|
||||||
// Reducer
|
// Reducer
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {};
|
||||||
me: { loading: true }
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function reducer(state: any = initialState, action: any = {}) {
|
export default function reducer(state: Object = initialState, action: Object) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case LOGIN_REQUEST:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
login: {
|
|
||||||
loading: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
case LOGIN_SUCCESS:
|
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:
|
case FETCH_ME_SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
me: {
|
me: action.payload,
|
||||||
entry: action.payload
|
|
||||||
},
|
|
||||||
login: {
|
|
||||||
authenticated: true
|
authenticated: true
|
||||||
}
|
|
||||||
};
|
};
|
||||||
case FETCH_ME_UNAUTHORIZED:
|
case FETCH_ME_UNAUTHORIZED:
|
||||||
return {
|
return {
|
||||||
...state,
|
|
||||||
me: {},
|
me: {},
|
||||||
login: {
|
|
||||||
authenticated: false
|
authenticated: false
|
||||||
}
|
|
||||||
};
|
|
||||||
case FETCH_ME_FAILURE:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
me: {
|
|
||||||
error: action.payload
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
case LOGOUT_REQUEST:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
logout: {
|
|
||||||
loading: true
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
case LOGOUT_SUCCESS:
|
case LOGOUT_SUCCESS:
|
||||||
return initialState;
|
return initialState;
|
||||||
case LOGOUT_FAILURE:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
logout: {
|
|
||||||
error: action.payload
|
|
||||||
}
|
|
||||||
};
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -104,15 +52,16 @@ export default function reducer(state: any = initialState, action: any = {}) {
|
|||||||
|
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
export const loginRequest = () => {
|
export const loginPending = () => {
|
||||||
return {
|
return {
|
||||||
type: LOGIN_REQUEST
|
type: LOGIN_PENDING
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loginSuccess = () => {
|
export const loginSuccess = (me: Me) => {
|
||||||
return {
|
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 {
|
return {
|
||||||
type: LOGOUT_REQUEST
|
type: LOGOUT_PENDING
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -142,9 +91,9 @@ export const logoutFailure = (error: Error) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchMeRequest = () => {
|
export const fetchMePending = () => {
|
||||||
return {
|
return {
|
||||||
type: FETCH_ME_REQUEST
|
type: FETCH_ME_PENDING
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,7 +106,8 @@ export const fetchMeSuccess = (me: Me) => {
|
|||||||
|
|
||||||
export const fetchMeUnauthenticated = () => {
|
export const fetchMeUnauthenticated = () => {
|
||||||
return {
|
return {
|
||||||
type: FETCH_ME_UNAUTHORIZED
|
type: FETCH_ME_UNAUTHORIZED,
|
||||||
|
resetPending: true
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -175,6 +125,17 @@ const LOGIN_URL = "/auth/access_token";
|
|||||||
|
|
||||||
// side effects
|
// side effects
|
||||||
|
|
||||||
|
const callFetchMe = (): Promise<Me> => {
|
||||||
|
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) => {
|
export const login = (username: string, password: string) => {
|
||||||
const login_data = {
|
const login_data = {
|
||||||
cookie: true,
|
cookie: true,
|
||||||
@@ -183,12 +144,14 @@ export const login = (username: string, password: string) => {
|
|||||||
password
|
password
|
||||||
};
|
};
|
||||||
return function(dispatch: any) {
|
return function(dispatch: any) {
|
||||||
dispatch(loginRequest());
|
dispatch(loginPending());
|
||||||
return apiClient
|
return apiClient
|
||||||
.post(LOGIN_URL, login_data)
|
.post(LOGIN_URL, login_data)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
dispatch(fetchMe());
|
return callFetchMe();
|
||||||
dispatch(loginSuccess());
|
})
|
||||||
|
.then(me => {
|
||||||
|
dispatch(loginSuccess(me));
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
dispatch(loginFailure(err));
|
dispatch(loginFailure(err));
|
||||||
@@ -198,16 +161,10 @@ export const login = (username: string, password: string) => {
|
|||||||
|
|
||||||
export const fetchMe = () => {
|
export const fetchMe = () => {
|
||||||
return function(dispatch: any) {
|
return function(dispatch: any) {
|
||||||
dispatch(fetchMeRequest());
|
dispatch(fetchMePending());
|
||||||
return apiClient
|
return callFetchMe()
|
||||||
.get(ME_URL)
|
|
||||||
.then(response => {
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(me => {
|
.then(me => {
|
||||||
dispatch(
|
dispatch(fetchMeSuccess(me));
|
||||||
fetchMeSuccess({ userName: me.name, displayName: me.displayName })
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((error: Error) => {
|
.catch((error: Error) => {
|
||||||
if (error === UNAUTHORIZED_ERROR) {
|
if (error === UNAUTHORIZED_ERROR) {
|
||||||
@@ -221,12 +178,11 @@ export const fetchMe = () => {
|
|||||||
|
|
||||||
export const logout = () => {
|
export const logout = () => {
|
||||||
return function(dispatch: any) {
|
return function(dispatch: any) {
|
||||||
dispatch(logoutRequest());
|
dispatch(logoutPending());
|
||||||
return apiClient
|
return apiClient
|
||||||
.delete(LOGIN_URL)
|
.delete(LOGIN_URL)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(logoutSuccess());
|
dispatch(logoutSuccess());
|
||||||
dispatch(fetchMe());
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
dispatch(logoutFailure(error));
|
dispatch(logoutFailure(error));
|
||||||
@@ -236,6 +192,41 @@ export const logout = () => {
|
|||||||
|
|
||||||
// selectors
|
// selectors
|
||||||
|
|
||||||
export const isAuthenticated = (state: any): boolean => {
|
const stateAuth = (state: Object): Object => {
|
||||||
return state.auth && state.auth.login && state.auth.login.authenticated;
|
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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,109 +3,69 @@ import reducer, {
|
|||||||
logout,
|
logout,
|
||||||
logoutSuccess,
|
logoutSuccess,
|
||||||
loginSuccess,
|
loginSuccess,
|
||||||
fetchMeRequest,
|
|
||||||
loginRequest,
|
|
||||||
logoutRequest,
|
|
||||||
fetchMeFailure,
|
|
||||||
fetchMeUnauthenticated,
|
fetchMeUnauthenticated,
|
||||||
loginFailure,
|
|
||||||
logoutFailure,
|
|
||||||
LOGIN_REQUEST,
|
|
||||||
FETCH_ME_REQUEST,
|
|
||||||
LOGIN_SUCCESS,
|
LOGIN_SUCCESS,
|
||||||
login,
|
login,
|
||||||
LOGIN_FAILURE,
|
LOGIN_FAILURE,
|
||||||
LOGOUT_FAILURE,
|
LOGOUT_FAILURE,
|
||||||
|
LOGOUT_SUCCESS,
|
||||||
FETCH_ME_SUCCESS,
|
FETCH_ME_SUCCESS,
|
||||||
fetchMe,
|
fetchMe,
|
||||||
FETCH_ME_FAILURE,
|
FETCH_ME_FAILURE,
|
||||||
FETCH_ME_UNAUTHORIZED,
|
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";
|
} from "./auth";
|
||||||
|
|
||||||
import configureMockStore from "redux-mock-store";
|
import configureMockStore from "redux-mock-store";
|
||||||
import thunk from "redux-thunk";
|
import thunk from "redux-thunk";
|
||||||
import fetchMock from "fetch-mock";
|
import fetchMock from "fetch-mock";
|
||||||
import { LOGOUT_REQUEST, LOGOUT_SUCCESS } from "./auth";
|
|
||||||
|
const me = { name: "tricia", displayName: "Tricia McMillian" };
|
||||||
|
|
||||||
describe("auth reducer", () => {
|
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", () => {
|
it("should set me and login on successful fetch of me", () => {
|
||||||
const state = reducer(undefined, fetchMeSuccess({ username: "tricia" }));
|
const state = reducer(undefined, fetchMeSuccess(me));
|
||||||
expect(state.me.loading).toBeFalsy();
|
expect(state.me).toBe(me);
|
||||||
expect(state.me.entry.username).toBe("tricia");
|
expect(state.authenticated).toBe(true);
|
||||||
expect(state.login.authenticated).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set authenticated to false", () => {
|
it("should set authenticated to false", () => {
|
||||||
const initialState = {
|
const initialState = {
|
||||||
login: {
|
authenticated: true,
|
||||||
authenticated: true
|
me
|
||||||
},
|
|
||||||
me: {
|
|
||||||
username: "tricia"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const state = reducer(initialState, fetchMeUnauthenticated());
|
const state = reducer(initialState, fetchMeUnauthenticated());
|
||||||
expect(state.me.username).toBeUndefined();
|
expect(state.me.name).toBeUndefined();
|
||||||
expect(state.login.authenticated).toBeFalsy();
|
expect(state.authenticated).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reset the state after logout", () => {
|
it("should reset the state after logout", () => {
|
||||||
const initialState = {
|
const initialState = {
|
||||||
login: {
|
authenticated: true,
|
||||||
authenticated: true
|
me
|
||||||
},
|
|
||||||
me: {
|
|
||||||
username: "tricia"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const state = reducer(initialState, logoutSuccess());
|
const state = reducer(initialState, logoutSuccess());
|
||||||
expect(state.me.loading).toBeTruthy();
|
expect(state.me).toBeUndefined();
|
||||||
expect(state.me.entry).toBeFalsy();
|
expect(state.authenticated).toBeUndefined();
|
||||||
expect(state.login).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set state authenticated after login", () => {
|
it("should set state authenticated and me after login", () => {
|
||||||
const state = reducer(undefined, loginSuccess());
|
const state = reducer(undefined, loginSuccess(me));
|
||||||
expect(state.login.authenticated).toBeTruthy();
|
expect(state.me).toBe(me);
|
||||||
});
|
expect(state.authenticated).toBe(true);
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -129,16 +89,13 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fetchMock.getOnce("/scm/api/rest/v2/me", {
|
fetchMock.getOnce("/scm/api/rest/v2/me", {
|
||||||
body: {
|
body: me,
|
||||||
username: "tricia"
|
|
||||||
},
|
|
||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{ type: LOGIN_REQUEST },
|
{ type: LOGIN_PENDING },
|
||||||
{ type: FETCH_ME_REQUEST },
|
{ type: LOGIN_SUCCESS, payload: me }
|
||||||
{ type: LOGIN_SUCCESS }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
@@ -156,7 +113,7 @@ describe("auth actions", () => {
|
|||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store.dispatch(login("tricia", "secret123")).then(() => {
|
return store.dispatch(login("tricia", "secret123")).then(() => {
|
||||||
const actions = store.getActions();
|
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].type).toEqual(LOGIN_FAILURE);
|
||||||
expect(actions[1].payload).toBeDefined();
|
expect(actions[1].payload).toBeDefined();
|
||||||
});
|
});
|
||||||
@@ -164,15 +121,15 @@ describe("auth actions", () => {
|
|||||||
|
|
||||||
it("should dispatch fetch me success", () => {
|
it("should dispatch fetch me success", () => {
|
||||||
fetchMock.getOnce("/scm/api/rest/v2/me", {
|
fetchMock.getOnce("/scm/api/rest/v2/me", {
|
||||||
body: { name: "sorbot", displayName: "Sorbot" },
|
body: me,
|
||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{ type: FETCH_ME_REQUEST },
|
{ type: FETCH_ME_PENDING },
|
||||||
{
|
{
|
||||||
type: FETCH_ME_SUCCESS,
|
type: FETCH_ME_SUCCESS,
|
||||||
payload: { userName: "sorbot", displayName: "Sorbot" }
|
payload: me
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -191,7 +148,7 @@ describe("auth actions", () => {
|
|||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store.dispatch(fetchMe()).then(() => {
|
return store.dispatch(fetchMe()).then(() => {
|
||||||
const actions = store.getActions();
|
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].type).toEqual(FETCH_ME_FAILURE);
|
||||||
expect(actions[1].payload).toBeDefined();
|
expect(actions[1].payload).toBeDefined();
|
||||||
});
|
});
|
||||||
@@ -203,8 +160,8 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{ type: FETCH_ME_REQUEST },
|
{ type: FETCH_ME_PENDING },
|
||||||
{ type: FETCH_ME_UNAUTHORIZED }
|
{ type: FETCH_ME_UNAUTHORIZED, resetPending: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
@@ -225,9 +182,8 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{ type: LOGOUT_REQUEST },
|
{ type: LOGOUT_PENDING },
|
||||||
{ type: LOGOUT_SUCCESS },
|
{ type: LOGOUT_SUCCESS }
|
||||||
{ type: FETCH_ME_REQUEST }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
@@ -245,7 +201,7 @@ describe("auth actions", () => {
|
|||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store.dispatch(logout()).then(() => {
|
return store.dispatch(logout()).then(() => {
|
||||||
const actions = store.getActions();
|
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].type).toEqual(LOGOUT_FAILURE);
|
||||||
expect(actions[1].payload).toBeDefined();
|
expect(actions[1].payload).toBeDefined();
|
||||||
});
|
});
|
||||||
@@ -253,18 +209,71 @@ describe("auth actions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("auth selectors", () => {
|
describe("auth selectors", () => {
|
||||||
it("should be false", () => {
|
const error = new Error("yo it failed");
|
||||||
expect(isAuthenticated({})).toBeFalsy();
|
|
||||||
expect(isAuthenticated({ auth: {} })).toBeFalsy();
|
it("should be false, if authenticated is undefined or false", () => {
|
||||||
expect(isAuthenticated({ auth: { login: {} } })).toBeFalsy();
|
expect(isAuthenticated({})).toBe(false);
|
||||||
expect(
|
expect(isAuthenticated({ auth: {} })).toBe(false);
|
||||||
isAuthenticated({ auth: { login: { authenticated: false } } })
|
expect(isAuthenticated({ auth: { authenticated: false } })).toBe(false);
|
||||||
).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shuld be true", () => {
|
it("should be true, if authenticated is true", () => {
|
||||||
expect(
|
expect(isAuthenticated({ auth: { authenticated: true } })).toBe(true);
|
||||||
isAuthenticated({ auth: { login: { authenticated: true } } })
|
});
|
||||||
).toBeTruthy();
|
|
||||||
|
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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import type { Action } from "../types/Action";
|
import type { Action } from "../types/Action";
|
||||||
|
import * as types from "./types";
|
||||||
|
|
||||||
const PENDING_SUFFIX = "_PENDING";
|
const PENDING_SUFFIX = "_" + types.PENDING_SUFFIX;
|
||||||
const RESET_PATTERN = /^(.*)_(SUCCESS|FAILURE|RESET)$/;
|
const RESET_ACTIONTYPES = [
|
||||||
|
types.SUCCESS_SUFFIX,
|
||||||
|
types.FAILURE_SUFFIX,
|
||||||
|
types.RESET_SUFFIX
|
||||||
|
];
|
||||||
|
|
||||||
function removeFromState(state: Object, identifier: string) {
|
function removeFromState(state: Object, identifier: string) {
|
||||||
let newState = {};
|
let newState = {};
|
||||||
@@ -32,15 +37,18 @@ export default function reducer(state: Object = {}, action: Action): Object {
|
|||||||
[identifier]: true
|
[identifier]: true
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const matches = RESET_PATTERN.exec(type);
|
const index = type.lastIndexOf("_");
|
||||||
if (matches) {
|
if (index > 0) {
|
||||||
let identifier = matches[1];
|
const actionType = type.substring(index + 1);
|
||||||
|
if (RESET_ACTIONTYPES.indexOf(actionType) >= 0 || action.resetPending) {
|
||||||
|
let identifier = type.substring(0, index);
|
||||||
if (action.itemId) {
|
if (action.itemId) {
|
||||||
identifier += "/" + action.itemId;
|
identifier += "/" + action.itemId;
|
||||||
}
|
}
|
||||||
return removeFromState(state, identifier);
|
return removeFromState(state, identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ describe("pending reducer", () => {
|
|||||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
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", () => {
|
it("should reset pending state for FETCH_ITEMS after FETCH_ITEMS_SUCCESS, but should not affect others", () => {
|
||||||
const newState = reducer(
|
const newState = reducer(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
export type Action = {
|
export type Action = {
|
||||||
type: string,
|
type: string,
|
||||||
payload?: any,
|
payload?: any,
|
||||||
itemId?: string | number
|
itemId?: string | number,
|
||||||
|
resetPending?: boolean
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
export type Me = {
|
export type Me = {
|
||||||
userName: string,
|
name: string,
|
||||||
displayName: string
|
displayName: string
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user