Bootstrapped validation for groups

This commit is contained in:
Philipp Czora
2018-08-01 13:40:54 +02:00
parent d7292dc60c
commit bc10ce587d
6 changed files with 77 additions and 29 deletions

View File

@@ -5,14 +5,17 @@ import InputField from "../../components/forms/InputField";
import { SubmitButton } from "../../components/buttons"; import { SubmitButton } from "../../components/buttons";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import type { Group } from "../types/Group"; import type { Group } from "../types/Group";
import * as validator from "./groupValidation"
export interface Props { type Props = {
t: string => string; t: string => string,
submitForm: Group => void; submitForm: Group => void
} }
export interface State { type State = {
group: Group; group: Group,
nameValidationError: boolean,
descriptionValidationError: boolean
} }
class GroupForm extends React.Component<Props, State> { class GroupForm extends React.Component<Props, State> {
@@ -28,22 +31,28 @@ class GroupForm extends React.Component<Props, State> {
_links: {}, _links: {},
members: [], members: [],
type: "", type: "",
} },
nameValidationError: false,
descriptionValidationError: false
}; };
} }
onSubmit = (event: Event) => { onSubmit = (event: Event) => {
event.preventDefault(); event.preventDefault();
this.props.submitForm(this.state.group); this.props.submitForm(this.state.group);
}; };
isValid = () => { isValid = () => {
return true; const group = this.state.group;
return !(this.state.nameValidationError || this.state.descriptionValidationError || group.name);
} }
submit = (event: Event) => { submit = (event: Event) => {
event.preventDefault(); event.preventDefault();
if (this.isValid) {
this.props.submitForm(this.state.group) this.props.submitForm(this.state.group)
} }
}
render() { render() {
const { t } = this.props; const { t } = this.props;
@@ -51,15 +60,15 @@ class GroupForm extends React.Component<Props, State> {
<form onSubmit={this.onSubmit}> <form onSubmit={this.onSubmit}>
<InputField <InputField
label={t("group.name")} label={t("group.name")}
errorMessage="" errorMessage="group name invalid"
onChange={this.handleGroupNameChange} onChange={this.handleGroupNameChange}
validationError={false} validationError={this.state.nameValidationError}
/> />
<InputField <InputField
label={t("group.description")} label={t("group.description")}
errorMessage="" errorMessage=""
onChange={this.handleDescriptionChange} onChange={this.handleDescriptionChange}
validationError={false} validationError={this.state.descriptionValidationError}
/> />
<SubmitButton label={t("group-form.submit")} /> <SubmitButton label={t("group-form.submit")} />
</form> </form>
@@ -68,19 +77,14 @@ class GroupForm extends React.Component<Props, State> {
handleGroupNameChange = (name: string) => { handleGroupNameChange = (name: string) => {
this.setState({ this.setState({
group: { nameValidationError: !validator.isNameValid(name),
...this.state.group, group: {...this.state.group, name}
name
}
}); });
}; };
handleDescriptionChange = (description: string) => { handleDescriptionChange = (description: string) => {
this.setState({ this.setState({
group: { group: {...this.state.group, description }
...this.state.group,
description
}
}); });
}; };
} }

View File

@@ -0,0 +1,10 @@
// @flow
//TODO: How should a group be validated
//TODO: Tests!
const nameRegex = /^([A-z0-9.\-_@]|[^ ]([A-z0-9.\-_@ ]*[A-z0-9.\-_@]|[^\s])?)$/;
export const isNameValid = (name: string) => {
return nameRegex.test(name);
};

View File

@@ -3,17 +3,19 @@ import React from "react";
import Page from "../../components/layout/Page"; import Page from "../../components/layout/Page";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import GroupForm from "./GroupForm"; import GroupForm from "../components/GroupForm";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createGroup } from "../modules/groups"; import { createGroup } from "../modules/groups";
import type { Group } from "../types/Group"; import type { Group } from "../types/Group";
import type { History } from "history";
export interface Props { type Props = {
t: string => string; t: string => string,
createGroup: Group => void; createGroup: (group: Group, callback?: () => void) => void,
history: History
} }
export interface State {} type State = {}
class AddGroup extends React.Component<Props, State> { class AddGroup extends React.Component<Props, State> {
render() { render() {
@@ -27,14 +29,18 @@ class AddGroup extends React.Component<Props, State> {
); );
} }
groupCreated = () => {
console.log("pushing history")
this.props.history.push("/groups")
}
createGroup = (group: Group) => { createGroup = (group: Group) => {
this.props.createGroup(group); this.props.createGroup(group, this.groupCreated)
}; };
} }
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
createGroup: (group: Group) => dispatch(createGroup(group)) createGroup: (group: Group, callback?: () => void) => dispatch(createGroup(group, callback))
}; };
}; };

View File

@@ -133,12 +133,16 @@ export function fetchGroupFailure(name: string, error: Error): Action {
} }
//create group //create group
export function createGroup(group: Group) { export function createGroup(group: Group, callback?: () => void) {
return function(dispatch: Dispatch) { return function(dispatch: Dispatch) {
dispatch(createGroupPending()); dispatch(createGroupPending());
return apiClient return apiClient
.postWithContentType(GROUPS_URL, group, CONTENT_TYPE_GROUP) .postWithContentType(GROUPS_URL, group, CONTENT_TYPE_GROUP)
.then(() => dispatch(createGroupSuccess())) .then(() => {
dispatch(createGroupSuccess())
if (callback) {
callback();
}})
.catch(error => { .catch(error => {
dispatch( dispatch(
createGroupFailure( createGroupFailure(
@@ -168,6 +172,11 @@ export function createGroupFailure(error: Error) {
}; };
} }
export function createGroupReset() {
return {
type: CREATE_GROUP_RESET
}
}
//delete group //delete group
export function deleteGroup(group: Group, callback?: () => void) { export function deleteGroup(group: Group, callback?: () => void) {

View File

@@ -205,6 +205,25 @@ describe("groups fetch()", () => {
}); });
}); });
it("should call the callback after creating group", () => {
fetchMock.postOnce(GROUPS_URL, {
status: 201
});
let called = false;
const callMe = () => {
called = true;
}
const store = mockStore({});
return store.dispatch(createGroup(humanGroup, callMe)).then(() => {
const actions = store.getActions();
expect(actions[0].type).toEqual(CREATE_GROUP_PENDING);
expect(actions[1].type).toEqual(CREATE_GROUP_SUCCESS);
expect(called).toEqual(true);
});
});
it("should fail creating group on HTTP 500", () => { it("should fail creating group on HTTP 500", () => {
fetchMock.postOnce(GROUPS_URL, { fetchMock.postOnce(GROUPS_URL, {
status: 500 status: 500

View File

@@ -1,7 +1,7 @@
//@flow //@flow
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import UserForm from "./../components/UserForm"; import UserForm from "../components/UserForm";
import type { User } from "../types/User"; import type { User } from "../types/User";
import type { History } from "history"; import type { History } from "history";
import { import {