mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
Added tests, fixed edit/add users
This commit is contained in:
11
scm-ui/src/components/AddButton.js
Normal file
11
scm-ui/src/components/AddButton.js
Normal file
@@ -0,0 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import Button, { type ButtonProps } from "./Button";
|
||||
|
||||
class AddButton extends React.Component<ButtonProps> {
|
||||
render() {
|
||||
return <Button type="default" {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default AddButton;
|
||||
@@ -1,12 +1,15 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export type ButtonProps = {
|
||||
label: string,
|
||||
loading?: boolean,
|
||||
disabled?: boolean,
|
||||
action: () => void
|
||||
action?: () => void,
|
||||
link?: string,
|
||||
fullWidth?: boolean
|
||||
};
|
||||
|
||||
type Props = ButtonProps & {
|
||||
@@ -14,18 +17,33 @@ type Props = ButtonProps & {
|
||||
};
|
||||
|
||||
class Button extends React.Component<Props> {
|
||||
render() {
|
||||
const { label, loading, disabled, type, action } = this.props;
|
||||
renderButton = () => {
|
||||
const { label, loading, disabled, type, action, fullWidth } = this.props;
|
||||
const loadingClass = loading ? "is-loading" : "";
|
||||
const fullWidthClass = fullWidth ? "is-fullwidth" : "";
|
||||
return (
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={action}
|
||||
className={classNames("button", "is-" + type, loadingClass)}
|
||||
onClick={action ? action : () => {}}
|
||||
className={classNames(
|
||||
"button",
|
||||
"is-" + type,
|
||||
loadingClass,
|
||||
fullWidthClass
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { link } = this.props;
|
||||
if (link) {
|
||||
return <Link to={link}>{this.renderButton()}</Link>;
|
||||
} else {
|
||||
return this.renderButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import Button, { type ButtonProps } from "./Button";
|
||||
|
||||
type Props = {
|
||||
value: string,
|
||||
disabled?: boolean,
|
||||
isLoading?: boolean,
|
||||
large?: boolean,
|
||||
fullWidth?: boolean
|
||||
};
|
||||
|
||||
class SubmitButton extends React.Component<Props> {
|
||||
class SubmitButton extends React.Component<ButtonProps> {
|
||||
render() {
|
||||
const { value, large, fullWidth, isLoading, disabled } = this.props;
|
||||
|
||||
const largeClass = large ? "is-large" : "";
|
||||
const fullWidthClass = fullWidth ? "is-fullwidth" : "";
|
||||
const loadingClass = isLoading ? "is-loading" : "";
|
||||
|
||||
return (
|
||||
<div className="field">
|
||||
<div className="control">
|
||||
<button
|
||||
disabled={disabled}
|
||||
className={classNames(
|
||||
"button",
|
||||
"is-link",
|
||||
largeClass,
|
||||
fullWidthClass,
|
||||
loadingClass
|
||||
)}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <Button type="primary" {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,10 +117,10 @@ class Login extends React.Component<Props, State> {
|
||||
onChange={this.handlePasswordChange}
|
||||
/>
|
||||
<SubmitButton
|
||||
value="Login"
|
||||
label="Login"
|
||||
disabled={this.isInValid()}
|
||||
fullWidth={true}
|
||||
isLoading={loading}
|
||||
loading={loading}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -5,10 +5,10 @@ import UserForm from "./UserForm";
|
||||
import type { User } from "../types/User";
|
||||
|
||||
import { addUser } from "../modules/users";
|
||||
import { Route, Link } from "react-router-dom";
|
||||
|
||||
type Props = {
|
||||
addUser: User => void
|
||||
addUser: User => void,
|
||||
loading?: boolean
|
||||
};
|
||||
|
||||
class AddUser extends React.Component<Props> {
|
||||
@@ -17,7 +17,10 @@ class AddUser extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<UserForm submitForm={user => addUser(user)} />
|
||||
<UserForm
|
||||
submitForm={user => addUser(user)}
|
||||
loading={this.props.loading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -32,6 +35,11 @@ const mapDispatchToProps = dispatch => {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
if (state.users && state.users.users) {
|
||||
return {
|
||||
loading: state.users.users.loading
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
@@ -3,21 +3,16 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import UserForm from "./UserForm";
|
||||
import type { User } from "../types/User";
|
||||
import Loading from "../../components/Loading";
|
||||
|
||||
import {
|
||||
updateUser,
|
||||
deleteUser,
|
||||
editUser,
|
||||
fetchUser,
|
||||
getUsersFromState
|
||||
} from "../modules/users";
|
||||
import { Route, Link } from "react-router-dom";
|
||||
import { updateUser, fetchUser } from "../modules/users";
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
fetchUser: string => void,
|
||||
usersByNames: Map<string, any>,
|
||||
updateUser: User => void
|
||||
userEntry?: UserEntry,
|
||||
updateUser: User => void,
|
||||
loading: boolean
|
||||
};
|
||||
|
||||
class EditUser extends React.Component<Props> {
|
||||
@@ -28,15 +23,18 @@ class EditUser extends React.Component<Props> {
|
||||
render() {
|
||||
const submitUser = this.props.updateUser;
|
||||
|
||||
const { usersByNames, name } = this.props;
|
||||
const { userEntry } = this.props;
|
||||
|
||||
if (!usersByNames || usersByNames[name].loading) {
|
||||
return <div>Loading...</div>;
|
||||
if (!userEntry || userEntry.loading) {
|
||||
return <Loading />;
|
||||
} else {
|
||||
const user = usersByNames[name].entry;
|
||||
return (
|
||||
<div>
|
||||
<UserForm submitForm={user => submitUser(user)} user={user} />
|
||||
<UserForm
|
||||
submitForm={user => submitUser(user)}
|
||||
user={userEntry.entry}
|
||||
loading={userEntry.loading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -55,9 +53,15 @@ const mapDispatchToProps = dispatch => {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const name = ownProps.match.params.name;
|
||||
let userEntry;
|
||||
if (state.users && state.users.usersByNames) {
|
||||
userEntry = state.users.usersByNames[name];
|
||||
}
|
||||
|
||||
return {
|
||||
usersByNames: state.users.usersByNames,
|
||||
name: ownProps.match.params.name
|
||||
name,
|
||||
userEntry
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import EditButton from "../../components/EditButton";
|
||||
import type { User } from "../types/User";
|
||||
import type { UserEntry } from "../types/UserEntry";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
@@ -17,11 +16,7 @@ class EditUserButton extends React.Component<Props> {
|
||||
if (!this.isEditable()) {
|
||||
return "";
|
||||
}
|
||||
return (
|
||||
<Link to={link}>
|
||||
<EditButton label="Edit" action={() => {}} loading={entry.loading} />
|
||||
</Link>
|
||||
);
|
||||
return <EditButton label="Edit" link={link} loading={entry.loading} />;
|
||||
}
|
||||
|
||||
isEditable = () => {
|
||||
|
||||
@@ -4,10 +4,12 @@ import type { User } from "../types/User";
|
||||
import InputField from "../../components/InputField";
|
||||
import Checkbox from "../../components/Checkbox";
|
||||
import SubmitButton from "../../components/SubmitButton";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
type Props = {
|
||||
submitForm: User => void,
|
||||
user?: User
|
||||
user?: User,
|
||||
loading?: boolean
|
||||
};
|
||||
|
||||
class UserForm extends React.Component<Props, User> {
|
||||
@@ -69,7 +71,7 @@ class UserForm extends React.Component<Props, User> {
|
||||
onChange={this.handleActiveChange}
|
||||
checked={user ? user.active : false}
|
||||
/>
|
||||
<SubmitButton value="Submit" />
|
||||
<SubmitButton label="Submit" loading={this.props.loading} />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import Loading from "../../components/Loading";
|
||||
import ErrorNotification from "../../components/ErrorNotification";
|
||||
import UserTable from "./UserTable";
|
||||
import type { User } from "../types/User";
|
||||
import AddButton from "../../components/AddButton";
|
||||
import type { UserEntry } from "../types/UserEntry";
|
||||
|
||||
type Props = {
|
||||
@@ -14,7 +15,8 @@ type Props = {
|
||||
error: Error,
|
||||
userEntries: Array<UserEntry>,
|
||||
fetchUsers: () => void,
|
||||
deleteUser: User => void
|
||||
deleteUser: User => void,
|
||||
canAddUsers: boolean
|
||||
};
|
||||
|
||||
class Users extends React.Component<Props, User> {
|
||||
@@ -29,6 +31,7 @@ class Users extends React.Component<Props, User> {
|
||||
<h1 className="title">SCM</h1>
|
||||
<h2 className="subtitle">Users</h2>
|
||||
{this.renderContent()}
|
||||
{this.renderAddButton()}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
@@ -47,13 +50,26 @@ class Users extends React.Component<Props, User> {
|
||||
return <Loading />;
|
||||
}
|
||||
}
|
||||
|
||||
renderAddButton() {
|
||||
if (this.props.canAddUsers) {
|
||||
return (
|
||||
<div>
|
||||
<AddButton label="Add User" link="/users/add" />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const userEntries = getUsersFromState(state);
|
||||
return {
|
||||
userEntries,
|
||||
error: state.users.error
|
||||
error: state.users.error,
|
||||
canAddUsers: state.users.userCreatePermission
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ export const ADD_USER = "scm/users/ADD";
|
||||
export const ADD_USER_SUCCESS = "scm/users/ADD_SUCCESS";
|
||||
export const ADD_USER_FAILURE = "scm/users/ADD_FAILURE";
|
||||
|
||||
export const EDIT_USER = "scm/users/EDIT";
|
||||
|
||||
export const UPDATE_USER = "scm/users/UPDATE";
|
||||
export const UPDATE_USER_SUCCESS = "scm/users/UPDATE_SUCCESS";
|
||||
export const UPDATE_USER_FAILURE = "scm/users/UPDATE_FAILURE";
|
||||
@@ -31,13 +29,13 @@ const USER_URL = "users/";
|
||||
|
||||
const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2";
|
||||
|
||||
function requestUsers() {
|
||||
export function requestUsers() {
|
||||
return {
|
||||
type: FETCH_USERS
|
||||
};
|
||||
}
|
||||
|
||||
function failedToFetchUsers(url: string, err: Error) {
|
||||
export function failedToFetchUsers(url: string, err: Error) {
|
||||
return {
|
||||
type: FETCH_USERS_FAILURE,
|
||||
payload: err,
|
||||
@@ -78,14 +76,14 @@ export function fetchUsers() {
|
||||
};
|
||||
}
|
||||
|
||||
function fetchUsersSuccess(users: any) {
|
||||
export function fetchUsersSuccess(users: any) {
|
||||
return {
|
||||
type: FETCH_USERS_SUCCESS,
|
||||
payload: users
|
||||
};
|
||||
}
|
||||
|
||||
function requestUser(name: string) {
|
||||
export function requestUser(name: string) {
|
||||
return {
|
||||
type: FETCH_USER,
|
||||
payload: { name }
|
||||
@@ -126,7 +124,7 @@ function fetchUserSuccess(user: User) {
|
||||
};
|
||||
}
|
||||
|
||||
function requestAddUser(user: User) {
|
||||
export function requestAddUser(user: User) {
|
||||
return {
|
||||
type: ADD_USER,
|
||||
user
|
||||
@@ -142,17 +140,24 @@ export function addUser(user: User) {
|
||||
dispatch(addUserSuccess());
|
||||
dispatch(fetchUsers());
|
||||
})
|
||||
.catch(err => dispatch(addUserFailure(user, err)));
|
||||
.catch(err =>
|
||||
dispatch(
|
||||
addUserFailure(
|
||||
user,
|
||||
new Error(`failed to add user ${user.name}: ${err.message}`)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function addUserSuccess() {
|
||||
export function addUserSuccess() {
|
||||
return {
|
||||
type: ADD_USER_SUCCESS
|
||||
};
|
||||
}
|
||||
|
||||
function addUserFailure(user: User, err: Error) {
|
||||
export function addUserFailure(user: User, err: Error) {
|
||||
return {
|
||||
type: ADD_USER_FAILURE,
|
||||
payload: err,
|
||||
@@ -267,13 +272,6 @@ function extractUsersByNames(
|
||||
return usersByNames;
|
||||
}
|
||||
|
||||
export function editUser(user: User) {
|
||||
return {
|
||||
type: EDIT_USER,
|
||||
user
|
||||
};
|
||||
}
|
||||
|
||||
const reduceUsersByNames = (
|
||||
state: any,
|
||||
username: string,
|
||||
@@ -320,6 +318,7 @@ export default function reducer(state: any = {}, action: any = {}) {
|
||||
return {
|
||||
...state,
|
||||
users: {
|
||||
userCreatePermission: action.payload._links.create ? true : false,
|
||||
error: null,
|
||||
entries: userNames,
|
||||
loading: false
|
||||
@@ -349,10 +348,33 @@ export default function reducer(state: any = {}, action: any = {}) {
|
||||
error: action.payload.error,
|
||||
entry: action.payload.user
|
||||
});
|
||||
case EDIT_USER:
|
||||
case ADD_USER:
|
||||
return {
|
||||
...state,
|
||||
editUser: action.user
|
||||
users: {
|
||||
...state.users,
|
||||
loading: true,
|
||||
error: null
|
||||
}
|
||||
};
|
||||
case ADD_USER_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
users: {
|
||||
...state.users,
|
||||
loading: false,
|
||||
error: null
|
||||
}
|
||||
|
||||
};
|
||||
case ADD_USER_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
users: {
|
||||
...state.users,
|
||||
loading: false,
|
||||
error: action.payload
|
||||
}
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -16,13 +16,17 @@ import {
|
||||
UPDATE_USER,
|
||||
UPDATE_USER_FAILURE,
|
||||
UPDATE_USER_SUCCESS,
|
||||
EDIT_USER,
|
||||
requestDeleteUser,
|
||||
deleteUserFailure,
|
||||
DELETE_USER,
|
||||
DELETE_USER_SUCCESS,
|
||||
DELETE_USER_FAILURE,
|
||||
deleteUser
|
||||
deleteUser,
|
||||
requestUsers,
|
||||
fetchUsersSuccess,
|
||||
requestAddUser,
|
||||
addUserSuccess,
|
||||
addUserFailure
|
||||
} from "./users";
|
||||
|
||||
import reducer from "./users";
|
||||
@@ -258,16 +262,13 @@ describe("users fetch()", () => {
|
||||
|
||||
describe("users reducer", () => {
|
||||
test("should update state correctly according to FETCH_USERS action", () => {
|
||||
const newState = reducer({}, { type: FETCH_USERS });
|
||||
const newState = reducer({}, requestUsers());
|
||||
expect(newState.loading).toBeTruthy();
|
||||
expect(newState.error).toBeNull();
|
||||
});
|
||||
|
||||
it("should update state correctly according to FETCH_USERS_SUCCESS action", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
{ type: FETCH_USERS_SUCCESS, payload: responseBody }
|
||||
);
|
||||
const newState = reducer({}, fetchUsersSuccess(responseBody));
|
||||
|
||||
expect(newState.users).toEqual({
|
||||
entries: ["zaphod", "ford"],
|
||||
@@ -285,7 +286,7 @@ describe("users reducer", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should update state correctly according to DELETE_USER action", () => {
|
||||
it("should update state correctly according to DELETE_USER action", () => {
|
||||
const state = {
|
||||
usersByNames: {
|
||||
zaphod: {
|
||||
@@ -367,22 +368,35 @@ describe("users reducer", () => {
|
||||
}
|
||||
};
|
||||
|
||||
const newState = reducer(oldState, {
|
||||
type: FETCH_USERS_SUCCESS,
|
||||
payload: responseBodyZaphod
|
||||
});
|
||||
const newState = reducer(oldState, fetchUsersSuccess(responseBody));
|
||||
expect(newState.usersByNames["zaphod"]).toBeDefined();
|
||||
expect(newState.usersByNames["ford"]).toBeDefined();
|
||||
});
|
||||
|
||||
it("should update state correctly according to EDIT_USER action", () => {
|
||||
it("should set userCreatePermission to true if update link is present", () => {
|
||||
const newState = reducer({}, fetchUsersSuccess(responseBody));
|
||||
|
||||
expect(newState.users.userCreatePermission).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should update state correctly according to ADD_USER action", () => {
|
||||
const newState = reducer({}, requestAddUser(userZaphod));
|
||||
expect(newState.users.loading).toBeTruthy();
|
||||
expect(newState.users.error).toBeNull();
|
||||
});
|
||||
|
||||
it("should update state correctly according to ADD_USER_SUCCESS action", () => {
|
||||
const newState = reducer({ loading: true }, addUserSuccess());
|
||||
expect(newState.users.loading).toBeFalsy();
|
||||
expect(newState.users.error).toBeNull();
|
||||
});
|
||||
|
||||
it("should set the loading to false and the error if user could not be added", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
{
|
||||
type: EDIT_USER,
|
||||
user: userZaphod
|
||||
}
|
||||
{ loading: true, error: null },
|
||||
addUserFailure(userFord, new Error("kaputt kaputt"))
|
||||
);
|
||||
expect(newState.editUser).toEqual(userZaphod);
|
||||
expect(newState.users.loading).toBeFalsy();
|
||||
expect(newState.users.error).toEqual(new Error("kaputt kaputt"));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user