mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 14:35:45 +01:00
Added tests/refactored users module
This commit is contained in:
@@ -4,13 +4,14 @@ import DeleteUserButton from "./DeleteUserButton";
|
||||
import type { User } from "../types/User";
|
||||
|
||||
type Props = {
|
||||
user: User,
|
||||
entry: { loading: boolean, error: Error, user: User },
|
||||
deleteUser: string => void
|
||||
};
|
||||
|
||||
export default class UserRow extends React.Component<Props> {
|
||||
render() {
|
||||
const { user, deleteUser } = this.props;
|
||||
const { deleteUser } = this.props;
|
||||
const user = this.props.entry.entry;
|
||||
return (
|
||||
<tr>
|
||||
<td>{user.name}</td>
|
||||
|
||||
@@ -4,13 +4,14 @@ import UserRow from "./UserRow";
|
||||
import type { User } from "../types/User";
|
||||
|
||||
type Props = {
|
||||
users: Array<User>,
|
||||
entries: [{ loading: boolean, error: Error, user: User }],
|
||||
deleteUser: string => void
|
||||
};
|
||||
|
||||
class UserTable extends React.Component<Props> {
|
||||
render() {
|
||||
const { users, deleteUser } = this.props;
|
||||
const { deleteUser } = this.props;
|
||||
const entries = this.props.entries;
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
@@ -22,8 +23,10 @@ class UserTable extends React.Component<Props> {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user, index) => {
|
||||
return <UserRow key={index} user={user} deleteUser={deleteUser} />;
|
||||
{entries.map((entry, index) => {
|
||||
return (
|
||||
<UserRow key={index} entry={entry} deleteUser={deleteUser} />
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -2,15 +2,27 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { fetchUsers, addUser, editUser, deleteUser } from "../modules/users";
|
||||
import {
|
||||
fetchUsers,
|
||||
addUser,
|
||||
editUser,
|
||||
deleteUser,
|
||||
getUsersFromState
|
||||
} from "../modules/users";
|
||||
import UserForm from "./UserForm";
|
||||
import UserTable from "./UserTable";
|
||||
import type { User } from "../types/User";
|
||||
|
||||
type UserEntry = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
user: User
|
||||
};
|
||||
|
||||
type Props = {
|
||||
login: boolean,
|
||||
error: Error,
|
||||
users: Array<User>,
|
||||
userEntries: Array<UserEntry>,
|
||||
fetchUsers: () => void,
|
||||
deleteUser: string => void,
|
||||
addUser: User => void,
|
||||
@@ -31,7 +43,7 @@ class Users extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { users, deleteUser } = this.props;
|
||||
const { userEntries, deleteUser } = this.props;
|
||||
const testUser: User = {
|
||||
name: "user",
|
||||
displayName: "user_display",
|
||||
@@ -40,13 +52,13 @@ class Users extends React.Component<Props> {
|
||||
active: true,
|
||||
admin: true
|
||||
};
|
||||
if (users) {
|
||||
if (userEntries) {
|
||||
return (
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<h1 className="title">SCM</h1>
|
||||
<h2 className="subtitle">Users</h2>
|
||||
<UserTable users={users} deleteUser={deleteUser} />
|
||||
<UserTable entries={userEntries} deleteUser={deleteUser} />
|
||||
{/* <UserForm submitForm={this.submitForm} /> */}
|
||||
<UserForm submitForm={user => {}} user={testUser} />
|
||||
</div>
|
||||
@@ -59,8 +71,12 @@ class Users extends React.Component<Props> {
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const userEntries = getUsersFromState(state);
|
||||
if (!userEntries) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
users: state.users.users
|
||||
userEntries
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -3,22 +3,22 @@ import { apiClient, PAGE_NOT_FOUND_ERROR } from "../../apiclient";
|
||||
import type { User } from "../types/User";
|
||||
import { ThunkDispatch } from "redux-thunk";
|
||||
|
||||
const FETCH_USERS = "scm/users/FETCH";
|
||||
const FETCH_USERS_SUCCESS = "scm/users/FETCH_SUCCESS";
|
||||
const FETCH_USERS_FAILURE = "scm/users/FETCH_FAILURE";
|
||||
const FETCH_USERS_NOTFOUND = "scm/users/FETCH_NOTFOUND";
|
||||
export const FETCH_USERS = "scm/users/FETCH";
|
||||
export const FETCH_USERS_SUCCESS = "scm/users/FETCH_SUCCESS";
|
||||
export const FETCH_USERS_FAILURE = "scm/users/FETCH_FAILURE";
|
||||
export const FETCH_USERS_NOTFOUND = "scm/users/FETCH_NOTFOUND";
|
||||
|
||||
const ADD_USER = "scm/users/ADD";
|
||||
const ADD_USER_SUCCESS = "scm/users/ADD_SUCCESS";
|
||||
const ADD_USER_FAILURE = "scm/users/ADD_FAILURE";
|
||||
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";
|
||||
|
||||
const EDIT_USER = "scm/users/EDIT";
|
||||
const EDIT_USER_SUCCESS = "scm/users/EDIT_SUCCESS";
|
||||
const EDIT_USER_FAILURE = "scm/users/EDIT_FAILURE";
|
||||
export const EDIT_USER = "scm/users/EDIT";
|
||||
export const EDIT_USER_SUCCESS = "scm/users/EDIT_SUCCESS";
|
||||
export const EDIT_USER_FAILURE = "scm/users/EDIT_FAILURE";
|
||||
|
||||
const DELETE_USER = "scm/users/DELETE";
|
||||
const DELETE_USER_SUCCESS = "scm/users/DELETE_SUCCESS";
|
||||
const DELETE_USER_FAILURE = "scm/users/DELETE_FAILURE";
|
||||
export const DELETE_USER = "scm/users/DELETE";
|
||||
export const DELETE_USER_SUCCESS = "scm/users/DELETE_SUCCESS";
|
||||
export const DELETE_USER_FAILURE = "scm/users/DELETE_FAILURE";
|
||||
|
||||
const USERS_URL = "users";
|
||||
|
||||
@@ -45,7 +45,7 @@ function usersNotFound(url: string) {
|
||||
}
|
||||
|
||||
export function fetchUsers() {
|
||||
return function(dispatch: ThunkDispatch) {
|
||||
return function(dispatch: any) {
|
||||
dispatch(requestUsers());
|
||||
return apiClient
|
||||
.get(USERS_URL)
|
||||
@@ -179,21 +179,58 @@ export function deleteUser(link: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getUsersFromState(state) {
|
||||
if (!state.users.users) {
|
||||
return null;
|
||||
}
|
||||
const userNames = state.users.users.entries;
|
||||
if (!userNames) {
|
||||
return null;
|
||||
}
|
||||
var userEntries = new Array();
|
||||
|
||||
for (let userName of userNames) {
|
||||
userEntries.push(state.users.usersByNames[userName]);
|
||||
}
|
||||
|
||||
return userEntries;
|
||||
}
|
||||
|
||||
function extractUsersByNames(users: Array<User>, userNames: Array<string>) {
|
||||
var usersByNames = {};
|
||||
|
||||
for (let user of users) {
|
||||
usersByNames[user.name] = {
|
||||
entry: user
|
||||
};
|
||||
}
|
||||
return usersByNames;
|
||||
}
|
||||
|
||||
export default function reducer(state: any = {}, action: any = {}) {
|
||||
switch (action.type) {
|
||||
case FETCH_USERS:
|
||||
return {
|
||||
loading: true,
|
||||
error: null
|
||||
};
|
||||
case DELETE_USER:
|
||||
return {
|
||||
...state,
|
||||
users: null,
|
||||
loading: true
|
||||
users: null
|
||||
};
|
||||
case FETCH_USERS_SUCCESS:
|
||||
const users = action.payload._embedded.users;
|
||||
const userNames = users.map(user => user.name);
|
||||
const usersByNames = {...state.usersByNames, extractUsersByNames(users, userNames)};
|
||||
return {
|
||||
...state,
|
||||
users: {
|
||||
error: null,
|
||||
users: action.payload._embedded.users,
|
||||
entries: userNames,
|
||||
loading: false
|
||||
},
|
||||
usersByNames
|
||||
};
|
||||
case FETCH_USERS_FAILURE:
|
||||
case DELETE_USER_FAILURE:
|
||||
|
||||
202
scm-ui/src/users/modules/users.test.js
Normal file
202
scm-ui/src/users/modules/users.test.js
Normal file
@@ -0,0 +1,202 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { configure, shallow } from "enzyme";
|
||||
import Adapter from "enzyme-adapter-react-16";
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
|
||||
import {
|
||||
FETCH_USERS,
|
||||
FETCH_USERS_SUCCESS,
|
||||
fetchUsers,
|
||||
FETCH_USERS_FAILURE
|
||||
} from "./users";
|
||||
|
||||
import reducer from "./users";
|
||||
|
||||
import "raf/polyfill";
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
const userZaphod = {
|
||||
active: true,
|
||||
admin: true,
|
||||
creationDate: "2018-07-11T12:23:49.027Z",
|
||||
displayName: "Z. Beeblebrox",
|
||||
mail: "president@heartofgold.universe",
|
||||
name: "zaphod",
|
||||
password: "__dummypassword__",
|
||||
type: "xml",
|
||||
properties: {},
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/scm/api/rest/v2/users/zaphod"
|
||||
},
|
||||
delete: {
|
||||
href: "http://localhost:8081/scm/api/rest/v2/users/zaphod"
|
||||
},
|
||||
update: {
|
||||
href: "http://localhost:8081/scm/api/rest/v2/users/zaphod"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const userFord = {
|
||||
active: true,
|
||||
admin: false,
|
||||
creationDate: "2018-07-06T13:21:18.459Z",
|
||||
displayName: "F. Prefect",
|
||||
mail: "ford@prefect.universe",
|
||||
name: "ford",
|
||||
password: "__dummypassword__",
|
||||
type: "xml",
|
||||
properties: {},
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/scm/api/rest/v2/users/ford"
|
||||
},
|
||||
delete: {
|
||||
href: "http://localhost:8081/scm/api/rest/v2/users/ford"
|
||||
},
|
||||
update: {
|
||||
href: "http://localhost:8081/scm/api/rest/v2/users/ford"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const responseBodyZaphod = {
|
||||
page: 0,
|
||||
pageTotal: 1,
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:3000/scm/api/rest/v2/users/?page=0&pageSize=10"
|
||||
},
|
||||
first: {
|
||||
href: "http://localhost:3000/scm/api/rest/v2/users/?page=0&pageSize=10"
|
||||
},
|
||||
last: {
|
||||
href: "http://localhost:3000/scm/api/rest/v2/users/?page=0&pageSize=10"
|
||||
},
|
||||
create: {
|
||||
href: "http://localhost:3000/scm/api/rest/v2/users/"
|
||||
}
|
||||
},
|
||||
_embedded: {
|
||||
users: [userZaphod]
|
||||
}
|
||||
};
|
||||
|
||||
const responseBody = {
|
||||
page: 0,
|
||||
pageTotal: 1,
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:3000/scm/api/rest/v2/users/?page=0&pageSize=10"
|
||||
},
|
||||
first: {
|
||||
href: "http://localhost:3000/scm/api/rest/v2/users/?page=0&pageSize=10"
|
||||
},
|
||||
last: {
|
||||
href: "http://localhost:3000/scm/api/rest/v2/users/?page=0&pageSize=10"
|
||||
},
|
||||
create: {
|
||||
href: "http://localhost:3000/scm/api/rest/v2/users/"
|
||||
}
|
||||
},
|
||||
_embedded: {
|
||||
users: [userZaphod, userFord]
|
||||
}
|
||||
};
|
||||
|
||||
const response = {
|
||||
headers: { "content-type": "application/json" },
|
||||
responseBody
|
||||
};
|
||||
|
||||
describe("fetch tests", () => {
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
test("successful users fetch", () => {
|
||||
fetchMock.getOnce("/scm/api/rest/v2/users", response);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_USERS },
|
||||
{
|
||||
type: FETCH_USERS_SUCCESS,
|
||||
payload: response
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(fetchUsers()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
test("me fetch failed", () => {
|
||||
fetchMock.getOnce("/scm/api/rest/v2/users", {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchUsers()).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_USERS);
|
||||
expect(actions[1].type).toEqual(FETCH_USERS_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("reducer tests", () => {
|
||||
test("users request", () => {
|
||||
var newState = reducer({}, { type: FETCH_USERS });
|
||||
expect(newState.loading).toBeTruthy();
|
||||
expect(newState.error).toBeNull();
|
||||
});
|
||||
|
||||
test("fetch users successful", () => {
|
||||
var newState = reducer(
|
||||
{},
|
||||
{ type: FETCH_USERS_SUCCESS, payload: responseBody }
|
||||
);
|
||||
|
||||
expect(newState.users).toEqual({
|
||||
entries: ["zaphod", "ford"],
|
||||
error: null,
|
||||
loading: false
|
||||
});
|
||||
|
||||
expect(newState.usersByNames).toEqual({
|
||||
zaphod: {
|
||||
entry: userZaphod
|
||||
},
|
||||
ford: {
|
||||
entry: userFord
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("reducer does not replace whole usersByNames map", () => {
|
||||
const oldState = {
|
||||
usersByNames: {
|
||||
ford: {
|
||||
entry: userFord
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const newState = reducer(oldState, {
|
||||
type: FETCH_USERS_SUCCESS,
|
||||
payload: responseBodyZaphod
|
||||
});
|
||||
expect(newState.usersByNames["zaphod"]).toBeDefined();
|
||||
expect(newState.usersByNames["ford"]).toBeDefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user