mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 22:45:45 +01:00
implemented repository create form
This commit is contained in:
@@ -1,10 +1,28 @@
|
|||||||
{
|
{
|
||||||
|
"repository": {
|
||||||
|
"name": "Name",
|
||||||
|
"type": "Type",
|
||||||
|
"contact": "Contact",
|
||||||
|
"description": "Description"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"name-invalid": "The repository name is invalid",
|
||||||
|
"contact-invalid": "Contact must be a valid mail address"
|
||||||
|
},
|
||||||
"overview": {
|
"overview": {
|
||||||
"title": "Repositories",
|
"title": "Repositories",
|
||||||
"subtitle": "Overview of available repositories"
|
"subtitle": "Overview of available repositories",
|
||||||
|
"create-button": "Create"
|
||||||
},
|
},
|
||||||
"repository-root": {
|
"repository-root": {
|
||||||
"error-title": "Error",
|
"error-title": "Error",
|
||||||
"error-subtitle": "Unknown repository error"
|
"error-subtitle": "Unknown repository error"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "Create Repository",
|
||||||
|
"subtitle": "Create a new repository"
|
||||||
|
},
|
||||||
|
"repository-form": {
|
||||||
|
"submit": "Speichern"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
//@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;
|
|
||||||
@@ -10,7 +10,8 @@ export type ButtonProps = {
|
|||||||
action?: () => void,
|
action?: () => void,
|
||||||
link?: string,
|
link?: string,
|
||||||
fullWidth?: boolean,
|
fullWidth?: boolean,
|
||||||
className?: string
|
className?: string,
|
||||||
|
classes: any
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = ButtonProps & {
|
type Props = ButtonProps & {
|
||||||
|
|||||||
24
scm-ui/src/components/buttons/CreateButton.js
Normal file
24
scm-ui/src/components/buttons/CreateButton.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import Button, { type ButtonProps } from "./Button";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
spacing: {
|
||||||
|
margin: "1em 0 0 1em"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CreateButton extends React.Component<ButtonProps> {
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={classNames("is-pulled-right", classes.spacing)}>
|
||||||
|
<Button type="default" {...this.props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectSheet(styles)(CreateButton);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export { default as AddButton } from "./AddButton";
|
export { default as CreateButton } from "./CreateButton";
|
||||||
export { default as Button } from "./Button";
|
export { default as Button } from "./Button";
|
||||||
export { default as DeleteButton } from "./DeleteButton";
|
export { default as DeleteButton } from "./DeleteButton";
|
||||||
export { default as EditButton } from "./EditButton";
|
export { default as EditButton } from "./EditButton";
|
||||||
|
|||||||
59
scm-ui/src/components/forms/Select.js
Normal file
59
scm-ui/src/components/forms/Select.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export type SelectItem = {
|
||||||
|
value: string,
|
||||||
|
label: string
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label?: string,
|
||||||
|
options: SelectItem[],
|
||||||
|
value?: SelectItem,
|
||||||
|
onChange: string => void
|
||||||
|
};
|
||||||
|
|
||||||
|
class Select extends React.Component<Props> {
|
||||||
|
field: ?HTMLSelectElement;
|
||||||
|
|
||||||
|
handleInput = (event: SyntheticInputEvent<HTMLSelectElement>) => {
|
||||||
|
this.props.onChange(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderLabel = () => {
|
||||||
|
const label = this.props.label;
|
||||||
|
if (label) {
|
||||||
|
return <label className="label">{label}</label>;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { options, value } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="field">
|
||||||
|
{this.renderLabel()}
|
||||||
|
<div className="control select">
|
||||||
|
<select
|
||||||
|
ref={input => {
|
||||||
|
this.field = input;
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
onChange={this.handleInput}
|
||||||
|
>
|
||||||
|
{options.map(opt => {
|
||||||
|
return (
|
||||||
|
<option value={opt.value} key={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Select;
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export { default as Checkbox } from "./Checkbox";
|
export { default as Checkbox } from "./Checkbox";
|
||||||
export { default as InputField } from "./InputField";
|
export { default as InputField } from "./InputField";
|
||||||
|
export { default as Select } from "./Select";
|
||||||
|
|||||||
12
scm-ui/src/components/validation.js
Normal file
12
scm-ui/src/components/validation.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// @flow
|
||||||
|
const nameRegex = /^([A-z0-9.\-_@]|[^ ]([A-z0-9.\-_@ ]*[A-z0-9.\-_@]|[^\s])?)$/;
|
||||||
|
|
||||||
|
export const isNameValid = (name: string) => {
|
||||||
|
return nameRegex.test(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mailRegex = /^[A-z0-9][\w.-]*@[A-z0-9][\w\-.]*\.[A-z0-9][A-z0-9-]+$/;
|
||||||
|
|
||||||
|
export const isMailValid = (mail: string) => {
|
||||||
|
return mailRegex.test(mail);
|
||||||
|
};
|
||||||
75
scm-ui/src/components/validation.test.js
Normal file
75
scm-ui/src/components/validation.test.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import * as validator from "./validation";
|
||||||
|
|
||||||
|
describe("test name validation", () => {
|
||||||
|
it("should return false", () => {
|
||||||
|
// invalid names taken from ValidationUtilTest.java
|
||||||
|
const invalidNames = [
|
||||||
|
" test 123",
|
||||||
|
" test 123 ",
|
||||||
|
"test 123 ",
|
||||||
|
"test/123",
|
||||||
|
"test%123",
|
||||||
|
"test:123",
|
||||||
|
"t ",
|
||||||
|
" t",
|
||||||
|
" t ",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
for (let name of invalidNames) {
|
||||||
|
expect(validator.isNameValid(name)).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true", () => {
|
||||||
|
// valid names taken from ValidationUtilTest.java
|
||||||
|
const validNames = [
|
||||||
|
"test",
|
||||||
|
"test.git",
|
||||||
|
"Test123.git",
|
||||||
|
"Test123-git",
|
||||||
|
"Test_user-123.git",
|
||||||
|
"test@scm-manager.de",
|
||||||
|
"test 123",
|
||||||
|
"tt",
|
||||||
|
"t"
|
||||||
|
];
|
||||||
|
for (let name of validNames) {
|
||||||
|
expect(validator.isNameValid(name)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("test mail validation", () => {
|
||||||
|
it("should return false", () => {
|
||||||
|
// invalid taken from ValidationUtilTest.java
|
||||||
|
const invalid = [
|
||||||
|
"ostfalia.de",
|
||||||
|
"@ostfalia.de",
|
||||||
|
"s.sdorra@",
|
||||||
|
"s.sdorra@ostfalia",
|
||||||
|
"s.sdorra@@ostfalia.de",
|
||||||
|
"s.sdorra@ ostfalia.de",
|
||||||
|
"s.sdorra @ostfalia.de"
|
||||||
|
];
|
||||||
|
for (let mail of invalid) {
|
||||||
|
expect(validator.isMailValid(mail)).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true", () => {
|
||||||
|
// valid taken from ValidationUtilTest.java
|
||||||
|
const valid = [
|
||||||
|
"s.sdorra@ostfalia.de",
|
||||||
|
"sdorra@ostfalia.de",
|
||||||
|
"s.sdorra@hbk-bs.de",
|
||||||
|
"s.sdorra@gmail.com",
|
||||||
|
"s.sdorra@t.co",
|
||||||
|
"s.sdorra@ucla.college",
|
||||||
|
"s.sdorra@example.xn--p1ai",
|
||||||
|
"s.sdorra@scm.solutions"
|
||||||
|
];
|
||||||
|
for (let mail of valid) {
|
||||||
|
expect(validator.isMailValid(mail)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,7 +12,8 @@ import { Switch } from "react-router-dom";
|
|||||||
import ProtectedRoute from "../components/ProtectedRoute";
|
import ProtectedRoute from "../components/ProtectedRoute";
|
||||||
import AddUser from "../users/containers/AddUser";
|
import AddUser from "../users/containers/AddUser";
|
||||||
import SingleUser from "../users/containers/SingleUser";
|
import SingleUser from "../users/containers/SingleUser";
|
||||||
import RepositoryRoot from '../repos/containers/RepositoryRoot';
|
import RepositoryRoot from "../repos/containers/RepositoryRoot";
|
||||||
|
import Create from "../repos/containers/Create";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
authenticated?: boolean
|
authenticated?: boolean
|
||||||
@@ -33,6 +34,12 @@ class Main extends React.Component<Props> {
|
|||||||
component={Overview}
|
component={Overview}
|
||||||
authenticated={authenticated}
|
authenticated={authenticated}
|
||||||
/>
|
/>
|
||||||
|
<ProtectedRoute
|
||||||
|
exact
|
||||||
|
path="/repos/create"
|
||||||
|
component={Create}
|
||||||
|
authenticated={authenticated}
|
||||||
|
/>
|
||||||
<ProtectedRoute
|
<ProtectedRoute
|
||||||
exact
|
exact
|
||||||
path="/repos/:page"
|
path="/repos/:page"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { routerReducer, routerMiddleware } from "react-router-redux";
|
|||||||
|
|
||||||
import users from "./users/modules/users";
|
import users from "./users/modules/users";
|
||||||
import repos from "./repos/modules/repos";
|
import repos from "./repos/modules/repos";
|
||||||
|
import repositoryTypes from "./repos/modules/repository-types";
|
||||||
import auth from "./modules/auth";
|
import auth from "./modules/auth";
|
||||||
import pending from "./modules/pending";
|
import pending from "./modules/pending";
|
||||||
import failure from "./modules/failure";
|
import failure from "./modules/failure";
|
||||||
@@ -22,6 +23,7 @@ function createReduxStore(history: BrowserHistory) {
|
|||||||
failure,
|
failure,
|
||||||
users,
|
users,
|
||||||
repos,
|
repos,
|
||||||
|
repositoryTypes,
|
||||||
auth
|
auth
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
167
scm-ui/src/repos/components/RepositoryForm.js
Normal file
167
scm-ui/src/repos/components/RepositoryForm.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import { InputField, Select } from "../../components/forms";
|
||||||
|
import { SubmitButton } from "../../components/buttons";
|
||||||
|
import type { Repository } from "../types/Repositories";
|
||||||
|
import * as validator from "./repositoryValidation";
|
||||||
|
import type { RepositoryType } from "../types/RepositoryTypes";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
submitForm: Repository => void,
|
||||||
|
repository?: Repository,
|
||||||
|
repositoryTypes: RepositoryType[],
|
||||||
|
loading?: boolean,
|
||||||
|
t: string => string
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
repository: Repository,
|
||||||
|
nameValidationError: boolean,
|
||||||
|
contactValidationError: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
class RepositoryForm extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
repository: {
|
||||||
|
name: "",
|
||||||
|
namespace: "",
|
||||||
|
type: "",
|
||||||
|
contact: "",
|
||||||
|
description: "",
|
||||||
|
_links: {}
|
||||||
|
},
|
||||||
|
nameValidationError: false,
|
||||||
|
contactValidationError: false,
|
||||||
|
descriptionValidationError: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { repository } = this.props;
|
||||||
|
if (repository) {
|
||||||
|
this.setState({ repository: { ...repository } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isFalsy(value) {
|
||||||
|
if (!value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid = () => {
|
||||||
|
const repository = this.state.repository;
|
||||||
|
return !(
|
||||||
|
this.state.nameValidationError ||
|
||||||
|
this.state.contactValidationError ||
|
||||||
|
this.isFalsy(repository.name)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
submit = (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.isValid()) {
|
||||||
|
this.props.submitForm(this.state.repository);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
isCreateMode = () => {
|
||||||
|
return !this.props.repository;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, t } = this.props;
|
||||||
|
const repository = this.state.repository;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={this.submit}>
|
||||||
|
{this.renderCreateOnlyFields()}
|
||||||
|
<InputField
|
||||||
|
label={t("repository.contact")}
|
||||||
|
onChange={this.handleContactChange}
|
||||||
|
value={repository ? repository.contact : ""}
|
||||||
|
validationError={this.state.contactValidationError}
|
||||||
|
errorMessage={t("validation.contact-invalid")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputField
|
||||||
|
label={t("repository.description")}
|
||||||
|
onChange={this.handleDescriptionChange}
|
||||||
|
value={repository ? repository.description : ""}
|
||||||
|
/>
|
||||||
|
<SubmitButton
|
||||||
|
disabled={!this.isValid()}
|
||||||
|
loading={loading}
|
||||||
|
label={t("repository-form.submit")}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createSelectOptions(repositoryTypes: RepositoryType[]) {
|
||||||
|
return repositoryTypes.map(repositoryType => {
|
||||||
|
return {
|
||||||
|
label: repositoryType.displayName,
|
||||||
|
value: repositoryType.name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCreateOnlyFields() {
|
||||||
|
if (!this.isCreateMode()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { repositoryTypes, t } = this.props;
|
||||||
|
const repository = this.state.repository;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<InputField
|
||||||
|
label={t("repository.name")}
|
||||||
|
onChange={this.handleNameChange}
|
||||||
|
value={repository ? repository.name : ""}
|
||||||
|
validationError={this.state.nameValidationError}
|
||||||
|
errorMessage={t("validation.name-invalid")}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label={t("repository.type")}
|
||||||
|
onChange={this.handleTypeChange}
|
||||||
|
value={repository ? repository.type : ""}
|
||||||
|
options={this.createSelectOptions(repositoryTypes)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNameChange = (name: string) => {
|
||||||
|
this.setState({
|
||||||
|
nameValidationError: !validator.isNameValid(name),
|
||||||
|
repository: { ...this.state.repository, name }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTypeChange = (type: string) => {
|
||||||
|
this.setState({
|
||||||
|
repository: { ...this.state.repository, type }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleContactChange = (contact: string) => {
|
||||||
|
this.setState({
|
||||||
|
contactValidationError: !validator.isContactValid(contact),
|
||||||
|
repository: { ...this.state.repository, contact }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDescriptionChange = (description: string) => {
|
||||||
|
this.setState({
|
||||||
|
repository: { ...this.state.repository, description }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("repos")(RepositoryForm);
|
||||||
10
scm-ui/src/repos/components/repositoryValidation.js
Normal file
10
scm-ui/src/repos/components/repositoryValidation.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// @flow
|
||||||
|
import * as generalValidator from "../../components/validation";
|
||||||
|
|
||||||
|
export const isNameValid = (name: string) => {
|
||||||
|
return generalValidator.isNameValid(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isContactValid(mail: string) {
|
||||||
|
return "" === mail || generalValidator.isMailValid(mail);
|
||||||
|
}
|
||||||
31
scm-ui/src/repos/components/repositoryValidation.test.js
Normal file
31
scm-ui/src/repos/components/repositoryValidation.test.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import * as validator from "./repositoryValidation";
|
||||||
|
|
||||||
|
describe("repository name validation", () => {
|
||||||
|
// we don't need rich tests, because they are in validation.test.js
|
||||||
|
it("should validate the name", () => {
|
||||||
|
expect(validator.isNameValid("scm-manager")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail for old nested repository names", () => {
|
||||||
|
// in v2 this is not allowed
|
||||||
|
expect(validator.isNameValid("scm/manager")).toBe(false);
|
||||||
|
expect(validator.isNameValid("scm/ma/nager")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("repository contact validation", () => {
|
||||||
|
it("should allow empty contact", () => {
|
||||||
|
expect(validator.isContactValid("")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// we don't need rich tests, because they are in validation.test.js
|
||||||
|
it("should allow real mail addresses", () => {
|
||||||
|
expect(validator.isContactValid("trici.mcmillian@hitchhiker.com")).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail on invalid mail addresses", () => {
|
||||||
|
expect(validator.isContactValid("tricia")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
71
scm-ui/src/repos/containers/Create.js
Normal file
71
scm-ui/src/repos/containers/Create.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import { Page } from "../../components/layout";
|
||||||
|
import RepositoryForm from "../components/RepositoryForm";
|
||||||
|
import type { RepositoryType } from "../types/RepositoryTypes";
|
||||||
|
import {
|
||||||
|
fetchRepositoryTypesIfNeeded,
|
||||||
|
getFetchRepositoryTypesFailure,
|
||||||
|
getRepositoryTypes,
|
||||||
|
isFetchRepositoryTypesPending
|
||||||
|
} from "../modules/repository-types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
repositoryTypes: RepositoryType[],
|
||||||
|
typesLoading: boolean,
|
||||||
|
error: Error,
|
||||||
|
|
||||||
|
// dispatch functions
|
||||||
|
fetchRepositoryTypesIfNeeded: () => void,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
t: string => string
|
||||||
|
};
|
||||||
|
|
||||||
|
class Create extends React.Component<Props> {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchRepositoryTypesIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { typesLoading, repositoryTypes, error } = this.props;
|
||||||
|
|
||||||
|
const { t } = this.props;
|
||||||
|
return (
|
||||||
|
<Page
|
||||||
|
title={t("create.title")}
|
||||||
|
subtitle={t("create.subtitle")}
|
||||||
|
loading={typesLoading}
|
||||||
|
error={error}
|
||||||
|
>
|
||||||
|
<RepositoryForm repositoryTypes={repositoryTypes} />
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const repositoryTypes = getRepositoryTypes(state);
|
||||||
|
const typesLoading = isFetchRepositoryTypesPending(state);
|
||||||
|
const error = getFetchRepositoryTypesFailure(state);
|
||||||
|
return {
|
||||||
|
repositoryTypes,
|
||||||
|
typesLoading,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
fetchRepositoryTypesIfNeeded: () => {
|
||||||
|
dispatch(fetchRepositoryTypesIfNeeded());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(translate("repos")(Create));
|
||||||
@@ -4,13 +4,21 @@ import React from "react";
|
|||||||
import type { RepositoryCollection } from "../types/Repositories";
|
import type { RepositoryCollection } from "../types/Repositories";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {fetchRepos, fetchReposByLink, fetchReposByPage, getFetchReposFailure, getRepositoryCollection, isFetchReposPending} from "../modules/repos";
|
import {
|
||||||
|
fetchRepos,
|
||||||
|
fetchReposByLink,
|
||||||
|
fetchReposByPage,
|
||||||
|
getFetchReposFailure,
|
||||||
|
getRepositoryCollection,
|
||||||
|
isFetchReposPending
|
||||||
|
} from "../modules/repos";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { Page } from "../../components/layout";
|
import { Page } from "../../components/layout";
|
||||||
import RepositoryList from "../components/RepositoryList";
|
import RepositoryList from "../components/RepositoryList";
|
||||||
import Paginator from '../../components/Paginator';
|
import Paginator from "../../components/Paginator";
|
||||||
import {withRouter} from 'react-router-dom';
|
import { withRouter } from "react-router-dom";
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
|
import CreateButton from "../../components/buttons/CreateButton";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
page: number,
|
page: number,
|
||||||
@@ -44,24 +52,33 @@ class Overview extends React.Component<Props> {
|
|||||||
this.props.history.push(`/repos/${statePage}`);
|
this.props.history.push(`/repos/${statePage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { error, loading, t } = this.props;
|
const { error, loading, t } = this.props;
|
||||||
return (
|
return (
|
||||||
<Page title={t("overview.title")} subtitle={t("overview.subtitle")} loading={loading} error={error}>
|
<Page
|
||||||
|
title={t("overview.title")}
|
||||||
|
subtitle={t("overview.subtitle")}
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
>
|
||||||
{this.renderList()}
|
{this.renderList()}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList() {
|
renderList() {
|
||||||
const { collection, fetchReposByLink } = this.props;
|
const { collection, fetchReposByLink, t } = this.props;
|
||||||
if (collection) {
|
if (collection) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<RepositoryList repositories={collection._embedded.repositories} />
|
<RepositoryList repositories={collection._embedded.repositories} />
|
||||||
<Paginator collection={collection} onPageChange={fetchReposByLink} />
|
<Paginator collection={collection} onPageChange={fetchReposByLink} />
|
||||||
|
<CreateButton
|
||||||
|
label={t("overview.create-button")}
|
||||||
|
link="/repos/create"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -98,10 +115,10 @@ const mapDispatchToProps = dispatch => {
|
|||||||
dispatch(fetchRepos());
|
dispatch(fetchRepos());
|
||||||
},
|
},
|
||||||
fetchReposByPage: (page: number) => {
|
fetchReposByPage: (page: number) => {
|
||||||
dispatch(fetchReposByPage(page))
|
dispatch(fetchReposByPage(page));
|
||||||
},
|
},
|
||||||
fetchReposByLink: (link: string) => {
|
fetchReposByLink: (link: string) => {
|
||||||
dispatch(fetchReposByLink(link))
|
dispatch(fetchReposByLink(link));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
107
scm-ui/src/repos/modules/repository-types.js
Normal file
107
scm-ui/src/repos/modules/repository-types.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import * as types from "../../modules/types";
|
||||||
|
import type { Action } from "../../types/Action";
|
||||||
|
import type {
|
||||||
|
RepositoryType,
|
||||||
|
RepositoryTypeCollection
|
||||||
|
} from "../types/RepositoryTypes";
|
||||||
|
import { apiClient } from "../../apiclient";
|
||||||
|
import { isPending } from "../../modules/pending";
|
||||||
|
import { getFailure } from "../../modules/failure";
|
||||||
|
|
||||||
|
export const FETCH_REPOSITORY_TYPES = "scm/repos/FETCH_REPOSITORY_TYPES";
|
||||||
|
export const FETCH_REPOSITORY_TYPES_PENDING = `${FETCH_REPOSITORY_TYPES}_${
|
||||||
|
types.PENDING_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const FETCH_REPOSITORY_TYPES_SUCCESS = `${FETCH_REPOSITORY_TYPES}_${
|
||||||
|
types.SUCCESS_SUFFIX
|
||||||
|
}`;
|
||||||
|
export const FETCH_REPOSITORY_TYPES_FAILURE = `${FETCH_REPOSITORY_TYPES}_${
|
||||||
|
types.FAILURE_SUFFIX
|
||||||
|
}`;
|
||||||
|
|
||||||
|
export function fetchRepositoryTypesIfNeeded() {
|
||||||
|
return function(dispatch: any, getState: () => Object) {
|
||||||
|
if (shouldFetchRepositoryTypes(getState())) {
|
||||||
|
return fetchRepositoryTypes(dispatch);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchRepositoryTypes(dispatch: any) {
|
||||||
|
dispatch(fetchRepositoryTypesPending());
|
||||||
|
return apiClient
|
||||||
|
.get("repository-types")
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(repositoryTypes => {
|
||||||
|
dispatch(fetchRepositoryTypesSuccess(repositoryTypes));
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
dispatch(fetchRepositoryTypesFailure(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldFetchRepositoryTypes(state: Object) {
|
||||||
|
if (
|
||||||
|
isFetchRepositoryTypesPending(state) ||
|
||||||
|
getFetchRepositoryTypesFailure(state)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (state.repositoryTypes && state.repositoryTypes.length > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchRepositoryTypesPending(): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_REPOSITORY_TYPES_PENDING
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchRepositoryTypesSuccess(
|
||||||
|
repositoryTypes: RepositoryTypeCollection
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_REPOSITORY_TYPES_SUCCESS,
|
||||||
|
payload: repositoryTypes
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchRepositoryTypesFailure(error: Error): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_REPOSITORY_TYPES_FAILURE,
|
||||||
|
payload: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// reducers
|
||||||
|
|
||||||
|
export default function reducer(
|
||||||
|
state: RepositoryType[] = [],
|
||||||
|
action: Action = { type: "UNKNOWN" }
|
||||||
|
): RepositoryType[] {
|
||||||
|
if (action.type === FETCH_REPOSITORY_TYPES_SUCCESS && action.payload) {
|
||||||
|
return action.payload._embedded["repository-types"];
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectors
|
||||||
|
|
||||||
|
export function getRepositoryTypes(state: Object) {
|
||||||
|
if (state.repositoryTypes) {
|
||||||
|
return state.repositoryTypes;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFetchRepositoryTypesPending(state: Object) {
|
||||||
|
return isPending(state, FETCH_REPOSITORY_TYPES);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFetchRepositoryTypesFailure(state: Object) {
|
||||||
|
return getFailure(state, FETCH_REPOSITORY_TYPES);
|
||||||
|
}
|
||||||
198
scm-ui/src/repos/modules/repository-types.test.js
Normal file
198
scm-ui/src/repos/modules/repository-types.test.js
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import fetchMock from "fetch-mock";
|
||||||
|
import configureMockStore from "redux-mock-store";
|
||||||
|
import thunk from "redux-thunk";
|
||||||
|
import {
|
||||||
|
FETCH_REPOSITORY_TYPES,
|
||||||
|
FETCH_REPOSITORY_TYPES_FAILURE,
|
||||||
|
FETCH_REPOSITORY_TYPES_PENDING,
|
||||||
|
FETCH_REPOSITORY_TYPES_SUCCESS,
|
||||||
|
fetchRepositoryTypesIfNeeded,
|
||||||
|
fetchRepositoryTypesSuccess,
|
||||||
|
getFetchRepositoryTypesFailure,
|
||||||
|
getRepositoryTypes,
|
||||||
|
isFetchRepositoryTypesPending,
|
||||||
|
shouldFetchRepositoryTypes
|
||||||
|
} from "./repository-types";
|
||||||
|
import reducer from "./repository-types";
|
||||||
|
|
||||||
|
const git = {
|
||||||
|
name: "git",
|
||||||
|
displayName: "Git",
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: "http://localhost:8081/scm/api/rest/v2/repository-types/git"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hg = {
|
||||||
|
name: "hg",
|
||||||
|
displayName: "Mercurial",
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: "http://localhost:8081/scm/api/rest/v2/repository-types/hg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const svn = {
|
||||||
|
name: "svn",
|
||||||
|
displayName: "Subversion",
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: "http://localhost:8081/scm/api/rest/v2/repository-types/svn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const collection = {
|
||||||
|
_embedded: {
|
||||||
|
"repository-types": [git, hg, svn]
|
||||||
|
},
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: "http://localhost:8081/scm/api/rest/v2/repository-types"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("repository types caching", () => {
|
||||||
|
it("should fetch repository types, on empty state", () => {
|
||||||
|
expect(shouldFetchRepositoryTypes({})).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch repository types, if the state contains an empty array", () => {
|
||||||
|
const state = {
|
||||||
|
repositoryTypes: []
|
||||||
|
};
|
||||||
|
expect(shouldFetchRepositoryTypes(state)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not fetch repository types, on pending state", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[FETCH_REPOSITORY_TYPES]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(shouldFetchRepositoryTypes(state)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not fetch repository types, on failure state", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[FETCH_REPOSITORY_TYPES]: new Error("no...")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(shouldFetchRepositoryTypes(state)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not fetch repository types, if they are already fetched", () => {
|
||||||
|
const state = {
|
||||||
|
repositoryTypes: [git, hg, svn]
|
||||||
|
};
|
||||||
|
expect(shouldFetchRepositoryTypes(state)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("repository types fetch", () => {
|
||||||
|
const URL = "/scm/api/rest/v2/repository-types";
|
||||||
|
const mockStore = configureMockStore([thunk]);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.reset();
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should successfully fetch repository types", () => {
|
||||||
|
fetchMock.getOnce(URL, collection);
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: FETCH_REPOSITORY_TYPES_PENDING },
|
||||||
|
{
|
||||||
|
type: FETCH_REPOSITORY_TYPES_SUCCESS,
|
||||||
|
payload: collection
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(fetchRepositoryTypesIfNeeded()).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch FETCH_REPOSITORY_TYPES_FAILURE on server error", () => {
|
||||||
|
fetchMock.getOnce(URL, {
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(fetchRepositoryTypesIfNeeded()).then(() => {
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions[0].type).toBe(FETCH_REPOSITORY_TYPES_PENDING);
|
||||||
|
expect(actions[1].type).toBe(FETCH_REPOSITORY_TYPES_FAILURE);
|
||||||
|
expect(actions[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch not dispatch any action, if the repository types are already fetched", () => {
|
||||||
|
const store = mockStore({
|
||||||
|
repositoryTypes: [git, hg, svn]
|
||||||
|
});
|
||||||
|
store.dispatch(fetchRepositoryTypesIfNeeded());
|
||||||
|
expect(store.getActions().length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("repository types reducer", () => {
|
||||||
|
it("should return unmodified state on unknown action", () => {
|
||||||
|
const state = [];
|
||||||
|
expect(reducer(state)).toBe(state);
|
||||||
|
});
|
||||||
|
it("should store the repository types on FETCH_REPOSITORY_TYPES_SUCCESS", () => {
|
||||||
|
const newState = reducer([], fetchRepositoryTypesSuccess(collection));
|
||||||
|
expect(newState).toEqual([git, hg, svn]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("repository types selectors", () => {
|
||||||
|
const error = new Error("The end of the universe");
|
||||||
|
|
||||||
|
it("should return an emtpy array", () => {
|
||||||
|
expect(getRepositoryTypes({})).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the repository types", () => {
|
||||||
|
const state = {
|
||||||
|
repositoryTypes: [git, hg, svn]
|
||||||
|
};
|
||||||
|
expect(getRepositoryTypes(state)).toEqual([git, hg, svn]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true, when fetch repository types is pending", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[FETCH_REPOSITORY_TYPES]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(isFetchRepositoryTypesPending(state)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false, when fetch repos is not pending", () => {
|
||||||
|
expect(isFetchRepositoryTypesPending({})).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error when fetch repository types did fail", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[FETCH_REPOSITORY_TYPES]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(getFetchRepositoryTypesFailure(state)).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined when fetch repos did not fail", () => {
|
||||||
|
expect(getFetchRepositoryTypesFailure({})).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
14
scm-ui/src/repos/types/RepositoryTypes.js
Normal file
14
scm-ui/src/repos/types/RepositoryTypes.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import type { Collection } from "../../types/Collection";
|
||||||
|
|
||||||
|
export type RepositoryType = {
|
||||||
|
name: string,
|
||||||
|
displayName: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RepositoryTypeCollection = Collection & {
|
||||||
|
_embedded: {
|
||||||
|
"repository-types": RepositoryType[]
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -4,7 +4,8 @@ import { translate } from "react-i18next";
|
|||||||
import type { User } from "../types/User";
|
import type { User } from "../types/User";
|
||||||
import { Checkbox, InputField } from "../../components/forms";
|
import { Checkbox, InputField } from "../../components/forms";
|
||||||
import { SubmitButton } from "../../components/buttons";
|
import { SubmitButton } from "../../components/buttons";
|
||||||
import * as validator from "./userValidation";
|
import * as validator from "../../components/validation";
|
||||||
|
import * as userValidator from "./userValidation";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
submitForm: User => void,
|
submitForm: User => void,
|
||||||
@@ -157,7 +158,9 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
|
|
||||||
handleDisplayNameChange = (displayName: string) => {
|
handleDisplayNameChange = (displayName: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
displayNameValidationError: !validator.isDisplayNameValid(displayName),
|
displayNameValidationError: !userValidator.isDisplayNameValid(
|
||||||
|
displayName
|
||||||
|
),
|
||||||
user: { ...this.state.user, displayName }
|
user: { ...this.state.user, displayName }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -175,7 +178,7 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
this.state.validatePassword
|
this.state.validatePassword
|
||||||
);
|
);
|
||||||
this.setState({
|
this.setState({
|
||||||
validatePasswordError: !validator.isPasswordValid(password),
|
validatePasswordError: !userValidator.isPasswordValid(password),
|
||||||
passwordValidationError: validatePasswordError,
|
passwordValidationError: validatePasswordError,
|
||||||
user: { ...this.state.user, password }
|
user: { ...this.state.user, password }
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,30 +1,20 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import injectSheet from "react-jss";
|
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { AddButton } from "../../../components/buttons";
|
import { CreateButton } from "../../../components/buttons";
|
||||||
import classNames from "classnames";
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
spacing: {
|
|
||||||
margin: "1em 0 0 1em"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// TODO remove
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string
|
||||||
classes: any
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CreateUserButton extends React.Component<Props> {
|
class CreateUserButton extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { classes, t } = this.props;
|
const { t } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={classNames("is-pulled-right", classes.spacing)}>
|
<CreateButton label={t("create-user-button.label")} link="/users/add" />
|
||||||
<AddButton label={t("create-user-button.label")} link="/users/add" />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("users")(injectSheet(styles)(CreateUserButton));
|
export default translate("users")(CreateUserButton);
|
||||||
|
|||||||
@@ -1,24 +1,11 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
const nameRegex = /^([A-z0-9.\-_@]|[^ ]([A-z0-9.\-_@ ]*[A-z0-9.\-_@]|[^\s])?)$/;
|
|
||||||
|
|
||||||
export const isNameValid = (name: string) => {
|
|
||||||
return nameRegex.test(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isDisplayNameValid = (displayName: string) => {
|
export const isDisplayNameValid = (displayName: string) => {
|
||||||
if (displayName) {
|
if (displayName) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mailRegex = /^[A-z0-9][\w.-]*@[A-z0-9][\w\-.]*\.[A-z0-9][A-z0-9-]+$/;
|
|
||||||
|
|
||||||
export const isMailValid = (mail: string) => {
|
|
||||||
return mailRegex.test(mail);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isPasswordValid = (password: string) => {
|
export const isPasswordValid = (password: string) => {
|
||||||
return password.length > 6 && password.length < 32;
|
return password.length > 6 && password.length < 32;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,45 +1,6 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as validator from "./userValidation";
|
import * as validator from "./userValidation";
|
||||||
|
|
||||||
describe("test name validation", () => {
|
|
||||||
it("should return false", () => {
|
|
||||||
// invalid names taken from ValidationUtilTest.java
|
|
||||||
const invalidNames = [
|
|
||||||
" test 123",
|
|
||||||
" test 123 ",
|
|
||||||
"test 123 ",
|
|
||||||
"test/123",
|
|
||||||
"test%123",
|
|
||||||
"test:123",
|
|
||||||
"t ",
|
|
||||||
" t",
|
|
||||||
" t ",
|
|
||||||
""
|
|
||||||
];
|
|
||||||
for (let name of invalidNames) {
|
|
||||||
expect(validator.isNameValid(name)).toBe(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return true", () => {
|
|
||||||
// valid names taken from ValidationUtilTest.java
|
|
||||||
const validNames = [
|
|
||||||
"test",
|
|
||||||
"test.git",
|
|
||||||
"Test123.git",
|
|
||||||
"Test123-git",
|
|
||||||
"Test_user-123.git",
|
|
||||||
"test@scm-manager.de",
|
|
||||||
"test 123",
|
|
||||||
"tt",
|
|
||||||
"t"
|
|
||||||
];
|
|
||||||
for (let name of validNames) {
|
|
||||||
expect(validator.isNameValid(name)).toBe(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("test displayName validation", () => {
|
describe("test displayName validation", () => {
|
||||||
it("should return false", () => {
|
it("should return false", () => {
|
||||||
expect(validator.isDisplayNameValid("")).toBe(false);
|
expect(validator.isDisplayNameValid("")).toBe(false);
|
||||||
@@ -60,41 +21,6 @@ describe("test displayName validation", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("test mail validation", () => {
|
|
||||||
it("should return false", () => {
|
|
||||||
// invalid taken from ValidationUtilTest.java
|
|
||||||
const invalid = [
|
|
||||||
"ostfalia.de",
|
|
||||||
"@ostfalia.de",
|
|
||||||
"s.sdorra@",
|
|
||||||
"s.sdorra@ostfalia",
|
|
||||||
"s.sdorra@@ostfalia.de",
|
|
||||||
"s.sdorra@ ostfalia.de",
|
|
||||||
"s.sdorra @ostfalia.de"
|
|
||||||
];
|
|
||||||
for (let mail of invalid) {
|
|
||||||
expect(validator.isMailValid(mail)).toBe(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return true", () => {
|
|
||||||
// valid taken from ValidationUtilTest.java
|
|
||||||
const valid = [
|
|
||||||
"s.sdorra@ostfalia.de",
|
|
||||||
"sdorra@ostfalia.de",
|
|
||||||
"s.sdorra@hbk-bs.de",
|
|
||||||
"s.sdorra@gmail.com",
|
|
||||||
"s.sdorra@t.co",
|
|
||||||
"s.sdorra@ucla.college",
|
|
||||||
"s.sdorra@example.xn--p1ai",
|
|
||||||
"s.sdorra@scm.solutions"
|
|
||||||
];
|
|
||||||
for (let mail of valid) {
|
|
||||||
expect(validator.isMailValid(mail)).toBe(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("test password validation", () => {
|
describe("test password validation", () => {
|
||||||
it("should return false", () => {
|
it("should return false", () => {
|
||||||
// invalid taken from ValidationUtilTest.java
|
// invalid taken from ValidationUtilTest.java
|
||||||
|
|||||||
Reference in New Issue
Block a user