fix review findings

This commit is contained in:
Eduard Heimbuch
2020-08-11 10:34:29 +02:00
parent a46d8c4749
commit c1cfff603b
63 changed files with 578 additions and 494 deletions

View File

@@ -23,6 +23,7 @@
*/
import React from "react";
import classNames from "classnames";
import { createAttributesForTesting } from "./devBuild";
type Props = {
title?: string;
@@ -31,6 +32,7 @@ type Props = {
color: string;
className?: string;
onClick?: () => void;
testId?: string;
};
export default class Icon extends React.Component<Props> {
@@ -40,12 +42,23 @@ export default class Icon extends React.Component<Props> {
};
render() {
const { title, iconStyle, name, color, className, onClick } = this.props;
const { title, iconStyle, name, color, className, onClick, testId } = this.props;
if (title) {
return (
<i onClick={onClick} title={title} className={classNames(iconStyle, "fa-fw", "fa-" + name, `has-text-${color}`, className)} />
<i
onClick={onClick}
title={title}
className={classNames(iconStyle, "fa-fw", "fa-" + name, `has-text-${color}`, className)}
{...createAttributesForTesting(testId)}
/>
);
}
return <i onClick={onClick} className={classNames(iconStyle, "fa-" + name, `has-text-${color}`, className)} />;
return (
<i
onClick={onClick}
className={classNames(iconStyle, "fa-" + name, `has-text-${color}`, className)}
{...createAttributesForTesting(testId)}
/>
);
}
}

View File

@@ -32,11 +32,12 @@ type Props = RouteComponentProps & {
showCreateButton: boolean;
link: string;
label?: string;
testId?: string;
};
class OverviewPageActions extends React.Component<Props> {
render() {
const { history, location, link } = this.props;
const { history, location, link, testId } = this.props;
return (
<>
<FilterInput
@@ -44,6 +45,7 @@ class OverviewPageActions extends React.Component<Props> {
filter={filter => {
history.push(`/${link}/?q=${filter}`);
}}
testId={testId + "-filter"}
/>
{this.renderCreateButton()}
</>

View File

@@ -39093,15 +39093,6 @@ exports[`Storyshots Layout|Footer Default 1`] = `
footer.user.profile
</a>
</li>
<li>
<a
className=""
href="/me/settings/password"
onClick={[Function]}
>
profile.changePasswordNavLink
</a>
</li>
</ul>
</section>
<section
@@ -39186,16 +39177,18 @@ exports[`Storyshots Layout|Footer Full 1`] = `
<div
className="FooterSection__Title-lx0ikb-0 gUQuRF"
>
<span
className="Footer__AvatarContainer-k70cxq-1 faWKKx image is-rounded"
>
<img
alt="trillian"
className="is-rounded Footer__VCenteredAvatar-k70cxq-0 btJmxP"
src="test-file-stub"
/>
</span>
Trillian McMillian
<div>
<span
className="Footer__AvatarContainer-k70cxq-1 faWKKx image is-rounded"
>
<img
alt="trillian"
className="is-rounded Footer__VCenteredAvatar-k70cxq-0 btJmxP"
src="test-file-stub"
/>
</span>
Trillian McMillian
</div>
</div>
<ul
className="FooterSection__Menu-lx0ikb-1 idmusL"
@@ -39209,15 +39202,6 @@ exports[`Storyshots Layout|Footer Full 1`] = `
footer.user.profile
</a>
</li>
<li>
<a
className=""
href="/me/settings/password"
onClick={[Function]}
>
profile.changePasswordNavLink
</a>
</li>
<li>
<a
className=""
@@ -39338,16 +39322,18 @@ exports[`Storyshots Layout|Footer With Avatar 1`] = `
<div
className="FooterSection__Title-lx0ikb-0 gUQuRF"
>
<span
className="Footer__AvatarContainer-k70cxq-1 faWKKx image is-rounded"
>
<img
alt="trillian"
className="is-rounded Footer__VCenteredAvatar-k70cxq-0 btJmxP"
src="test-file-stub"
/>
</span>
Trillian McMillian
<div>
<span
className="Footer__AvatarContainer-k70cxq-1 faWKKx image is-rounded"
>
<img
alt="trillian"
className="is-rounded Footer__VCenteredAvatar-k70cxq-0 btJmxP"
src="test-file-stub"
/>
</span>
Trillian McMillian
</div>
</div>
<ul
className="FooterSection__Menu-lx0ikb-1 idmusL"
@@ -39361,15 +39347,6 @@ exports[`Storyshots Layout|Footer With Avatar 1`] = `
footer.user.profile
</a>
</li>
<li>
<a
className=""
href="/me/settings/password"
onClick={[Function]}
>
profile.changePasswordNavLink
</a>
</li>
</ul>
</section>
<section
@@ -39472,15 +39449,6 @@ exports[`Storyshots Layout|Footer With Plugin Links 1`] = `
footer.user.profile
</a>
</li>
<li>
<a
className=""
href="/me/settings/password"
onClick={[Function]}
>
profile.changePasswordNavLink
</a>
</li>
<li>
<a
className=""

View File

@@ -25,6 +25,7 @@ import React, { MouseEvent, ReactNode } from "react";
import classNames from "classnames";
import { withRouter, RouteComponentProps } from "react-router-dom";
import Icon from "../Icon";
import { createAttributesForTesting } from "../devBuild";
export type ButtonProps = {
label?: string;
@@ -37,6 +38,7 @@ export type ButtonProps = {
fullWidth?: boolean;
reducedMobile?: boolean;
children?: ReactNode;
testId?: string;
};
type Props = ButtonProps &
@@ -73,7 +75,8 @@ class Button extends React.Component<Props> {
icon,
fullWidth,
reducedMobile,
children
children,
testId
} = this.props;
const loadingClass = loading ? "is-loading" : "";
const fullWidthClass = fullWidth ? "is-fullwidth" : "";
@@ -86,6 +89,7 @@ class Button extends React.Component<Props> {
disabled={disabled}
onClick={this.onClick}
className={classNames("button", "is-" + color, loadingClass, fullWidthClass, reducedMobileClass, className)}
{...createAttributesForTesting(testId)}
>
<span className="icon is-medium">
<Icon name={icon} color="inherit" />
@@ -104,6 +108,7 @@ class Button extends React.Component<Props> {
disabled={disabled}
onClick={this.onClick}
className={classNames("button", "is-" + color, loadingClass, fullWidthClass, className)}
{...createAttributesForTesting(testId)}
>
{label} {children}
</button>

View File

@@ -26,6 +26,7 @@ import Button, { ButtonProps } from "./Button";
type SubmitButtonProps = ButtonProps & {
scrollToTop: boolean;
testId?: string;
};
class SubmitButton extends React.Component<SubmitButtonProps> {
@@ -34,7 +35,7 @@ class SubmitButton extends React.Component<SubmitButtonProps> {
};
render() {
const { action, scrollToTop } = this.props;
const { action, scrollToTop, testId } = this.props;
return (
<Button
type="submit"
@@ -48,6 +49,7 @@ class SubmitButton extends React.Component<SubmitButtonProps> {
window.scrollTo(0, 0);
}
}}
testId={testId ? testId : "submit-button"}
/>
);
}

View File

@@ -0,0 +1,46 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
export const isDevBuild = () => (process.env.NODE_ENV === "development")
export const createAttributesForTesting = (testId?: string) => {
if (!testId || !isDevBuild()) {
return undefined;
}
return {
"data-testid": testId
}
};
export const replaceSpacesInTestId = (testId?: string) => {
if (!testId) {
return testId;
}
let id = testId;
while (id.includes(" ")) {
id = id.replace(" ", "-");
}
return id;
};

View File

@@ -35,6 +35,7 @@ type Props = {
title?: string;
disabled?: boolean;
helpText?: string;
testId?: string;
};
export default class Checkbox extends React.Component<Props> {
@@ -59,7 +60,7 @@ export default class Checkbox extends React.Component<Props> {
};
render() {
const { label, checked, indeterminate, disabled } = this.props;
const { label, checked, indeterminate, disabled, testId } = this.props;
return (
<div className="field">
{this.renderLabelWithHelp()}
@@ -70,7 +71,7 @@ export default class Checkbox extends React.Component<Props> {
but bulma does.
// @ts-ignore */}
<label className="checkbox" disabled={disabled}>
<TriStateCheckbox checked={checked} indeterminate={indeterminate} disabled={disabled} />
<TriStateCheckbox checked={checked} indeterminate={indeterminate} disabled={disabled} testId={testId} />
{label}
{this.renderHelp()}
</label>

View File

@@ -24,10 +24,12 @@
import React, { ChangeEvent, FormEvent } from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import styled from "styled-components";
import { createAttributesForTesting } from "../devBuild";
type Props = WithTranslation & {
filter: (p: string) => void;
value?: string;
testId?: string;
};
type State = {
@@ -58,9 +60,9 @@ class FilterInput extends React.Component<Props, State> {
};
render() {
const { t } = this.props;
const { t, testId } = this.props;
return (
<form className="input-field" onSubmit={this.handleSubmit}>
<form className="input-field" onSubmit={this.handleSubmit} {...createAttributesForTesting(testId)}>
<div className="control has-icons-left">
<FixedHeightInput
className="input"

View File

@@ -24,6 +24,7 @@
import React, { ChangeEvent, KeyboardEvent } from "react";
import classNames from "classnames";
import LabelWithHelpIcon from "./LabelWithHelpIcon";
import { createAttributesForTesting } from "../devBuild";
type Props = {
label?: string;
@@ -39,6 +40,7 @@ type Props = {
disabled?: boolean;
helpText?: string;
className?: string;
testId?: string;
};
class InputField extends React.Component<Props> {
@@ -80,7 +82,8 @@ class InputField extends React.Component<Props> {
disabled,
label,
helpText,
className
className,
testId
} = this.props;
const errorView = validationError ? "is-danger" : "";
const helper = validationError ? <p className="help is-danger">{errorMessage}</p> : "";
@@ -99,6 +102,7 @@ class InputField extends React.Component<Props> {
onChange={this.handleInput}
onKeyPress={this.handleKeyPress}
disabled={disabled}
{...createAttributesForTesting(testId)}
/>
</div>
{helper}

View File

@@ -24,6 +24,7 @@
import React, { ChangeEvent } from "react";
import classNames from "classnames";
import LabelWithHelpIcon from "./LabelWithHelpIcon";
import {createAttributesForTesting} from "../devBuild";
export type SelectItem = {
value: string;
@@ -39,6 +40,7 @@ type Props = {
loading?: boolean;
helpText?: string;
disabled?: boolean;
testId?: string;
};
class Select extends React.Component<Props> {
@@ -57,7 +59,7 @@ class Select extends React.Component<Props> {
};
render() {
const { options, value, label, helpText, loading, disabled } = this.props;
const { options, value, label, helpText, loading, disabled, testId } = this.props;
const loadingClass = loading ? "is-loading" : "";
return (
@@ -71,6 +73,7 @@ class Select extends React.Component<Props> {
value={value}
onChange={this.handleInput}
disabled={disabled}
{...createAttributesForTesting(testId)}
>
{options.map(opt => {
return (

View File

@@ -29,9 +29,10 @@ type Props = {
indeterminate?: boolean;
disabled?: boolean;
label?: string;
testId?: string;
};
const TriStateCheckbox: FC<Props> = ({ checked, indeterminate, disabled, label }) => {
const TriStateCheckbox: FC<Props> = ({ checked, indeterminate, disabled, label, testId }) => {
let icon;
if (indeterminate) {
icon = "minus-square";
@@ -57,8 +58,11 @@ const TriStateCheckbox: FC<Props> = ({ checked, indeterminate, disabled, label }
color = "black";
}
return <><Icon iconStyle={"is-outlined"} name={icon} className={className} color={color} />{" "}
{label}</>;
return (
<>
<Icon iconStyle={"is-outlined"} name={icon} className={className} color={color} testId={testId} /> {label}
</>
);
};
export default TriStateCheckbox;

View File

@@ -85,6 +85,7 @@ export { default as comparators } from "./comparators";
export { apiClient } from "./apiclient";
export * from "./errors";
export { isDevBuild, createAttributesForTesting, replaceSpacesInTestId } from "./devBuild";
export * from "./avatar";
export * from "./buttons";

View File

@@ -22,8 +22,8 @@
* SOFTWARE.
*/
import React, { FC } from "react";
import { Me, Links } from "@scm-manager/ui-types";
import { useBinder, ExtensionPoint } from "@scm-manager/ui-extensions";
import { Links, Me } from "@scm-manager/ui-types";
import { ExtensionPoint, useBinder } from "@scm-manager/ui-extensions";
import { AvatarImage } from "../avatar";
import NavLink from "../navigation/NavLink";
import FooterSection from "./FooterSection";
@@ -31,6 +31,7 @@ import styled from "styled-components";
import { EXTENSION_POINT } from "../avatar/Avatar";
import ExternalNavLink from "../navigation/ExternalNavLink";
import { useTranslation } from "react-i18next";
import { createAttributesForTesting, replaceSpacesInTestId } from "../devBuild";
type Props = {
me?: Me;
@@ -43,11 +44,13 @@ type TitleWithIconsProps = {
icon: string;
};
const TitleWithIcon: FC<TitleWithIconsProps> = ({ icon, title }) => (
<>
<i className={`fas fa-${icon} fa-fw`} /> {title}
</>
);
const TitleWithIcon: FC<TitleWithIconsProps> = ({ icon, title }) => {
return (
<>
<i className={`fas fa-${icon} fa-fw`} {...createAttributesForTesting(replaceSpacesInTestId(title))} /> {title}
</>
);
};
type TitleWithAvatarProps = {
me: Me;
@@ -66,12 +69,12 @@ const AvatarContainer = styled.span`
`;
const TitleWithAvatar: FC<TitleWithAvatarProps> = ({ me }) => (
<>
<div {...createAttributesForTesting(replaceSpacesInTestId(me.displayName))}>
<AvatarContainer className="image is-rounded">
<VCenteredAvatar person={me} representation="rounded" />
</AvatarContainer>
{me.displayName}
</>
</div>
);
const Footer: FC<Props> = ({ me, version, links }) => {
@@ -89,24 +92,14 @@ const Footer: FC<Props> = ({ me, version, links }) => {
meSectionTile = <TitleWithIcon title={me.displayName} icon="user-circle" />;
}
let meSectionBody = <div />;
{
if (me.name !== "_anonymous")
meSectionBody = (
<>
<NavLink to="/me/settings/password" label={t("profile.changePasswordNavLink")} />
<ExtensionPoint name="profile.setting" props={extensionProps} renderAll={true} />
</>
);
}
return (
<footer className="footer">
<section className="section container">
<div className="columns is-size-7">
<FooterSection title={meSectionTile}>
<NavLink to="/me" label={t("footer.user.profile")} />
{meSectionBody}
<NavLink to="/me" label={t("footer.user.profile")} testId="footer-user-profile" />
{me?._links?.password && <NavLink to="/me/settings/password" label={t("profile.changePasswordNavLink")} />}
<ExtensionPoint name="profile.setting" props={extensionProps} renderAll={true} />
</FooterSection>
<FooterSection title={<TitleWithIcon title={t("footer.information.title")} icon="info-circle" />}>
<ExternalNavLink to="https://www.scm-manager.org/" label={`SCM-Manager ${version}`} />

View File

@@ -28,15 +28,16 @@ import { RoutingProps } from "./RoutingProps";
import { FC } from "react";
import useMenuContext from "./MenuContext";
import useActiveMatch from "./useActiveMatch";
import {createAttributesForTesting} from "../devBuild";
type Props = RoutingProps & {
label: string;
title?: string;
icon?: string;
className?: string;
testId?: string;
};
const NavLink: FC<Props> = ({ to, activeWhenMatch, activeOnlyWhenExact, icon, label, title, className }) => {
const NavLink: FC<Props> = ({ to, activeWhenMatch, activeOnlyWhenExact, icon, label, title, testId }) => {
const active = useActiveMatch({ to, activeWhenMatch, activeOnlyWhenExact });
const context = useMenuContext();
@@ -53,7 +54,11 @@ const NavLink: FC<Props> = ({ to, activeWhenMatch, activeOnlyWhenExact, icon, la
return (
<li title={collapsed ? title : undefined}>
<Link className={classNames(active ? "is-active" : "", collapsed ? "has-text-centered" : "", className)} to={to}>
<Link
className={classNames(active ? "is-active" : "", collapsed ? "has-text-centered" : "")}
to={to}
{...createAttributesForTesting(testId)}
>
{showIcon}
{collapsed ? null : label}
</Link>

View File

@@ -40,7 +40,7 @@ class PrimaryNavigation extends React.Component<Props> {
return (to: string, match: string, label: string, linkName: string) => {
const link = links[linkName];
if (link) {
const navigationItem = <PrimaryNavigationLink className={t(label)} to={to} match={match} label={t(label)} key={linkName} />;
const navigationItem = <PrimaryNavigationLink testId={label.replace(".", "-")} to={to} match={match} label={t(label)} key={linkName} />;
navigationItems.push(navigationItem);
}
};

View File

@@ -23,30 +23,39 @@
*/
import * as React from "react";
import { Route, Link } from "react-router-dom";
import classNames from "classnames";
import { createAttributesForTesting } from "../devBuild";
type Props = {
to: string;
label: string;
match?: string;
activeOnlyWhenExact?: boolean;
className?: string;
testId?: string;
};
class PrimaryNavigationLink extends React.Component<Props> {
renderLink = (route: any) => {
const { to, label, className } = this.props;
const { to, label, testId } = this.props;
return (
<li className={classNames(route.match ? "is-active" : "", className)}>
<Link to={to}>{label}</Link>
<li className={route.match ? "is-active" : ""}>
<Link to={to} {...createAttributesForTesting(testId)}>
{label}
</Link>
</li>
);
};
render() {
const { to, match, activeOnlyWhenExact } = this.props;
const { to, match, activeOnlyWhenExact, testId } = this.props;
const path = match ? match : to;
return <Route path={path} exact={activeOnlyWhenExact} children={this.renderLink} />;
return (
<Route
path={path}
exact={activeOnlyWhenExact}
children={this.renderLink}
{...createAttributesForTesting(testId)}
/>
);
}
}

View File

@@ -27,15 +27,25 @@ import classNames from "classnames";
import useMenuContext from "./MenuContext";
import { RoutingProps } from "./RoutingProps";
import useActiveMatch from "./useActiveMatch";
import { createAttributesForTesting } from "../devBuild";
type Props = RoutingProps & {
label: string;
title?: string;
icon?: string;
className?: string;
testId?: string;
};
const SubNavigation: FC<Props> = ({ to, activeOnlyWhenExact, activeWhenMatch, icon, title, label, children, className }) => {
const SubNavigation: FC<Props> = ({
to,
activeOnlyWhenExact,
activeWhenMatch,
icon,
title,
label,
children,
testId
}) => {
const context = useMenuContext();
const collapsed = context.isCollapsed();
@@ -61,7 +71,11 @@ const SubNavigation: FC<Props> = ({ to, activeOnlyWhenExact, activeWhenMatch, ic
return (
<li title={collapsed ? title : undefined}>
<Link className={classNames(active ? "is-active" : "", collapsed ? "has-text-centered" : "", className)} to={to}>
<Link
className={classNames(active ? "is-active" : "", collapsed ? "has-text-centered" : "")}
to={to}
{...createAttributesForTesting(testId)}
>
<i className={classNames(defaultIcon, "fa-fw")} /> {collapsed ? "" : label}
</Link>
{childrenList}