update users module to use new pending and error state

This commit is contained in:
Sebastian Sdorra
2018-07-30 13:38:15 +02:00
parent 9e029c0c5c
commit 7be1366c1a
14 changed files with 274 additions and 443 deletions

View File

@@ -35,7 +35,10 @@ class Page extends React.Component<Props> {
}
renderContent() {
const { loading, children } = this.props;
const { loading, children, error } = this.props;
if (error) {
return null;
}
if (loading) {
return <Loading />;
}

View File

@@ -34,7 +34,11 @@ export default function reducer(state: Object = {}, action: Action): Object {
} else {
const matches = RESET_PATTERN.exec(type);
if (matches) {
return removeFromState(state, matches[1]);
let identifier = matches[1];
if (action.itemId) {
identifier += "/" + action.itemId;
}
return removeFromState(state, identifier);
}
}
return state;

View File

@@ -69,6 +69,16 @@ describe("pending reducer", () => {
expect(newState["FETCH_USER/21"]).toBe(true);
expect(newState["FETCH_USER/42"]).toBe(false);
});
it("should reset pending for a single item", () => {
const newState = reducer(
{
"FETCH_USER/42": true
},
{ type: "FETCH_USER_SUCCESS", itemId: 42 }
);
expect(newState["FETCH_USER/42"]).toBeFalsy();
});
});
describe("pending selectors", () => {

View File

@@ -0,0 +1,4 @@
export const PENDING_SUFFIX = "PENDING";
export const SUCCESS_SUFFIX = "SUCCESS";
export const FAILURE_SUFFIX = "FAILURE";
export const RESET_SUFFIX = "RESET";

View File

@@ -10,9 +10,3 @@ export type PagedCollection = Collection & {
page: number,
pageTotal: number
};
export type PageCollectionStateSlice = {
entry?: PagedCollection,
error?: Error,
loading?: boolean
};

View File

@@ -1,12 +1,12 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import type { UserEntry } from "../../types/UserEntry";
import type { User } from "../../types/User";
import { NavLink } from "../../../components/navigation";
type Props = {
t: string => string,
user: UserEntry,
user: User,
editUrl: String
};

View File

@@ -2,16 +2,16 @@
import React from "react";
import { translate } from "react-i18next";
import UserRow from "./UserRow";
import type { UserEntry } from "../../types/UserEntry";
import type { User } from "../../types/User";
type Props = {
t: string => string,
entries: Array<UserEntry>
users: User[]
};
class UserTable extends React.Component<Props> {
render() {
const { entries, t } = this.props;
const { users, t } = this.props;
return (
<table className="table is-hoverable is-fullwidth">
<thead>
@@ -23,8 +23,8 @@ class UserTable extends React.Component<Props> {
</tr>
</thead>
<tbody>
{entries.map((entry, index) => {
return <UserRow key={index} user={entry.entry} />;
{users.map((user, index) => {
return <UserRow key={index} user={user} />;
})}
</tbody>
</table>

View File

@@ -4,17 +4,26 @@ import { connect } from "react-redux";
import UserForm from "./../components/UserForm";
import type { User } from "../types/User";
import type { History } from "history";
import { createUser, createUserReset } from "../modules/users";
import {
createUser,
createUserReset,
isCreateUserPending,
getCreateUserFailure
} from "../modules/users";
import { Page } from "../../components/layout";
import { translate } from "react-i18next";
type Props = {
t: string => string,
addUser: (user: User, callback?: () => void) => void,
loading?: boolean,
error?: Error,
history: History,
resetForm: () => void
// dispatcher functions
addUser: (user: User, callback?: () => void) => void,
resetForm: () => void,
// context objects
t: string => string,
history: History
};
class AddUser extends React.Component<Props> {
@@ -61,10 +70,12 @@ const mapDispatchToProps = dispatch => {
};
const mapStateToProps = (state, ownProps) => {
if (state.users && state.users.create) {
return state.users.create;
}
return {};
const loading = isCreateUserPending(state);
const error = getCreateUserFailure(state);
return {
loading,
error
};
};
export default connect(

View File

@@ -4,13 +4,23 @@ import {connect} from "react-redux";
import { withRouter } from "react-router-dom";
import UserForm from "./../components/UserForm";
import type { User } from "../types/User";
import {modifyUser} from "../modules/users";
import {
modifyUser,
isModifyUserPending,
getModifyUserFailure
} from "../modules/users";
import type { History } from "history";
import ErrorNotification from "../../components/ErrorNotification";
type Props = {
user: User,
modifyUser: (user: User, callback?: () => void) => void,
loading: boolean,
error: Error,
// dispatch functions
modifyUser: (user: User, callback?: () => void) => void,
// context objects
user: User,
history: History
};
@@ -24,8 +34,17 @@ class EditUser extends React.Component<Props> {
};
render() {
const { user } = this.props;
return <UserForm submitForm={user => this.modifyUser(user)} user={user} />;
const { user, loading, error } = this.props;
return (
<div>
<ErrorNotification error={error} />
<UserForm
submitForm={user => this.modifyUser(user)}
user={user}
loading={loading}
/>
</div>
);
}
}
@@ -38,7 +57,12 @@ const mapDispatchToProps = dispatch => {
};
const mapStateToProps = (state, ownProps) => {
return {};
const loading = isModifyUserPending(state, ownProps.user.name);
const error = getModifyUserFailure(state, ownProps.user.name);
return {
loading,
error
};
};
export default connect(

View File

@@ -6,9 +6,16 @@ import { Route } from "react-router";
import { Details } from "./../components/table";
import EditUser from "./EditUser";
import type { User } from "../types/User";
import type { UserEntry } from "../types/UserEntry";
import type { History } from "history";
import { fetchUser, deleteUser } from "../modules/users";
import {
fetchUser,
deleteUser,
getUserByName,
isFetchUserPending,
getFetchUserFailure,
isDeleteUserPending,
getDeleteUserFailure
} from "../modules/users";
import Loading from "../../components/Loading";
import { Navigation, Section, NavLink } from "../../components/navigation";
@@ -16,14 +23,19 @@ import { DeleteUserNavLink, EditUserNavLink } from "./../components/navLinks";
import ErrorPage from "../../components/ErrorPage";
import { translate } from "react-i18next";
type Props = {
t: string => string,
name: string,
userEntry?: UserEntry,
match: any,
user: User,
loading: boolean,
error: Error,
// dispatcher functions
deleteUser: (user: User, callback?: () => void) => void,
fetchUser: string => void,
// context objects
t: string => string,
match: any,
history: History
};
@@ -52,23 +64,22 @@ class SingleUser extends React.Component<Props> {
};
render() {
const { t, userEntry } = this.props;
const { t, loading, error, user } = this.props;
if (!userEntry || userEntry.loading) {
return <Loading />;
}
if (userEntry.error) {
if (error) {
return (
<ErrorPage
title={t("single-user.error-title")}
subtitle={t("single-user.error-subtitle")}
error={userEntry.error}
error={error}
/>
);
}
const user = userEntry.entry;
if (!user || loading) {
return <Loading />;
}
const url = this.matchedUrl();
return (
@@ -84,7 +95,10 @@ class SingleUser extends React.Component<Props> {
<div className="column">
<Navigation>
<Section label={t("single-user.navigation-label")}>
<NavLink to={`${url}`} label={t("single-user.information-label")} />
<NavLink
to={`${url}`}
label={t("single-user.information-label")}
/>
<EditUserNavLink user={user} editUrl={`${url}/edit`} />
</Section>
<Section label={t("single-user.actions-label")}>
@@ -101,14 +115,17 @@ class SingleUser extends React.Component<Props> {
const mapStateToProps = (state, ownProps) => {
const name = ownProps.match.params.name;
let userEntry;
if (state.users && state.users.byNames) {
userEntry = state.users.byNames[name];
}
const user = getUserByName(state, name);
const loading =
isFetchUserPending(state, name) || isDeleteUserPending(state, name);
const error =
getFetchUserFailure(state, name) || getDeleteUserFailure(state, name);
return {
name,
userEntry
user,
loading,
error
};
};

View File

@@ -9,20 +9,24 @@ import {
fetchUsersByLink,
getUsersFromState,
selectListAsCollection,
isPermittedToCreateUsers
isPermittedToCreateUsers,
isFetchUsersPending,
getFetchUsersFailure
} from "../modules/users";
import { Page } from "../../components/layout";
import { UserTable } from "./../components/table";
import type { UserEntry } from "../types/UserEntry";
import type { PageCollectionStateSlice } from "../../types/Collection";
import type { User } from "../types/User";
import type { PagedCollection } from "../../types/Collection";
import Paginator from "../../components/Paginator";
import CreateUserButton from "../components/buttons/CreateUserButton";
type Props = {
userEntries: UserEntry[],
users: User[],
loading: boolean,
error: Error,
canAddUsers: boolean,
list: PageCollectionStateSlice,
list: PagedCollection,
page: number,
// context objects
@@ -48,9 +52,9 @@ class Users extends React.Component<Props> {
*/
componentDidUpdate = (prevProps: Props) => {
const { page, list } = this.props;
if (list.entry) {
if (list.page) {
// backend starts paging by 0
const statePage: number = list.entry.page + 1;
const statePage: number = list.page + 1;
if (page !== statePage) {
this.props.history.push(`/users/${statePage}`);
}
@@ -58,15 +62,15 @@ class Users extends React.Component<Props> {
};
render() {
const { userEntries, list, t } = this.props;
const { users, loading, error, t } = this.props;
return (
<Page
title={t("users.title")}
subtitle={t("users.subtitle")}
loading={list.loading || !userEntries}
error={list.error}
loading={loading || !users}
error={error}
>
<UserTable entries={userEntries} />
<UserTable users={users} />
{this.renderPaginator()}
{this.renderCreateButton()}
</Page>
@@ -75,10 +79,8 @@ class Users extends React.Component<Props> {
renderPaginator() {
const { list } = this.props;
if (list.entry) {
return (
<Paginator collection={list.entry} onPageChange={this.onPageChange} />
);
if (list) {
return <Paginator collection={list} onPageChange={this.onPageChange} />;
}
return null;
}
@@ -103,12 +105,18 @@ const getPageFromProps = props => {
};
const mapStateToProps = (state, ownProps) => {
const users = getUsersFromState(state);
const loading = isFetchUsersPending(state);
const error = getFetchUsersFailure(state);
const page = getPageFromProps(ownProps);
const userEntries = getUsersFromState(state);
const canAddUsers = isPermittedToCreateUsers(state);
const list = selectListAsCollection(state);
return {
userEntries,
users,
loading,
error,
canAddUsers,
list,
page

View File

@@ -1,37 +1,44 @@
// @flow
import { apiClient } from "../../apiclient";
import { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure";
import * as types from "../../modules/types";
import type { User } from "../types/User";
import type { UserEntry } from "../types/UserEntry";
import { combineReducers, Dispatch } from "redux";
import type { Action } from "../../types/Action";
import type { PageCollectionStateSlice } from "../../types/Collection";
import type { PagedCollection } from "../../types/Collection";
export const FETCH_USERS_PENDING = "scm/users/FETCH_USERS_PENDING";
export const FETCH_USERS_SUCCESS = "scm/users/FETCH_USERS_SUCCESS";
export const FETCH_USERS_FAILURE = "scm/users/FETCH_USERS_FAILURE";
export const FETCH_USERS = "scm/users/FETCH_USERS";
export const FETCH_USERS_PENDING = `${FETCH_USERS}_${types.PENDING_SUFFIX}`;
export const FETCH_USERS_SUCCESS = `${FETCH_USERS}_${types.SUCCESS_SUFFIX}`;
export const FETCH_USERS_FAILURE = `${FETCH_USERS}_${types.FAILURE_SUFFIX}`;
export const FETCH_USER_PENDING = "scm/users/FETCH_USER_PENDING";
export const FETCH_USER_SUCCESS = "scm/users/FETCH_USER_SUCCESS";
export const FETCH_USER_FAILURE = "scm/users/FETCH_USER_FAILURE";
export const FETCH_USER = "scm/users/FETCH_USER";
export const FETCH_USER_PENDING = `${FETCH_USER}_${types.PENDING_SUFFIX}`;
export const FETCH_USER_SUCCESS = `${FETCH_USER}_${types.SUCCESS_SUFFIX}`;
export const FETCH_USER_FAILURE = `${FETCH_USER}_${types.FAILURE_SUFFIX}`;
export const CREATE_USER_PENDING = "scm/users/CREATE_USER_PENDING";
export const CREATE_USER_SUCCESS = "scm/users/CREATE_USER_SUCCESS";
export const CREATE_USER_FAILURE = "scm/users/CREATE_USER_FAILURE";
export const CREATE_USER_RESET = "scm/users/CREATE_USER_RESET";
export const CREATE_USER = "scm/users/CREATE_USER";
export const CREATE_USER_PENDING = `${CREATE_USER}_${types.PENDING_SUFFIX}`;
export const CREATE_USER_SUCCESS = `${CREATE_USER}_${types.SUCCESS_SUFFIX}`;
export const CREATE_USER_FAILURE = `${CREATE_USER}_${types.FAILURE_SUFFIX}`;
export const CREATE_USER_RESET = `${CREATE_USER}_${types.RESET_SUFFIX}`;
export const MODIFY_USER_PENDING = "scm/users/MODIFY_USER_PENDING";
export const MODIFY_USER_SUCCESS = "scm/users/MODIFY_USER_SUCCESS";
export const MODIFY_USER_FAILURE = "scm/users/MODIFY_USER_FAILURE";
export const MODIFY_USER = "scm/users/MODIFY_USER";
export const MODIFY_USER_PENDING = `${MODIFY_USER}_${types.PENDING_SUFFIX}`;
export const MODIFY_USER_SUCCESS = `${MODIFY_USER}_${types.SUCCESS_SUFFIX}`;
export const MODIFY_USER_FAILURE = `${MODIFY_USER}_${types.FAILURE_SUFFIX}`;
export const DELETE_USER_PENDING = "scm/users/DELETE_PENDING";
export const DELETE_USER_SUCCESS = "scm/users/DELETE_SUCCESS";
export const DELETE_USER_FAILURE = "scm/users/DELETE_FAILURE";
export const DELETE_USER = "scm/users/DELETE";
export const DELETE_USER_PENDING = `${DELETE_USER}_${types.PENDING_SUFFIX}`;
export const DELETE_USER_SUCCESS = `${DELETE_USER}_${types.SUCCESS_SUFFIX}`;
export const DELETE_USER_FAILURE = `${DELETE_USER}_${types.FAILURE_SUFFIX}`;
const USERS_URL = "users";
const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2";
//TODO i18n
// TODO i18n for error messages
// fetch users
@@ -111,14 +118,16 @@ export function fetchUser(name: string) {
export function fetchUserPending(name: string): Action {
return {
type: FETCH_USER_PENDING,
payload: name
payload: name,
itemId: name
};
}
export function fetchUserSuccess(user: any): Action {
return {
type: FETCH_USER_SUCCESS,
payload: user
payload: user,
itemId: user.name
};
}
@@ -128,7 +137,8 @@ export function fetchUserFailure(name: string, error: Error): Action {
payload: {
name,
error
}
},
itemId: name
};
}
@@ -203,14 +213,16 @@ export function modifyUser(user: User, callback?: () => void) {
export function modifyUserPending(user: User): Action {
return {
type: MODIFY_USER_PENDING,
payload: user
payload: user,
itemId: user.name
};
}
export function modifyUserSuccess(user: User): Action {
return {
type: MODIFY_USER_SUCCESS,
payload: user
payload: user,
itemId: user.name
};
}
@@ -220,7 +232,8 @@ export function modifyUserFailure(user: User, error: Error): Action {
payload: {
error,
user
}
},
itemId: user.name
};
}
@@ -249,14 +262,16 @@ export function deleteUser(user: User, callback?: () => void) {
export function deleteUserPending(user: User): Action {
return {
type: DELETE_USER_PENDING,
payload: user
payload: user,
itemId: user.name
};
}
export function deleteUserSuccess(user: User): Action {
return {
type: DELETE_USER_SUCCESS,
payload: user
payload: user,
itemId: user.name
};
}
@@ -266,40 +281,20 @@ export function deleteUserFailure(user: User, error: Error): Action {
payload: {
error,
user
}
},
itemId: user.name
};
}
//helper functions
export function getUsersFromState(state: any) {
if (!state.users.list) {
return null;
}
const userNames = state.users.list.entries;
if (!userNames) {
return null;
}
const userEntries: Array<UserEntry> = [];
for (let userName of userNames) {
userEntries.push(state.users.byNames[userName]);
}
return userEntries;
}
function extractUsersByNames(
users: Array<User>,
userNames: Array<string>,
oldUsersByNames: {}
users: User[],
userNames: string[],
oldUsersByNames: Object
) {
const usersByNames = {};
for (let user of users) {
usersByNames[user.name] = {
entry: user
};
usersByNames[user.name] = user;
}
for (let userName in oldUsersByNames) {
@@ -335,20 +330,12 @@ const reducerByName = (state: any, username: string, newUserState: any) => {
function listReducer(state: any = {}, action: any = {}) {
switch (action.type) {
// Fetch all users actions
case FETCH_USERS_PENDING:
return {
...state,
loading: true
};
case FETCH_USERS_SUCCESS:
const users = action.payload._embedded.users;
const userNames = users.map(user => user.name);
return {
...state,
error: null,
entries: userNames,
loading: false,
entry: {
userCreatePermission: action.payload._links.create ? true : false,
page: action.payload.page,
@@ -356,12 +343,7 @@ function listReducer(state: any = {}, action: any = {}) {
_links: action.payload._links
}
};
case FETCH_USERS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error
};
// Delete single user actions
case DELETE_USER_SUCCESS:
const newUserEntries = deleteUserInEntries(
@@ -389,44 +371,12 @@ function byNamesReducer(state: any = {}, action: any = {}) {
};
// Fetch single user actions
case FETCH_USER_PENDING:
return reducerByName(state, action.payload, {
loading: true,
error: null
});
case FETCH_USER_SUCCESS:
return reducerByName(state, action.payload.name, {
loading: false,
error: null,
entry: action.payload
});
case FETCH_USER_FAILURE:
return reducerByName(state, action.payload.name, {
loading: false,
error: action.payload.error
});
return reducerByName(state, action.payload.name, action.payload);
// Update single user actions
case MODIFY_USER_PENDING:
return reducerByName(state, action.payload.name, {
loading: true
});
case MODIFY_USER_SUCCESS:
return reducerByName(state, action.payload.name, {
entry: action.payload
});
case MODIFY_USER_FAILURE:
return reducerByName(state, action.payload.user.name, {
error: action.payload.error
});
return reducerByName(state, action.payload.name, action.payload);
// Delete single user actions
case DELETE_USER_PENDING:
return reducerByName(state, action.payload.name, {
loading: true,
error: null,
entry: action.payload
});
case DELETE_USER_SUCCESS:
const newUserByNames = deleteUserInUsersByNames(
state,
@@ -434,17 +384,16 @@ function byNamesReducer(state: any = {}, action: any = {}) {
);
return newUserByNames;
case DELETE_USER_FAILURE:
return reducerByName(state, action.payload.user.name, {
loading: false,
error: action.payload.error,
entry: action.payload.user
});
default:
return state;
}
}
export default combineReducers({
list: listReducer,
byNames: byNamesReducer
});
// selectors
const selectList = (state: Object) => {
@@ -454,7 +403,7 @@ const selectList = (state: Object) => {
return {};
};
const selectListEntry = (state: Object) => {
const selectListEntry = (state: Object): Object => {
const list = selectList(state);
if (list.entry) {
return list.entry;
@@ -462,10 +411,8 @@ const selectListEntry = (state: Object) => {
return {};
};
export const selectListAsCollection = (
state: Object
): PageCollectionStateSlice => {
return selectList(state);
export const selectListAsCollection = (state: Object): PagedCollection => {
return selectListEntry(state);
};
export const isPermittedToCreateUsers = (state: Object): boolean => {
@@ -475,29 +422,63 @@ export const isPermittedToCreateUsers = (state: Object): boolean => {
}
return false;
};
function createReducer(state: any = {}, action: any = {}) {
switch (action.type) {
case CREATE_USER_PENDING:
return {
loading: true
};
case CREATE_USER_SUCCESS:
case CREATE_USER_RESET:
return {
loading: false
};
case CREATE_USER_FAILURE:
return {
loading: false,
error: action.payload
};
default:
return state;
export function getUsersFromState(state: Object) {
const userNames = selectList(state).entries;
if (!userNames) {
return null;
}
const userEntries: User[] = [];
for (let userName of userNames) {
userEntries.push(state.users.byNames[userName]);
}
return userEntries;
}
export function isFetchUsersPending(state: Object) {
return isPending(state, FETCH_USERS);
}
export function getFetchUsersFailure(state: Object) {
return getFailure(state, FETCH_USERS);
}
export function isCreateUserPending(state: Object) {
return isPending(state, CREATE_USER);
}
export function getCreateUserFailure(state: Object) {
return getFailure(state, CREATE_USER);
}
export function getUserByName(state: Object, name: string) {
if (state.users && state.users.byNames) {
return state.users.byNames[name];
}
}
export default combineReducers({
list: listReducer,
byNames: byNamesReducer,
create: createReducer
});
export function isFetchUserPending(state: Object, name: string) {
return isPending(state, FETCH_USER, name);
}
export function getFetchUserFailure(state: Object, name: string) {
return getFailure(state, FETCH_USER, name);
}
export function isModifyUserPending(state: Object, name: string) {
return isPending(state, MODIFY_USER, name);
}
export function getModifyUserFailure(state: Object, name: string) {
return getFailure(state, MODIFY_USER, name);
}
export function isDeleteUserPending(state: Object, name: string) {
return isPending(state, DELETE_USER, name);
}
export function getDeleteUserFailure(state: Object, name: string) {
return getFailure(state, DELETE_USER, name);
}

View File

@@ -8,16 +8,10 @@ import reducer, {
CREATE_USER_PENDING,
CREATE_USER_SUCCESS,
createUser,
createUserFailure,
createUserPending,
createUserSuccess,
createUserReset,
DELETE_USER_FAILURE,
DELETE_USER_PENDING,
DELETE_USER_SUCCESS,
deleteUser,
deleteUserFailure,
deleteUserPending,
deleteUserSuccess,
FETCH_USER_FAILURE,
FETCH_USER_PENDING,
@@ -26,21 +20,15 @@ import reducer, {
FETCH_USERS_PENDING,
FETCH_USERS_SUCCESS,
fetchUser,
fetchUserFailure,
fetchUserPending,
fetchUsers,
fetchUsersFailure,
fetchUsersPending,
fetchUsersSuccess,
fetchUserSuccess,
fetchUsers,
fetchUsersSuccess,
selectListAsCollection,
isPermittedToCreateUsers,
MODIFY_USER_FAILURE,
MODIFY_USER_PENDING,
MODIFY_USER_SUCCESS,
modifyUser,
modifyUserFailure,
modifyUserPending,
modifyUserSuccess
} from "./users";
@@ -325,19 +313,11 @@ describe("users fetch()", () => {
});
describe("users reducer", () => {
it("should update state correctly according to FETCH_USERS_PENDING action", () => {
const newState = reducer({}, fetchUsersPending());
expect(newState.list.loading).toBeTruthy();
expect(newState.list.error).toBeFalsy();
});
it("should update state correctly according to FETCH_USERS_SUCCESS action", () => {
const newState = reducer({}, fetchUsersSuccess(responseBody));
expect(newState.list).toEqual({
entries: ["zaphod", "ford"],
error: null,
loading: false,
entry: {
userCreatePermission: true,
page: 0,
@@ -347,31 +327,13 @@ describe("users reducer", () => {
});
expect(newState.byNames).toEqual({
zaphod: {
entry: userZaphod
},
ford: {
entry: userFord
}
zaphod: userZaphod,
ford: userFord
});
expect(newState.list.entry.userCreatePermission).toBeTruthy();
});
it("should set error when fetching users failed", () => {
const oldState = {
list: {
loading: true
}
};
const error = new Error("kaputt");
const newState = reducer(oldState, fetchUsersFailure("url.com", error));
expect(newState.list.loading).toBeFalsy();
expect(newState.list.error).toEqual(error);
});
it("should set userCreatePermission to true if update link is present", () => {
const newState = reducer({}, fetchUsersSuccess(responseBody));
@@ -381,9 +343,7 @@ describe("users reducer", () => {
it("should not replace whole byNames map when fetching users", () => {
const oldState = {
byNames: {
ford: {
entry: userFord
}
ford: userFord
}
};
@@ -392,95 +352,14 @@ describe("users reducer", () => {
expect(newState.byNames["ford"]).toBeDefined();
});
it("should update state correctly according to DELETE_USER_PENDING action", () => {
const state = {
byNames: {
zaphod: {
loading: false,
error: null,
entry: userZaphod
}
}
};
const newState = reducer(state, deleteUserPending(userZaphod));
const zaphod = newState.byNames["zaphod"];
expect(zaphod.loading).toBeTruthy();
expect(zaphod.entry).toBe(userZaphod);
});
it("should not effect other users if one user will be deleted", () => {
const state = {
byNames: {
zaphod: {
loading: false,
error: null,
entry: userZaphod
},
ford: {
loading: false
}
}
};
const newState = reducer(state, deleteUserPending(userZaphod));
const ford = newState.byNames["ford"];
expect(ford.loading).toBeFalsy();
});
it("should set the error of user which could not be deleted", () => {
const state = {
byNames: {
zaphod: {
loading: true,
entry: userZaphod
}
}
};
const error = new Error("error");
const newState = reducer(state, deleteUserFailure(userZaphod, error));
const zaphod = newState.byNames["zaphod"];
expect(zaphod.loading).toBeFalsy();
expect(zaphod.error).toBe(error);
});
it("should not effect other users if one user could not be deleted", () => {
const state = {
byNames: {
zaphod: {
loading: false,
error: null,
entry: userZaphod
},
ford: {
loading: false
}
}
};
const error = new Error("error");
const newState = reducer(state, deleteUserFailure(userZaphod, error));
const ford = newState.byNames["ford"];
expect(ford.loading).toBeFalsy();
});
it("should remove user from state when delete succeeds", () => {
const state = {
list: {
entries: ["ford", "zaphod"]
},
byNames: {
zaphod: {
loading: true,
error: null,
entry: userZaphod
},
ford: {
loading: true,
error: null,
entry: userFord
}
zaphod: userZaphod,
ford: userFord
}
};
@@ -499,68 +378,9 @@ describe("users reducer", () => {
expect(newState.byNames["zaphod"]).toBeTruthy();
});
it("should update state correctly according to CREATE_USER_PENDING action", () => {
const newState = reducer({}, createUserPending(userZaphod));
expect(newState.create.loading).toBeTruthy();
expect(newState.create.error).toBeFalsy();
});
it("should update state correctly according to CREATE_USER_SUCCESS action", () => {
const newState = reducer(
{ create: { loading: true } },
createUserSuccess()
);
expect(newState.create.loading).toBeFalsy();
expect(newState.create.error).toBeFalsy();
});
it("should set the loading to false and the error if user could not be created", () => {
const newState = reducer(
{ create: { loading: true, error: null } },
createUserFailure(new Error("kaputt kaputt"))
);
expect(newState.create.loading).toBeFalsy();
expect(newState.create.error).toEqual(new Error("kaputt kaputt"));
});
it("should reset the user create form", () => {
const newState = reducer(
{ create: { loading: true, error: new Error("kaputt kaputt") } },
createUserReset()
);
expect(newState.create.loading).toBeFalsy();
expect(newState.create.error).toBeFalsy();
});
it("should update state according to FETCH_USER_PENDING action", () => {
const newState = reducer({}, fetchUserPending("zaphod"));
expect(newState.byNames["zaphod"].loading).toBeTruthy();
});
it("should not affect list state", () => {
const newState = reducer(
{
list: {
entries: ["ford"]
}
},
fetchUserPending("zaphod")
);
expect(newState.byNames["zaphod"].loading).toBeTruthy();
expect(newState.list.entries).toEqual(["ford"]);
});
it("should update state according to FETCH_USER_FAILURE action", () => {
const error = new Error("kaputt!");
const newState = reducer({}, fetchUserFailure(userFord.name, error));
expect(newState.byNames["ford"].error).toBe(error);
expect(newState.byNames["ford"].loading).toBeFalsy();
});
it("should update state according to FETCH_USER_SUCCESS action", () => {
const newState = reducer({}, fetchUserSuccess(userFord));
expect(newState.byNames["ford"].loading).toBeFalsy();
expect(newState.byNames["ford"].entry).toBe(userFord);
expect(newState.byNames["ford"]).toBe(userFord);
});
it("should affect users state nor the state of other users", () => {
@@ -572,62 +392,22 @@ describe("users reducer", () => {
},
fetchUserSuccess(userFord)
);
expect(newState.byNames["ford"].loading).toBeFalsy();
expect(newState.byNames["ford"].entry).toBe(userFord);
expect(newState.byNames["ford"]).toBe(userFord);
expect(newState.list.entries).toEqual(["zaphod"]);
});
it("should update state according to MODIFY_USER_PENDING action", () => {
const newState = reducer(
{
byNames: {
ford: {
error: new Error("something"),
entry: {}
}
}
},
modifyUserPending(userFord)
);
expect(newState.byNames["ford"].loading).toBeTruthy();
expect(newState.byNames["ford"].error).toBeFalsy();
expect(newState.byNames["ford"].entry).toBeFalsy();
});
it("should update state according to MODIFY_USER_SUCCESS action", () => {
const newState = reducer(
{
byNames: {
ford: {
loading: true,
error: new Error("something"),
entry: {}
name: "ford"
}
}
},
modifyUserSuccess(userFord)
);
expect(newState.byNames["ford"].loading).toBeFalsy();
expect(newState.byNames["ford"].error).toBeFalsy();
expect(newState.byNames["ford"].entry).toBe(userFord);
});
it("should update state according to MODIFY_USER_SUCCESS action", () => {
const error = new Error("something went wrong");
const newState = reducer(
{
byNames: {
ford: {
loading: true,
entry: {}
}
}
},
modifyUserFailure(userFord, error)
);
expect(newState.byNames["ford"].loading).toBeFalsy();
expect(newState.byNames["ford"].error).toBe(error);
expect(newState.byNames["ford"].entry).toBeFalsy();
expect(newState.byNames["ford"]).toBe(userFord);
});
});

View File

@@ -1,5 +0,0 @@
export type UserEntry = {
loading: boolean,
error: Error,
entry: User
};