use reflow to migrate from flow to typescript

This commit is contained in:
Sebastian Sdorra
2019-10-19 16:38:07 +02:00
parent f7b8050dfa
commit 6e7a08a3bb
495 changed files with 14239 additions and 13766 deletions

View File

@@ -1,28 +0,0 @@
//@flow
import React from "react";
import type { Repository } from "@scm-manager/ui-types";
import { NavLink } from "@scm-manager/ui-components";
import { translate } from "react-i18next";
type Props = {
repository: Repository,
editUrl: string,
t: string => string
};
class EditRepoNavLink extends React.Component<Props> {
isEditable = () => {
return this.props.repository._links.update;
};
render() {
const { editUrl, t } = this.props;
if (!this.isEditable()) {
return null;
}
return <NavLink to={editUrl} label={t("repositoryRoot.menu.generalNavLink")} />;
}
}
export default translate("repos")(EditRepoNavLink);

View File

@@ -1,35 +0,0 @@
import React from "react";
import { shallow, mount } from "@scm-manager/ui-tests/enzyme-router";
import "@scm-manager/ui-tests/enzyme";
import "@scm-manager/ui-tests/i18n";
import EditRepoNavLink from "./EditRepoNavLink";
describe("GeneralNavLink", () => {
it("should render nothing, if the modify link is missing", () => {
const repository = {
_links: {}
};
const navLink = shallow(
<EditRepoNavLink repository={repository} editUrl="" />
);
expect(navLink.text()).toBe("");
});
it("should render the navLink", () => {
const repository = {
_links: {
update: {
href: "/repositories"
}
}
};
const navLink = mount(
<EditRepoNavLink repository={repository} editUrl="" />
);
expect(navLink.text()).toBe("repositoryRoot.menu.generalNavLink");
});
});

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { shallow, mount } from '@scm-manager/ui-tests/enzyme-router';
import '@scm-manager/ui-tests/enzyme';
import '@scm-manager/ui-tests/i18n';
import EditRepoNavLink from './EditRepoNavLink';
describe('GeneralNavLink', () => {
it('should render nothing, if the modify link is missing', () => {
const repository = {
_links: {},
};
const navLink = shallow(
<EditRepoNavLink repository={repository} editUrl="" />,
);
expect(navLink.text()).toBe('');
});
it('should render the navLink', () => {
const repository = {
_links: {
update: {
href: '/repositories',
},
},
};
const navLink = mount(
<EditRepoNavLink repository={repository} editUrl="" />,
);
expect(navLink.text()).toBe('repositoryRoot.menu.generalNavLink');
});
});

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { Repository } from '@scm-manager/ui-types';
import { NavLink } from '@scm-manager/ui-components';
import { translate } from 'react-i18next';
type Props = {
repository: Repository;
editUrl: string;
t: (p: string) => string;
};
class EditRepoNavLink extends React.Component<Props> {
isEditable = () => {
return this.props.repository._links.update;
};
render() {
const { editUrl, t } = this.props;
if (!this.isEditable()) {
return null;
}
return (
<NavLink to={editUrl} label={t('repositoryRoot.menu.generalNavLink')} />
);
}
}
export default translate('repos')(EditRepoNavLink);

View File

@@ -1,28 +0,0 @@
//@flow
import React from "react";
import { NavLink } from "@scm-manager/ui-components";
import { translate } from "react-i18next";
import type { Repository } from "@scm-manager/ui-types";
type Props = {
permissionUrl: string,
t: string => string,
repository: Repository
};
class PermissionsNavLink extends React.Component<Props> {
hasPermissionsLink = () => {
return this.props.repository._links.permissions;
};
render() {
if (!this.hasPermissionsLink()) {
return null;
}
const { permissionUrl, t } = this.props;
return (
<NavLink to={permissionUrl} label={t("repositoryRoot.menu.permissionsNavLink")} />
);
}
}
export default translate("repos")(PermissionsNavLink);

View File

@@ -1,35 +0,0 @@
// @flow
import React from "react";
import {mount, shallow } from "@scm-manager/ui-tests/enzyme-router";
import "@scm-manager/ui-tests/enzyme";
import "@scm-manager/ui-tests/i18n";
import PermissionsNavLink from "./PermissionsNavLink";
describe("PermissionsNavLink", () => {
it("should render nothing, if the modify link is missing", () => {
const repository = {
_links: {}
};
const navLink = shallow(
<PermissionsNavLink repository={repository} permissionUrl="" />
);
expect(navLink.text()).toBe("");
});
it("should render the navLink", () => {
const repository = {
_links: {
permissions: {
href: "/permissions"
}
}
};
const navLink = mount(
<PermissionsNavLink repository={repository} permissionUrl="" />
);
expect(navLink.text()).toBe("repositoryRoot.menu.permissionsNavLink");
});
});

View File

@@ -0,0 +1,33 @@
import React from 'react';
import { mount, shallow } from '@scm-manager/ui-tests/enzyme-router';
import '@scm-manager/ui-tests/enzyme';
import '@scm-manager/ui-tests/i18n';
import PermissionsNavLink from './PermissionsNavLink';
describe('PermissionsNavLink', () => {
it('should render nothing, if the modify link is missing', () => {
const repository = {
_links: {},
};
const navLink = shallow(
<PermissionsNavLink repository={repository} permissionUrl="" />,
);
expect(navLink.text()).toBe('');
});
it('should render the navLink', () => {
const repository = {
_links: {
permissions: {
href: '/permissions',
},
},
};
const navLink = mount(
<PermissionsNavLink repository={repository} permissionUrl="" />,
);
expect(navLink.text()).toBe('repositoryRoot.menu.permissionsNavLink');
});
});

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { NavLink } from '@scm-manager/ui-components';
import { translate } from 'react-i18next';
import { Repository } from '@scm-manager/ui-types';
type Props = {
permissionUrl: string;
t: (p: string) => string;
repository: Repository;
};
class PermissionsNavLink extends React.Component<Props> {
hasPermissionsLink = () => {
return this.props.repository._links.permissions;
};
render() {
if (!this.hasPermissionsLink()) {
return null;
}
const { permissionUrl, t } = this.props;
return (
<NavLink
to={permissionUrl}
label={t('repositoryRoot.menu.permissionsNavLink')}
/>
);
}
}
export default translate('repos')(PermissionsNavLink);

View File

@@ -1,13 +1,12 @@
//@flow
import React from "react";
import type { Repository } from "@scm-manager/ui-types";
import { MailLink, DateFromNow } from "@scm-manager/ui-components";
import { translate } from "react-i18next";
import React from 'react';
import { Repository } from '@scm-manager/ui-types';
import { MailLink, DateFromNow } from '@scm-manager/ui-components';
import { translate } from 'react-i18next';
type Props = {
repository: Repository,
repository: Repository;
// context props
t: string => string
t: (p: string) => string;
};
class RepositoryDetailTable extends React.Component<Props> {
@@ -17,31 +16,31 @@ class RepositoryDetailTable extends React.Component<Props> {
<table className="table">
<tbody>
<tr>
<th>{t("repository.name")}</th>
<th>{t('repository.name')}</th>
<td>{repository.name}</td>
</tr>
<tr>
<th>{t("repository.type")}</th>
<th>{t('repository.type')}</th>
<td>{repository.type}</td>
</tr>
<tr>
<th>{t("repository.contact")}</th>
<th>{t('repository.contact')}</th>
<td>
<MailLink address={repository.contact} />
</td>
</tr>
<tr>
<th>{t("repository.description")}</th>
<th>{t('repository.description')}</th>
<td>{repository.description}</td>
</tr>
<tr>
<th>{t("repository.creationDate")}</th>
<th>{t('repository.creationDate')}</th>
<td>
<DateFromNow date={repository.creationDate} />
</td>
</tr>
<tr>
<th>{t("repository.lastModified")}</th>
<th>{t('repository.lastModified')}</th>
<td>
<DateFromNow date={repository.lastModified} />
</td>
@@ -52,4 +51,4 @@ class RepositoryDetailTable extends React.Component<Props> {
}
}
export default translate("repos")(RepositoryDetailTable);
export default translate('repos')(RepositoryDetailTable);

View File

@@ -1,11 +1,10 @@
//@flow
import React from "react";
import type { Repository } from "@scm-manager/ui-types";
import RepositoryDetailTable from "./RepositoryDetailTable";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import React from 'react';
import { Repository } from '@scm-manager/ui-types';
import RepositoryDetailTable from './RepositoryDetailTable';
import { ExtensionPoint } from '@scm-manager/ui-extensions';
type Props = {
repository: Repository
repository: Repository;
};
class RepositoryDetails extends React.Component<Props> {
@@ -13,13 +12,15 @@ class RepositoryDetails extends React.Component<Props> {
const { repository } = this.props;
return (
<div>
<RepositoryDetailTable repository={repository}/>
<hr/>
<RepositoryDetailTable repository={repository} />
<hr />
<div className="content">
<ExtensionPoint
name="repos.repository-details.information"
renderAll={true}
props={{ repository }}
props={{
repository,
}}
/>
</div>
</div>

View File

@@ -1,52 +0,0 @@
// @flow
import React from "react";
import { shallow, mount } from "@scm-manager/ui-tests/enzyme-router";
import "@scm-manager/ui-tests/i18n";
import RepositoryNavLink from "./RepositoryNavLink";
describe("RepositoryNavLink", () => {
it("should render nothing, if the sources link is missing", () => {
const repository = {
namespace: "Namespace",
name: "Repo",
type: "GIT",
_links: {}
};
const navLink = shallow(
<RepositoryNavLink
repository={repository}
linkName="sources"
to="/sources"
label="Sources"
activeOnlyWhenExact={true}
/>
);
expect(navLink.text()).toBe("");
});
it("should render the navLink", () => {
const repository = {
namespace: "Namespace",
name: "Repo",
type: "GIT",
_links: {
sources: {
href: "/sources"
}
}
};
const navLink = mount(
<RepositoryNavLink
repository={repository}
linkName="sources"
to="/sources"
label="Sources"
activeOnlyWhenExact={true}
/>
);
expect(navLink.text()).toBe("Sources");
});
});

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { shallow, mount } from '@scm-manager/ui-tests/enzyme-router';
import '@scm-manager/ui-tests/i18n';
import RepositoryNavLink from './RepositoryNavLink';
describe('RepositoryNavLink', () => {
it('should render nothing, if the sources link is missing', () => {
const repository = {
namespace: 'Namespace',
name: 'Repo',
type: 'GIT',
_links: {},
};
const navLink = shallow(
<RepositoryNavLink
repository={repository}
linkName="sources"
to="/sources"
label="Sources"
activeOnlyWhenExact={true}
/>,
);
expect(navLink.text()).toBe('');
});
it('should render the navLink', () => {
const repository = {
namespace: 'Namespace',
name: 'Repo',
type: 'GIT',
_links: {
sources: {
href: '/sources',
},
},
};
const navLink = mount(
<RepositoryNavLink
repository={repository}
linkName="sources"
to="/sources"
label="Sources"
activeOnlyWhenExact={true}
/>,
);
expect(navLink.text()).toBe('Sources');
});
});

View File

@@ -1,15 +1,14 @@
//@flow
import React from "react";
import type { Repository } from "@scm-manager/ui-types";
import { NavLink } from "@scm-manager/ui-components";
import React from 'react';
import { Repository } from '@scm-manager/ui-types';
import { NavLink } from '@scm-manager/ui-components';
type Props = {
repository: Repository,
to: string,
label: string,
linkName: string,
activeWhenMatch?: (route: any) => boolean,
activeOnlyWhenExact: boolean
repository: Repository;
to: string;
label: string;
linkName: string;
activeWhenMatch?: (route: any) => boolean;
activeOnlyWhenExact: boolean;
};
/**

View File

@@ -1,10 +1,9 @@
//@flow
import React from "react";
import { Interpolate, translate } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Changeset, Repository, Tag } from "@scm-manager/ui-types";
import React from 'react';
import { Interpolate, translate } from 'react-i18next';
import classNames from 'classnames';
import styled from 'styled-components';
import { ExtensionPoint } from '@scm-manager/ui-extensions';
import { Changeset, Repository, Tag } from '@scm-manager/ui-types';
import {
DateFromNow,
ChangesetId,
@@ -15,19 +14,19 @@ import {
AvatarImage,
changesets,
Level,
Button
} from "@scm-manager/ui-components";
Button,
} from '@scm-manager/ui-components';
type Props = {
changeset: Changeset,
repository: Repository,
changeset: Changeset;
repository: Repository;
// context props
t: string => string
t: (p: string) => string;
};
type State = {
collapsed: boolean
collapsed: boolean;
};
const RightMarginP = styled.p`
@@ -48,7 +47,7 @@ class ChangesetDetails extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
collapsed: false
collapsed: false,
};
}
@@ -64,11 +63,14 @@ class ChangesetDetails extends React.Component<Props, State> {
return (
<>
<div className={classNames("content", "is-marginless")}>
<div className={classNames('content', 'is-marginless')}>
<h4>
<ExtensionPoint
name="changeset.description"
props={{ changeset, value: description.title }}
props={{
changeset,
value: description.title,
}}
renderAll={false}
>
{description.title}
@@ -76,7 +78,7 @@ class ChangesetDetails extends React.Component<Props, State> {
</h4>
<article className="media">
<AvatarWrapper>
<RightMarginP className={classNames("image", "is-64x64")}>
<RightMarginP className={classNames('image', 'is-64x64')}>
<AvatarImage person={changeset.author} />
</RightMarginP>
</AvatarWrapper>
@@ -92,12 +94,15 @@ class ChangesetDetails extends React.Component<Props, State> {
</article>
<p>
{description.message.split("\n").map((item, key) => {
{description.message.split('\n').map((item, key) => {
return (
<span key={key}>
<ExtensionPoint
name="changeset.description"
props={{ changeset, value: item }}
props={{
changeset,
value: item,
}}
renderAll={false}
>
{item}
@@ -114,8 +119,8 @@ class ChangesetDetails extends React.Component<Props, State> {
<Button
action={this.collapseDiffs}
color="default"
icon={collapsed ? "eye" : "eye-slash"}
label={t("changesets.collapseDiffs")}
icon={collapsed ? 'eye' : 'eye-slash'}
label={t('changesets.collapseDiffs')}
reducedMobile={true}
/>
}
@@ -146,9 +151,9 @@ class ChangesetDetails extends React.Component<Props, State> {
collapseDiffs = () => {
this.setState(state => ({
collapsed: !state.collapsed
collapsed: !state.collapsed,
}));
};
}
export default translate("repos")(ChangesetDetails);
export default translate('repos')(ChangesetDetails);

View File

@@ -1,34 +1,33 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import React from 'react';
import { translate } from 'react-i18next';
import {
Subtitle,
InputField,
Select,
SubmitButton,
Textarea
} from "@scm-manager/ui-components";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Repository, RepositoryType } from "@scm-manager/ui-types";
import * as validator from "./repositoryValidation";
Textarea,
} from '@scm-manager/ui-components';
import { ExtensionPoint } from '@scm-manager/ui-extensions';
import { Repository, RepositoryType } from '@scm-manager/ui-types';
import * as validator from './repositoryValidation';
type Props = {
submitForm: Repository => void,
repository?: Repository,
repositoryTypes: RepositoryType[],
namespaceStrategy: string,
loading?: boolean,
t: string => string
submitForm: (p: Repository) => void;
repository?: Repository;
repositoryTypes: RepositoryType[];
namespaceStrategy: string;
loading?: boolean;
t: (p: string) => string;
};
type State = {
repository: Repository,
namespaceValidationError: boolean,
nameValidationError: boolean,
contactValidationError: boolean
repository: Repository;
namespaceValidationError: boolean;
nameValidationError: boolean;
contactValidationError: boolean;
};
const CUSTOM_NAMESPACE_STRATEGY = "CustomNamespaceStrategy";
const CUSTOM_NAMESPACE_STRATEGY = 'CustomNamespaceStrategy';
class RepositoryForm extends React.Component<Props, State> {
constructor(props: Props) {
@@ -36,23 +35,27 @@ class RepositoryForm extends React.Component<Props, State> {
this.state = {
repository: {
name: "",
namespace: "",
type: "",
contact: "",
description: "",
_links: {}
name: '',
namespace: '',
type: '',
contact: '',
description: '',
_links: {},
},
namespaceValidationError: false,
nameValidationError: false,
contactValidationError: false
contactValidationError: false,
};
}
componentDidMount() {
const { repository } = this.props;
if (repository) {
this.setState({ repository: { ...repository } });
this.setState({
repository: {
...repository,
},
});
}
}
@@ -101,14 +104,14 @@ class RepositoryForm extends React.Component<Props, State> {
<SubmitButton
disabled={!this.isValid()}
loading={loading}
label={t("repositoryForm.submit")}
label={t('repositoryForm.submit')}
/>
);
let subtitle = null;
if (this.props.repository) {
// edit existing repo
subtitle = <Subtitle subtitle={t("repositoryForm.subtitle")} />;
subtitle = <Subtitle subtitle={t('repositoryForm.subtitle')} />;
}
return (
@@ -117,20 +120,20 @@ class RepositoryForm extends React.Component<Props, State> {
<form onSubmit={this.submit}>
{this.renderCreateOnlyFields()}
<InputField
label={t("repository.contact")}
label={t('repository.contact')}
onChange={this.handleContactChange}
value={repository ? repository.contact : ""}
value={repository ? repository.contact : ''}
validationError={this.state.contactValidationError}
errorMessage={t("validation.contact-invalid")}
helpText={t("help.contactHelpText")}
errorMessage={t('validation.contact-invalid')}
helpText={t('help.contactHelpText')}
disabled={disabled}
/>
<Textarea
label={t("repository.description")}
label={t('repository.description')}
onChange={this.handleDescriptionChange}
value={repository ? repository.description : ""}
helpText={t("help.descriptionHelpText")}
value={repository ? repository.description : ''}
helpText={t('help.descriptionHelpText')}
disabled={disabled}
/>
{submitButton}
@@ -143,7 +146,7 @@ class RepositoryForm extends React.Component<Props, State> {
return repositoryTypes.map(repositoryType => {
return {
label: repositoryType.displayName,
value: repositoryType.name
value: repositoryType.name,
};
});
}
@@ -152,12 +155,12 @@ class RepositoryForm extends React.Component<Props, State> {
const { namespaceStrategy, t } = this.props;
const repository = this.state.repository;
const props = {
label: t("repository.namespace"),
helpText: t("help.namespaceHelpText"),
value: repository ? repository.namespace : "",
label: t('repository.namespace'),
helpText: t('help.namespaceHelpText'),
value: repository ? repository.namespace : '',
onChange: this.handleNamespaceChange,
errorMessage: t("validation.namespace-invalid"),
validationError: this.state.namespaceValidationError
errorMessage: t('validation.namespace-invalid'),
validationError: this.state.namespaceValidationError,
};
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY) {
@@ -183,19 +186,19 @@ class RepositoryForm extends React.Component<Props, State> {
<>
{this.renderNamespaceField()}
<InputField
label={t("repository.name")}
label={t('repository.name')}
onChange={this.handleNameChange}
value={repository ? repository.name : ""}
value={repository ? repository.name : ''}
validationError={this.state.nameValidationError}
errorMessage={t("validation.name-invalid")}
helpText={t("help.nameHelpText")}
errorMessage={t('validation.name-invalid')}
helpText={t('help.nameHelpText')}
/>
<Select
label={t("repository.type")}
label={t('repository.type')}
onChange={this.handleTypeChange}
value={repository ? repository.type : ""}
value={repository ? repository.type : ''}
options={this.createSelectOptions(repositoryTypes)}
helpText={t("help.typeHelpText")}
helpText={t('help.typeHelpText')}
/>
</>
);
@@ -204,35 +207,50 @@ class RepositoryForm extends React.Component<Props, State> {
handleNamespaceChange = (namespace: string) => {
this.setState({
namespaceValidationError: !validator.isNameValid(namespace),
repository: { ...this.state.repository, namespace }
repository: {
...this.state.repository,
namespace,
},
});
};
handleNameChange = (name: string) => {
this.setState({
nameValidationError: !validator.isNameValid(name),
repository: { ...this.state.repository, name }
repository: {
...this.state.repository,
name,
},
});
};
handleTypeChange = (type: string) => {
this.setState({
repository: { ...this.state.repository, type }
repository: {
...this.state.repository,
type,
},
});
};
handleContactChange = (contact: string) => {
this.setState({
contactValidationError: !validator.isContactValid(contact),
repository: { ...this.state.repository, contact }
repository: {
...this.state.repository,
contact,
},
});
};
handleDescriptionChange = (description: string) => {
this.setState({
repository: { ...this.state.repository, description }
repository: {
...this.state.repository,
description,
},
});
};
}
export default translate("repos")(RepositoryForm);
export default translate('repos')(RepositoryForm);

View File

@@ -1,2 +0,0 @@
import RepositoryForm from "./RepositoryForm";
export default RepositoryForm;

View File

@@ -0,0 +1,2 @@
import RepositoryForm from './RepositoryForm';
export default RepositoryForm;

View File

@@ -1,106 +0,0 @@
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);
});
it("should allow same names as the backend", () => {
const validPaths = [
"scm",
"s",
"sc",
".hiddenrepo",
"b.",
"...",
"..c",
"d..",
"a..c"
];
validPaths.forEach((path) =>
expect(validator.isNameValid(path)).toBe(true)
);
});
it("should deny same names as the backend", () => {
const invalidPaths = [
".",
"/",
"//",
"..",
"/.",
"/..",
"./",
"../",
"/../",
"/./",
"/...",
"/abc",
".../",
"/sdf/",
"asdf/",
"./b",
"scm/plugins/.",
"scm/../plugins",
"scm/main/",
"/scm/main/",
"scm/./main",
"scm//main",
"scm\\main",
"scm/main-$HOME",
"scm/main-${HOME}-home",
"scm/main-%HOME-home",
"scm/main-%HOME%-home",
"abc$abc",
"abc%abc",
"abc<abc",
"abc>abc",
"abc#abc",
"abc+abc",
"abc{abc",
"abc}abc",
"abc(abc",
"abc)abc",
"abc[abc",
"abc]abc",
"abc|abc",
"scm/main",
"scm/plugins/git-plugin",
".scm/plugins",
"a/b..",
"a/..b",
"scm/main",
"scm/plugins/git-plugin",
"scm/plugins/git-plugin"
];
invalidPaths.forEach((path) =>
expect(validator.isNameValid(path)).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);
});
});

View File

@@ -0,0 +1,104 @@
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);
});
it('should allow same names as the backend', () => {
const validPaths = [
'scm',
's',
'sc',
'.hiddenrepo',
'b.',
'...',
'..c',
'd..',
'a..c',
];
validPaths.forEach(path => expect(validator.isNameValid(path)).toBe(true));
});
it('should deny same names as the backend', () => {
const invalidPaths = [
'.',
'/',
'//',
'..',
'/.',
'/..',
'./',
'../',
'/../',
'/./',
'/...',
'/abc',
'.../',
'/sdf/',
'asdf/',
'./b',
'scm/plugins/.',
'scm/../plugins',
'scm/main/',
'/scm/main/',
'scm/./main',
'scm//main',
'scm\\main',
'scm/main-$HOME',
'scm/main-${HOME}-home',
'scm/main-%HOME-home',
'scm/main-%HOME%-home',
'abc$abc',
'abc%abc',
'abc<abc',
'abc>abc',
'abc#abc',
'abc+abc',
'abc{abc',
'abc}abc',
'abc(abc',
'abc)abc',
'abc[abc',
'abc]abc',
'abc|abc',
'scm/main',
'scm/plugins/git-plugin',
'.scm/plugins',
'a/b..',
'a/..b',
'scm/main',
'scm/plugins/git-plugin',
'scm/plugins/git-plugin',
];
invalidPaths.forEach(path =>
expect(validator.isNameValid(path)).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);
});
});

View File

@@ -1,5 +1,4 @@
// @flow
import { validation } from "@scm-manager/ui-components";
import { validation } from '@scm-manager/ui-components';
const nameRegex = /(?!^\.\.$)(?!^\.$)(?!.*[\\\[\]])^[A-Za-z0-9\.][A-Za-z0-9\.\-_]*$/;
@@ -8,5 +7,5 @@ export const isNameValid = (name: string) => {
};
export function isContactValid(mail: string) {
return "" === mail || validation.isMailValid(mail);
return '' === mail || validation.isMailValid(mail);
}

View File

@@ -1,24 +0,0 @@
//@flow
import React from "react";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Repository } from "@scm-manager/ui-types";
import { Image } from "@scm-manager/ui-components";
type Props = {
repository: Repository
};
class RepositoryAvatar extends React.Component<Props> {
render() {
const { repository } = this.props;
return (
<p className="image is-64x64">
<ExtensionPoint name="repos.repository-avatar" props={{ repository }}>
<Image src="/images/blib.jpg" alt="Logo" />
</ExtensionPoint>
</p>
);
}
}
export default RepositoryAvatar;

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { ExtensionPoint } from '@scm-manager/ui-extensions';
import { Repository } from '@scm-manager/ui-types';
import { Image } from '@scm-manager/ui-components';
type Props = {
repository: Repository;
};
class RepositoryAvatar extends React.Component<Props> {
render() {
const { repository } = this.props;
return (
<p className="image is-64x64">
<ExtensionPoint
name="repos.repository-avatar"
props={{
repository,
}}
>
<Image src="/images/blib.jpg" alt="Logo" />
</ExtensionPoint>
</p>
);
}
}
export default RepositoryAvatar;

View File

@@ -1,12 +1,11 @@
//@flow
import React from "react";
import type { Repository } from "@scm-manager/ui-types";
import { CardColumn, DateFromNow } from "@scm-manager/ui-components";
import RepositoryEntryLink from "./RepositoryEntryLink";
import RepositoryAvatar from "./RepositoryAvatar";
import React from 'react';
import { Repository } from '@scm-manager/ui-types';
import { CardColumn, DateFromNow } from '@scm-manager/ui-components';
import RepositoryEntryLink from './RepositoryEntryLink';
import RepositoryAvatar from './RepositoryAvatar';
type Props = {
repository: Repository
repository: Repository;
};
class RepositoryEntry extends React.Component<Props> {
@@ -15,11 +14,11 @@ class RepositoryEntry extends React.Component<Props> {
};
renderBranchesLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["branches"]) {
if (repository._links['branches']) {
return (
<RepositoryEntryLink
icon="code-branch"
to={repositoryLink + "/branches"}
to={repositoryLink + '/branches'}
/>
);
}
@@ -27,11 +26,11 @@ class RepositoryEntry extends React.Component<Props> {
};
renderChangesetsLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["changesets"]) {
if (repository._links['changesets']) {
return (
<RepositoryEntryLink
icon="exchange-alt"
to={repositoryLink + "/changesets"}
to={repositoryLink + '/changesets'}
/>
);
}
@@ -39,20 +38,20 @@ class RepositoryEntry extends React.Component<Props> {
};
renderSourcesLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["sources"]) {
if (repository._links['sources']) {
return (
<RepositoryEntryLink icon="code" to={repositoryLink + "/sources"} />
<RepositoryEntryLink icon="code" to={repositoryLink + '/sources'} />
);
}
return null;
};
renderModifyLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["update"]) {
if (repository._links['update']) {
return (
<RepositoryEntryLink
icon="cog"
to={repositoryLink + "/settings/general"}
to={repositoryLink + '/settings/general'}
/>
);
}

View File

@@ -1,12 +1,11 @@
//@flow
import React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { Icon } from "@scm-manager/ui-components";
import React from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { Icon } from '@scm-manager/ui-components';
type Props = {
to: string,
icon: string
to: string;
icon: string;
};
const PointerEventsLink = styled(Link)`

View File

@@ -1,11 +1,10 @@
//@flow
import React from "react";
import { CardColumnGroup } from "@scm-manager/ui-components";
import type { RepositoryGroup } from "@scm-manager/ui-types";
import RepositoryEntry from "./RepositoryEntry";
import React from 'react';
import { CardColumnGroup } from '@scm-manager/ui-components';
import { RepositoryGroup } from '@scm-manager/ui-types';
import RepositoryEntry from './RepositoryEntry';
type Props = {
group: RepositoryGroup
group: RepositoryGroup;
};
class RepositoryGroupEntry extends React.Component<Props> {

View File

@@ -1,13 +1,12 @@
//@flow
import React from "react";
import React from 'react';
import type { Repository } from "@scm-manager/ui-types";
import { Repository } from '@scm-manager/ui-types';
import groupByNamespace from "./groupByNamespace";
import RepositoryGroupEntry from "./RepositoryGroupEntry";
import groupByNamespace from './groupByNamespace';
import RepositoryGroupEntry from './RepositoryGroupEntry';
type Props = {
repositories: Repository[]
repositories: Repository[];
};
class RepositoryList extends React.Component<Props> {

View File

@@ -1,74 +0,0 @@
// @flow
import groupByNamespace from "./groupByNamespace";
const base = {
type: "git",
_links: {}
};
const slartiBlueprintsFjords = {
...base,
namespace: "slarti",
name: "fjords-blueprints"
};
const slartiFjords = {
...base,
namespace: "slarti",
name: "fjords"
};
const hitchhikerRestand = {
...base,
namespace: "hitchhiker",
name: "restand"
};
const hitchhikerPuzzle42 = {
...base,
namespace: "hitchhiker",
name: "puzzle42"
};
const hitchhikerHeartOfGold = {
...base,
namespace: "hitchhiker",
name: "heartOfGold"
};
const zaphodMarvinFirmware = {
...base,
namespace: "zaphod",
name: "marvin-firmware"
};
it("should group the repositories by their namespace", () => {
const repositories = [
zaphodMarvinFirmware,
slartiBlueprintsFjords,
hitchhikerRestand,
slartiFjords,
hitchhikerHeartOfGold,
hitchhikerPuzzle42
];
const expected = [
{
name: "hitchhiker",
repositories: [
hitchhikerHeartOfGold,
hitchhikerPuzzle42,
hitchhikerRestand
]
},
{
name: "slarti",
repositories: [slartiFjords, slartiBlueprintsFjords]
},
{
name: "zaphod",
repositories: [zaphodMarvinFirmware]
}
];
expect(groupByNamespace(repositories)).toEqual(expected);
});

View File

@@ -0,0 +1,73 @@
import groupByNamespace from './groupByNamespace';
const base = {
type: 'git',
_links: {},
};
const slartiBlueprintsFjords = {
...base,
namespace: 'slarti',
name: 'fjords-blueprints',
};
const slartiFjords = {
...base,
namespace: 'slarti',
name: 'fjords',
};
const hitchhikerRestand = {
...base,
namespace: 'hitchhiker',
name: 'restand',
};
const hitchhikerPuzzle42 = {
...base,
namespace: 'hitchhiker',
name: 'puzzle42',
};
const hitchhikerHeartOfGold = {
...base,
namespace: 'hitchhiker',
name: 'heartOfGold',
};
const zaphodMarvinFirmware = {
...base,
namespace: 'zaphod',
name: 'marvin-firmware',
};
it('should group the repositories by their namespace', () => {
const repositories = [
zaphodMarvinFirmware,
slartiBlueprintsFjords,
hitchhikerRestand,
slartiFjords,
hitchhikerHeartOfGold,
hitchhikerPuzzle42,
];
const expected = [
{
name: 'hitchhiker',
repositories: [
hitchhikerHeartOfGold,
hitchhikerPuzzle42,
hitchhikerRestand,
],
},
{
name: 'slarti',
repositories: [slartiFjords, slartiBlueprintsFjords],
},
{
name: 'zaphod',
repositories: [zaphodMarvinFirmware],
},
];
expect(groupByNamespace(repositories)).toEqual(expected);
});

View File

@@ -1,8 +1,7 @@
// @flow
import type { Repository, RepositoryGroup } from "@scm-manager/ui-types";
import { Repository, RepositoryGroup } from '@scm-manager/ui-types';
export default function groupByNamespace(
repositories: Repository[]
repositories: Repository[],
): RepositoryGroup[] {
let groups = {};
for (let repository of repositories) {
@@ -12,7 +11,7 @@ export default function groupByNamespace(
if (!group) {
group = {
name: groupName,
repositories: []
repositories: [],
};
groups[groupName] = group;
}

View File

@@ -1,2 +0,0 @@
import RepositoryList from "./RepositoryList";
export default RepositoryList;

View File

@@ -0,0 +1,2 @@
import RepositoryList from './RepositoryList';
export default RepositoryList;