mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-12-21 15:59:48 +01:00
fix review findings
This commit is contained in:
@@ -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)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()}
|
||||
</>
|
||||
|
||||
@@ -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=""
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
46
scm-ui/ui-components/src/devBuild.ts
Normal file
46
scm-ui/ui-components/src/devBuild.ts
Normal 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;
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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}`} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user