mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
chore(data-cy): require data-cy attributes [EE-6880] (#11453)
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
This commit is contained in:
parent
3cad13388c
commit
d38085a560
538 changed files with 2571 additions and 595 deletions
|
@ -1,11 +1,12 @@
|
|||
import { Briefcase } from 'lucide-react';
|
||||
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren';
|
||||
|
||||
interface Props {
|
||||
interface Props extends AutomationTestingProps {
|
||||
featureId: FeatureId;
|
||||
heading: string;
|
||||
message: string;
|
||||
|
@ -21,6 +22,7 @@ export function BETeaserButton({
|
|||
buttonText,
|
||||
className,
|
||||
buttonClassName,
|
||||
'data-cy': dataCy,
|
||||
}: Props) {
|
||||
return (
|
||||
<TooltipWithChildren
|
||||
|
@ -38,6 +40,7 @@ export function BETeaserButton({
|
|||
size="small"
|
||||
onClick={() => {}}
|
||||
disabled
|
||||
data-cy={dataCy}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
|
|
|
@ -19,7 +19,12 @@ export function Code({ children, showCopyButton }: Props) {
|
|||
<code className={styles.code}>{children}</code>
|
||||
|
||||
{showCopyButton && (
|
||||
<Button color="link" className={styles.copyButton} onClick={handleCopy}>
|
||||
<Button
|
||||
color="link"
|
||||
className={styles.copyButton}
|
||||
onClick={handleCopy}
|
||||
data-cy="code-copy-button"
|
||||
>
|
||||
<Icon
|
||||
icon={copiedSuccessfully ? Check : Copy}
|
||||
className="!ml-1"
|
||||
|
|
|
@ -7,12 +7,14 @@ import { useMemo } from 'react';
|
|||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { tags as highlightTags } from '@lezer/highlight';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { CopyButton } from '@@/buttons/CopyButton';
|
||||
|
||||
import styles from './CodeEditor.module.css';
|
||||
import { TextTip } from './Tip/TextTip';
|
||||
|
||||
interface Props {
|
||||
interface Props extends AutomationTestingProps {
|
||||
id: string;
|
||||
placeholder?: string;
|
||||
yaml?: boolean;
|
||||
|
@ -67,6 +69,7 @@ export function CodeEditor({
|
|||
yaml: isYaml,
|
||||
dockerFile: isDockerFile,
|
||||
shell: isShell,
|
||||
'data-cy': dataCy,
|
||||
}: Props) {
|
||||
const extensions = useMemo(() => {
|
||||
const extensions = [];
|
||||
|
@ -90,6 +93,7 @@ export function CodeEditor({
|
|||
</div>
|
||||
|
||||
<CopyButton
|
||||
data-cy={`copy-code-button-${id}`}
|
||||
fadeDelay={2500}
|
||||
copyText={value}
|
||||
color="link"
|
||||
|
@ -112,6 +116,7 @@ export function CodeEditor({
|
|||
highlightSelectionMatches: false,
|
||||
autocompletion: false,
|
||||
}}
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -19,7 +19,14 @@ interface StoryProps {
|
|||
}
|
||||
|
||||
function Template({ value, icon, type }: StoryProps) {
|
||||
return <DashboardItem value={value} icon={icon} type={type} />;
|
||||
return (
|
||||
<DashboardItem
|
||||
value={value}
|
||||
icon={icon}
|
||||
type={type}
|
||||
dataCy="data-cy-example"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const Primary: Story<StoryProps> = Template.bind({});
|
||||
|
@ -31,15 +38,25 @@ Primary.args = {
|
|||
|
||||
export function WithLink() {
|
||||
return (
|
||||
<Link to="example.page">
|
||||
<DashboardItem value={1} icon={List} type="Example resource" />
|
||||
<Link to="example.page" data-cy="data-cy-example">
|
||||
<DashboardItem
|
||||
value={1}
|
||||
icon={List}
|
||||
type="Example resource"
|
||||
dataCy="data-cy-example"
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function WithChildren() {
|
||||
return (
|
||||
<DashboardItem value={1} icon={List} type="Example resource">
|
||||
<DashboardItem
|
||||
value={1}
|
||||
icon={List}
|
||||
type="Example resource"
|
||||
dataCy="data-cy-example"
|
||||
>
|
||||
<div>Children</div>
|
||||
</DashboardItem>
|
||||
);
|
||||
|
|
|
@ -26,5 +26,7 @@ test('should have accessibility label created from the provided resource type',
|
|||
});
|
||||
|
||||
function renderComponent(value = 0, icon = User, type = '') {
|
||||
return render(<DashboardItem value={value} icon={icon} type={type} />);
|
||||
return render(
|
||||
<DashboardItem value={value} icon={icon} type={type} dataCy="example" />
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ interface Props extends IconProps {
|
|||
to?: string;
|
||||
params?: object;
|
||||
children?: ReactNode;
|
||||
dataCy?: string;
|
||||
dataCy: string;
|
||||
}
|
||||
|
||||
export function DashboardItem({
|
||||
|
@ -103,7 +103,12 @@ export function DashboardItem({
|
|||
|
||||
if (to) {
|
||||
return (
|
||||
<Link to={to} className="!no-underline" params={params}>
|
||||
<Link
|
||||
to={to}
|
||||
className="!no-underline"
|
||||
params={params}
|
||||
data-cy={`${dataCy}-link`}
|
||||
>
|
||||
{Item}
|
||||
</Link>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ export default {
|
|||
|
||||
function Template({ key1, val1, key2, val2 }: Args) {
|
||||
return (
|
||||
<DetailsTable>
|
||||
<DetailsTable dataCy="details-table">
|
||||
<DetailsRow label={key1}>{val1}</DetailsRow>
|
||||
<DetailsRow label={key2}>{val2}</DetailsRow>
|
||||
</DetailsTable>
|
||||
|
|
|
@ -10,7 +10,7 @@ test('should display child row elements', () => {
|
|||
};
|
||||
|
||||
const { queryByText } = render(
|
||||
<DetailsTable>
|
||||
<DetailsTable dataCy="details-table">
|
||||
<DetailsTable.Row label="Name">{person.name}</DetailsTable.Row>
|
||||
<DetailsTable.Row label="Id">{person.id}</DetailsTable.Row>
|
||||
</DetailsTable>
|
||||
|
|
|
@ -2,8 +2,8 @@ import clsx from 'clsx';
|
|||
import { Children, PropsWithChildren } from 'react';
|
||||
|
||||
type Props = {
|
||||
dataCy: string;
|
||||
headers?: string[];
|
||||
dataCy?: string;
|
||||
className?: string;
|
||||
emptyMessage?: string;
|
||||
};
|
||||
|
|
|
@ -34,6 +34,7 @@ export function AdvancedForm({
|
|||
}}
|
||||
placeholder="e.g. registry:port/my-image:my-tag"
|
||||
required
|
||||
data-cy="image-config-advanced-input"
|
||||
/>
|
||||
</FormControl>
|
||||
</>
|
||||
|
|
|
@ -46,6 +46,7 @@ export function ImageConfigFieldset({
|
|||
icon={Globe}
|
||||
className="!ml-0 p-0 hover:no-underline"
|
||||
onClick={() => setFieldValue('useRegistry', false)}
|
||||
data-cy="image-config-advanced-button"
|
||||
>
|
||||
Advanced mode
|
||||
</Button>
|
||||
|
@ -56,6 +57,7 @@ export function ImageConfigFieldset({
|
|||
icon={Database}
|
||||
className="!ml-0 p-0 hover:no-underline"
|
||||
onClick={() => setFieldValue('useRegistry', true)}
|
||||
data-cy="image-config-simple-button"
|
||||
>
|
||||
Simple mode
|
||||
</Button>
|
||||
|
|
|
@ -11,6 +11,7 @@ export function InputSearch({
|
|||
options,
|
||||
placeholder,
|
||||
inputId,
|
||||
'data-cy': dataCy,
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
|
@ -34,6 +35,7 @@ export function InputSearch({
|
|||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
inputId={inputId}
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -93,8 +93,13 @@ function RateLimitsInner({
|
|||
You are currently using an anonymous account to pull images
|
||||
from DockerHub and will be limited to 100 pulls every 6
|
||||
hours. You can configure DockerHub authentication in the{' '}
|
||||
<Link to="portainer.registries">Registries View</Link>.
|
||||
Remaining pulls:{' '}
|
||||
<Link
|
||||
to="portainer.registries"
|
||||
data-cy="image-registry-rate-limits-registries-view-link"
|
||||
>
|
||||
Registries View
|
||||
</Link>
|
||||
. Remaining pulls:{' '}
|
||||
<span className="font-bold">
|
||||
{pullRateLimits.remaining}/{pullRateLimits.limit}
|
||||
</span>
|
||||
|
|
|
@ -91,6 +91,7 @@ export function SimpleForm({
|
|||
rel: 'noreferrer',
|
||||
}}
|
||||
icon={DockerIcon}
|
||||
data-cy="component-dockerHubSearchButton"
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
|
@ -189,6 +190,7 @@ function ImageField({
|
|||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
id={inputId}
|
||||
data-cy="image-field-simple-input"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,12 @@ export function InformationPanel({
|
|||
<span>{title}</span>
|
||||
{!!onDismiss && (
|
||||
<span className="small" style={{ float: 'right' }}>
|
||||
<Button color="link" icon={X} onClick={() => onDismiss()}>
|
||||
<Button
|
||||
color="link"
|
||||
icon={X}
|
||||
onClick={() => onDismiss()}
|
||||
data-cy="dismiss-information-panel-button"
|
||||
>
|
||||
dismiss
|
||||
</Button>
|
||||
</span>
|
||||
|
|
|
@ -63,6 +63,7 @@ export function InsightsBox({
|
|||
{insightCloseId && (
|
||||
<Button
|
||||
icon={X}
|
||||
data-cy={`insight-close-${insightCloseId}`}
|
||||
className={clsx(
|
||||
'absolute right-2 top-3 flex !text-gray-7 hover:!text-gray-8 th-highcontrast:!text-gray-6 th-highcontrast:hover:!text-gray-5 th-dark:!text-gray-6 th-dark:hover:!text-gray-5',
|
||||
type === 'slim' && insightCloseId && 'top-1'
|
||||
|
|
|
@ -5,10 +5,12 @@ interface Props {
|
|||
title?: string;
|
||||
target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
|
||||
rel?: AnchorHTMLAttributes<HTMLAnchorElement>['rel'];
|
||||
'data-cy': AnchorHTMLAttributes<HTMLAnchorElement>['data-cy'];
|
||||
}
|
||||
|
||||
export function Link({
|
||||
children,
|
||||
'data-cy': dataCy,
|
||||
to,
|
||||
params,
|
||||
options,
|
||||
|
@ -18,7 +20,7 @@ export function Link({
|
|||
|
||||
return (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<a onClick={onClick} href={href} {...props}>
|
||||
<a onClick={onClick} href={href} data-cy={dataCy} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ export function LinkButton({
|
|||
className,
|
||||
children,
|
||||
title = '',
|
||||
'data-cy': dataCy,
|
||||
...props
|
||||
}: ComponentProps<typeof Button> & ComponentProps<typeof Link>) {
|
||||
return (
|
||||
|
@ -24,7 +25,9 @@ export function LinkButton({
|
|||
props={{
|
||||
to,
|
||||
params,
|
||||
'data-cy': `${dataCy}-link`,
|
||||
}}
|
||||
data-cy={`${dataCy}-button`}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
|
|
|
@ -43,6 +43,7 @@ function renderCrumb(crumb: Crumb | string) {
|
|||
to={crumb.link}
|
||||
params={crumb.linkParams}
|
||||
className="text-blue-9 hover:text-blue-11 hover:underline th-highcontrast:text-blue-5 th-dark:text-blue-7 th-dark:hover:text-blue-9"
|
||||
data-cy={`breadcrumb-${crumb.label}`}
|
||||
>
|
||||
{crumb.label}
|
||||
</Link>
|
||||
|
|
|
@ -25,6 +25,7 @@ export function ContextHelp() {
|
|||
)}
|
||||
title="Help"
|
||||
rel="noreferrer"
|
||||
data-cy="context-help-button"
|
||||
>
|
||||
<HelpCircle className="lucide" />
|
||||
</a>
|
||||
|
|
|
@ -16,7 +16,10 @@ export function HeaderTitle({ title, children }: PropsWithChildren<Props>) {
|
|||
return (
|
||||
<div className="flex justify-between whitespace-normal pt-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="m-0 text-2xl font-medium text-gray-11 th-highcontrast:text-white th-dark:text-white">
|
||||
<h1
|
||||
className="m-0 text-2xl font-medium text-gray-11 th-highcontrast:text-white th-dark:text-white"
|
||||
data-cy="page-title"
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
{children && <>{children}</>}
|
||||
|
|
|
@ -97,19 +97,25 @@ export function NotificationsMenu() {
|
|||
{reducedNotifications?.length > 0 ? (
|
||||
<>
|
||||
<div className={notificationStyles.notifications}>
|
||||
{reducedNotifications.map((notification) => (
|
||||
{reducedNotifications.map((notification, index) => (
|
||||
<MenuLink
|
||||
to="portainer.notifications"
|
||||
params={{ id: notification.id }}
|
||||
notification={notification}
|
||||
key={notification.id}
|
||||
onDelete={() => onDelete(notification.id)}
|
||||
data-cy={`notification-delete-button_${index}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={notificationStyles.notificationLink}>
|
||||
<Link to="portainer.notifications">View all notifications</Link>
|
||||
<Link
|
||||
to="portainer.notifications"
|
||||
data-cy="notifications-see-all-link"
|
||||
>
|
||||
View all notifications
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
@ -44,6 +44,7 @@ export function PageHeader({
|
|||
className="m-0 p-0 focus:text-inherit"
|
||||
disabled={loading}
|
||||
title="Refresh page"
|
||||
data-cy="refresh-page-button"
|
||||
>
|
||||
<RefreshCw className="icon" />
|
||||
</Button>
|
||||
|
|
|
@ -38,8 +38,13 @@ export function PageInput({ onChange, totalPages }: Props) {
|
|||
step={1}
|
||||
onChange={handleChange}
|
||||
onKeyPress={preventNotNumber}
|
||||
data-cy="pagination-go-to-page-input"
|
||||
/>
|
||||
<Button type="submit" disabled={!isValid}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!isValid}
|
||||
data-cy="pagination-go-to-page-button"
|
||||
>
|
||||
Go
|
||||
</Button>
|
||||
</form>
|
||||
|
|
|
@ -27,6 +27,7 @@ export function RadioGroup<T extends string | number = string>({
|
|||
checked={selectedOption === option.value}
|
||||
onChange={() => onOptionChange(option.value)}
|
||||
style={{ margin: '0 4px 0 0' }}
|
||||
data-cy={`radio-${option.value}`}
|
||||
/>
|
||||
{option.label}
|
||||
</label>
|
||||
|
|
|
@ -26,12 +26,14 @@ function Template({ totalSteps = 5 }: Args) {
|
|||
<Stepper currentStep={currentStep} steps={steps} />
|
||||
<Button
|
||||
onClick={() => setCurrentStep(currentStep - 1)}
|
||||
data-cy="previous-button"
|
||||
disabled={currentStep <= 1}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setCurrentStep(currentStep + 1)}
|
||||
data-cy="next-button"
|
||||
disabled={currentStep >= steps.length}
|
||||
>
|
||||
Next
|
||||
|
|
|
@ -21,9 +21,10 @@ export function TLSFieldset({ values, onChange, errors }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
label="TLS"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
data-cy="enable-tls-switch"
|
||||
checked={values.tls}
|
||||
onChange={(checked) => handleChange({ tls: checked })}
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,6 +35,7 @@ export function TLSFieldset({ values, onChange, errors }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
label="Skip Certification Verification"
|
||||
data-cy="skip-verify-switch"
|
||||
checked={!!values.skipVerify}
|
||||
onChange={(checked) => handleChange({ skipVerify: checked })}
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
|
@ -50,6 +52,7 @@ export function TLSFieldset({ values, onChange, errors }: Props) {
|
|||
>
|
||||
<FileUploadField
|
||||
inputId="ca-cert-field"
|
||||
data-cy="ca-cert-file-upload"
|
||||
onChange={(file) => handleChange({ caCertFile: file })}
|
||||
value={values.caCertFile}
|
||||
/>
|
||||
|
@ -61,6 +64,7 @@ export function TLSFieldset({ values, onChange, errors }: Props) {
|
|||
>
|
||||
<FileUploadField
|
||||
inputId="cert-field"
|
||||
data-cy="cert-file-upload"
|
||||
onChange={(file) => handleChange({ certFile: file })}
|
||||
value={values.certFile}
|
||||
/>
|
||||
|
@ -72,6 +76,7 @@ export function TLSFieldset({ values, onChange, errors }: Props) {
|
|||
>
|
||||
<FileUploadField
|
||||
inputId="tls-key-field"
|
||||
data-cy="tls-key-file-upload"
|
||||
onChange={(file) => handleChange({ keyFile: file })}
|
||||
value={values.keyFile}
|
||||
/>
|
||||
|
|
|
@ -45,7 +45,11 @@ export function TagSelector({ value, allowCreate = false, onChange }: Props) {
|
|||
<div className="form-group">
|
||||
<div className="col-sm-12 small text-muted">
|
||||
No tags available. Head over to the
|
||||
<Link to="portainer.tags" className="space-right space-left">
|
||||
<Link
|
||||
to="portainer.tags"
|
||||
className="space-right space-left"
|
||||
data-cy="environment-tags-view-link"
|
||||
>
|
||||
Tags view
|
||||
</Link>
|
||||
to add tags
|
||||
|
@ -82,6 +86,7 @@ export function TagSelector({ value, allowCreate = false, onChange }: Props) {
|
|||
formatCreateLabel={(inputValue) => `Create "${inputValue}"`}
|
||||
onCreateOption={handleCreateOption}
|
||||
aria-label="Tags"
|
||||
data-cy="environment-tags-selector"
|
||||
/>
|
||||
</FormControl>
|
||||
</>
|
||||
|
|
|
@ -23,6 +23,7 @@ function Example() {
|
|||
onChange={(value) => setSelectedTeams(value)}
|
||||
teams={teams}
|
||||
placeholder="Select one or more teams"
|
||||
dataCy="teams-selector"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ interface Props {
|
|||
value: TeamId[] | readonly TeamId[];
|
||||
onChange(value: readonly TeamId[]): void;
|
||||
teams: Team[];
|
||||
dataCy?: string;
|
||||
dataCy: string;
|
||||
inputId?: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
|
|
|
@ -24,6 +24,7 @@ function Example() {
|
|||
onChange={setSelectedUsers}
|
||||
users={users}
|
||||
placeholder="Select one or more users"
|
||||
dataCy="users-selector"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ interface Props {
|
|||
value: UserId[];
|
||||
onChange(value: UserId[]): void;
|
||||
users: User[];
|
||||
dataCy?: string;
|
||||
dataCy: string;
|
||||
inputId?: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { PropsWithChildren, useEffect, useMemo } from 'react';
|
|||
import { useTransitionHook } from '@uirouter/react';
|
||||
|
||||
import { BROWSER_OS_PLATFORM } from '@/react/constants';
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { CodeEditor } from '@@/CodeEditor';
|
||||
import { Tooltip } from '@@/Tip/Tooltip';
|
||||
|
@ -51,7 +52,7 @@ export const editorConfig = {
|
|||
win: otherEditorConfig,
|
||||
} as const;
|
||||
|
||||
interface Props {
|
||||
interface Props extends AutomationTestingProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
|
||||
|
@ -77,6 +78,7 @@ export function WebEditorForm({
|
|||
children,
|
||||
error,
|
||||
height,
|
||||
'data-cy': dataCy,
|
||||
}: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<div>
|
||||
|
@ -105,6 +107,7 @@ export function WebEditorForm({
|
|||
value={value}
|
||||
onChange={onChange}
|
||||
height={height}
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -46,6 +46,7 @@ export function WidgetTabs({ currentTabIndex, tabs }: Props) {
|
|||
currentTabIndex !== index,
|
||||
}
|
||||
)}
|
||||
data-cy={`tab-${index}`}
|
||||
>
|
||||
<Icon icon={icon} />
|
||||
{name}
|
||||
|
|
|
@ -12,7 +12,7 @@ type Args = {
|
|||
};
|
||||
|
||||
function Template({ label }: Args) {
|
||||
return <AddButton>{label}</AddButton>;
|
||||
return <AddButton data-cy="add-">{label}</AddButton>;
|
||||
}
|
||||
|
||||
export const Primary: Story<Args> = Template.bind({});
|
||||
|
|
|
@ -28,7 +28,11 @@ function renderDefault({
|
|||
],
|
||||
route: 'root',
|
||||
});
|
||||
return render(<Wrapped to="">{label}</Wrapped>);
|
||||
return render(
|
||||
<Wrapped to="" data-cy="wrapped">
|
||||
{label}
|
||||
</Wrapped>
|
||||
);
|
||||
}
|
||||
|
||||
test('should display a AddButton component', async () => {
|
||||
|
|
|
@ -25,10 +25,10 @@ export function AddButton({
|
|||
return (
|
||||
<Button
|
||||
as={Link}
|
||||
props={{ to, params }}
|
||||
props={{ to, params, 'data-cy': `${dataCy}-link` }}
|
||||
icon={Plus}
|
||||
className="!m-0"
|
||||
data-cy={dataCy}
|
||||
data-cy={`${dataCy}-button`}
|
||||
color={color}
|
||||
disabled={disabled}
|
||||
>
|
||||
|
|
|
@ -50,6 +50,7 @@ export function DifferentTheme() {
|
|||
{states.map((state) => (
|
||||
<Button
|
||||
color={color}
|
||||
data-cy="button"
|
||||
key={state}
|
||||
disabled={state === 'disabled'}
|
||||
>
|
||||
|
@ -72,7 +73,13 @@ function Template({
|
|||
disabled,
|
||||
}: JSX.IntrinsicAttributes & PropsWithChildren<Props>) {
|
||||
return (
|
||||
<Button onClick={onClick} color={color} size={size} disabled={disabled}>
|
||||
<Button
|
||||
onClick={onClick}
|
||||
color={color}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
data-cy="button"
|
||||
>
|
||||
Primary Button
|
||||
</Button>
|
||||
);
|
||||
|
@ -90,7 +97,7 @@ Primary.args = {
|
|||
|
||||
export function Disabled() {
|
||||
return (
|
||||
<Button color="primary" onClick={() => {}} disabled>
|
||||
<Button color="primary" onClick={() => {}} disabled data-cy="button">
|
||||
Disabled Button
|
||||
</Button>
|
||||
);
|
||||
|
@ -98,7 +105,7 @@ export function Disabled() {
|
|||
|
||||
export function Danger() {
|
||||
return (
|
||||
<Button color="danger" onClick={() => {}}>
|
||||
<Button color="danger" onClick={() => {}} data-cy="button">
|
||||
Danger Button
|
||||
</Button>
|
||||
);
|
||||
|
@ -106,7 +113,7 @@ export function Danger() {
|
|||
|
||||
export function ButtonIcon() {
|
||||
return (
|
||||
<Button color="primary" onClick={() => {}} icon={Download}>
|
||||
<Button color="primary" onClick={() => {}} icon={Download} data-cy="button">
|
||||
Button with an icon
|
||||
</Button>
|
||||
);
|
||||
|
@ -114,7 +121,13 @@ export function ButtonIcon() {
|
|||
|
||||
export function ButtonIconLarge() {
|
||||
return (
|
||||
<Button color="primary" onClick={() => {}} icon={Download} size="large">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {}}
|
||||
icon={Download}
|
||||
size="large"
|
||||
data-cy="button"
|
||||
>
|
||||
Button with an icon
|
||||
</Button>
|
||||
);
|
||||
|
@ -122,7 +135,13 @@ export function ButtonIconLarge() {
|
|||
|
||||
export function ButtonIconMedium() {
|
||||
return (
|
||||
<Button color="primary" onClick={() => {}} icon={Download} size="medium">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {}}
|
||||
icon={Download}
|
||||
size="medium"
|
||||
data-cy="button"
|
||||
>
|
||||
Button with an icon
|
||||
</Button>
|
||||
);
|
||||
|
@ -130,7 +149,13 @@ export function ButtonIconMedium() {
|
|||
|
||||
export function ButtonIconXSmall() {
|
||||
return (
|
||||
<Button color="primary" onClick={() => {}} icon={Download} size="xsmall">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {}}
|
||||
icon={Download}
|
||||
size="xsmall"
|
||||
data-cy="button"
|
||||
>
|
||||
Button with an icon
|
||||
</Button>
|
||||
);
|
||||
|
@ -138,7 +163,7 @@ export function ButtonIconXSmall() {
|
|||
|
||||
export function Default() {
|
||||
return (
|
||||
<Button color="default" onClick={() => {}}>
|
||||
<Button color="default" onClick={() => {}} data-cy="button">
|
||||
Default
|
||||
</Button>
|
||||
);
|
||||
|
@ -146,7 +171,7 @@ export function Default() {
|
|||
|
||||
export function Link() {
|
||||
return (
|
||||
<Button color="link" onClick={() => {}}>
|
||||
<Button color="link" onClick={() => {}} data-cy="button">
|
||||
Link Button
|
||||
</Button>
|
||||
);
|
||||
|
@ -154,7 +179,7 @@ export function Link() {
|
|||
|
||||
export function XSmall() {
|
||||
return (
|
||||
<Button color="primary" onClick={() => {}} size="xsmall">
|
||||
<Button color="primary" onClick={() => {}} size="xsmall" data-cy="button">
|
||||
XSmall Button
|
||||
</Button>
|
||||
);
|
||||
|
@ -162,7 +187,7 @@ export function XSmall() {
|
|||
|
||||
export function Small() {
|
||||
return (
|
||||
<Button color="primary" onClick={() => {}} size="small">
|
||||
<Button color="primary" onClick={() => {}} size="small" data-cy="button">
|
||||
Small Button
|
||||
</Button>
|
||||
);
|
||||
|
@ -170,7 +195,7 @@ export function Small() {
|
|||
|
||||
export function Large() {
|
||||
return (
|
||||
<Button color="primary" onClick={() => {}} size="large">
|
||||
<Button color="primary" onClick={() => {}} size="large" data-cy="button">
|
||||
Large Button
|
||||
</Button>
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ function renderDefault({
|
|||
return render(
|
||||
<Button
|
||||
type={type}
|
||||
data-cy="button"
|
||||
color={color}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
|
|
|
@ -15,19 +15,30 @@ function Template({
|
|||
}: JSX.IntrinsicAttributes & PropsWithChildren<Props>) {
|
||||
return (
|
||||
<ButtonGroup size={size}>
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
<Button icon={Play} color="primary" onClick={() => {}} data-cy="button">
|
||||
Start
|
||||
</Button>
|
||||
<Button icon={Square} color="danger" onClick={() => {}}>
|
||||
<Button icon={Square} color="danger" onClick={() => {}} data-cy="button">
|
||||
Stop
|
||||
</Button>
|
||||
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
|
||||
<Button
|
||||
icon={RefreshCw}
|
||||
color="primary"
|
||||
onClick={() => {}}
|
||||
data-cy="button"
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
<Button icon={Play} color="primary" disabled onClick={() => {}}>
|
||||
<Button
|
||||
icon={Play}
|
||||
color="primary"
|
||||
disabled
|
||||
onClick={() => {}}
|
||||
data-cy="button"
|
||||
>
|
||||
Resume
|
||||
</Button>
|
||||
<Button icon={Trash2} color="danger" onClick={() => {}}>
|
||||
<Button icon={Trash2} color="danger" onClick={() => {}} data-cy="button">
|
||||
Remove
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
@ -42,16 +53,21 @@ Primary.args = {
|
|||
export function Xsmall() {
|
||||
return (
|
||||
<ButtonGroup size="xsmall">
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
<Button icon={Play} color="primary" onClick={() => {}} data-cy="button">
|
||||
Start
|
||||
</Button>
|
||||
<Button icon={Square} color="danger" onClick={() => {}}>
|
||||
<Button icon={Square} color="danger" onClick={() => {}} data-cy="button">
|
||||
Stop
|
||||
</Button>
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
<Button icon={Play} color="primary" onClick={() => {}} data-cy="button">
|
||||
Start
|
||||
</Button>
|
||||
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
|
||||
<Button
|
||||
icon={RefreshCw}
|
||||
color="primary"
|
||||
onClick={() => {}}
|
||||
data-cy="button"
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
@ -61,16 +77,21 @@ export function Xsmall() {
|
|||
export function Small() {
|
||||
return (
|
||||
<ButtonGroup size="small">
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
<Button icon={Play} color="primary" onClick={() => {}} data-cy="button">
|
||||
Start
|
||||
</Button>
|
||||
<Button icon={Square} color="danger" onClick={() => {}}>
|
||||
<Button icon={Square} color="danger" onClick={() => {}} data-cy="button">
|
||||
Stop
|
||||
</Button>
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
<Button icon={Play} color="primary" onClick={() => {}} data-cy="button">
|
||||
Start
|
||||
</Button>
|
||||
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
|
||||
<Button
|
||||
icon={RefreshCw}
|
||||
color="primary"
|
||||
onClick={() => {}}
|
||||
data-cy="button"
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
@ -80,16 +101,21 @@ export function Small() {
|
|||
export function Large() {
|
||||
return (
|
||||
<ButtonGroup size="large">
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
<Button icon={Play} color="primary" onClick={() => {}} data-cy="button">
|
||||
Start
|
||||
</Button>
|
||||
<Button icon={Square} color="danger" onClick={() => {}}>
|
||||
<Button icon={Square} color="danger" onClick={() => {}} data-cy="button">
|
||||
Stop
|
||||
</Button>
|
||||
<Button icon={Play} color="light" onClick={() => {}}>
|
||||
<Button icon={Play} color="light" onClick={() => {}} data-cy="button">
|
||||
Start
|
||||
</Button>
|
||||
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
|
||||
<Button
|
||||
icon={RefreshCw}
|
||||
color="primary"
|
||||
onClick={() => {}}
|
||||
data-cy="button"
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
|
|
@ -14,7 +14,11 @@ function Template({
|
|||
children,
|
||||
}: JSX.IntrinsicAttributes & PropsWithChildren<Props>) {
|
||||
return (
|
||||
<CopyButton copyText={copyText} displayText={displayText}>
|
||||
<CopyButton
|
||||
copyText={copyText}
|
||||
displayText={displayText}
|
||||
data-cy="copy-button"
|
||||
>
|
||||
{children}
|
||||
</CopyButton>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,9 @@ import { CopyButton } from './CopyButton';
|
|||
test('should display a CopyButton with children', async () => {
|
||||
const children = 'test button children';
|
||||
const { findByText } = render(
|
||||
<CopyButton copyText="">{children}</CopyButton>
|
||||
<CopyButton copyText="" data-cy="copy-button">
|
||||
{children}
|
||||
</CopyButton>
|
||||
);
|
||||
|
||||
const button = await findByText(children);
|
||||
|
@ -25,7 +27,9 @@ test('CopyButton should copy text to clipboard', async () => {
|
|||
const children = 'button';
|
||||
const copyText = 'text successfully copied to clipboard';
|
||||
const { findByText } = render(
|
||||
<CopyButton copyText={copyText}>{children}</CopyButton>
|
||||
<CopyButton copyText={copyText} data-cy="copy-button">
|
||||
{children}
|
||||
</CopyButton>
|
||||
);
|
||||
|
||||
const button = await findByText(children);
|
||||
|
|
|
@ -2,6 +2,8 @@ import { ComponentProps, PropsWithChildren } from 'react';
|
|||
import clsx from 'clsx';
|
||||
import { Check, Copy } from 'lucide-react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import { Button } from '../Button';
|
||||
|
@ -9,7 +11,7 @@ import { Button } from '../Button';
|
|||
import styles from './CopyButton.module.css';
|
||||
import { useCopy } from './useCopy';
|
||||
|
||||
export interface Props {
|
||||
export interface Props extends AutomationTestingProps {
|
||||
copyText: string;
|
||||
fadeDelay?: number;
|
||||
displayText?: string;
|
||||
|
@ -26,6 +28,7 @@ export function CopyButton({
|
|||
color,
|
||||
indicatorPosition = 'right',
|
||||
children,
|
||||
'data-cy': dataCy,
|
||||
}: PropsWithChildren<Props>) {
|
||||
const { handleCopy, copiedSuccessfully } = useCopy(copyText, fadeDelay);
|
||||
|
||||
|
@ -57,6 +60,7 @@ export function CopyButton({
|
|||
type="button"
|
||||
icon={Copy}
|
||||
disabled={!copyText}
|
||||
data-cy={dataCy}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
|
|
|
@ -23,8 +23,8 @@ type ConfirmOrClick =
|
|||
export function DeleteButton({
|
||||
disabled,
|
||||
size,
|
||||
'data-cy': dataCy,
|
||||
children,
|
||||
'data-cy': dataCy,
|
||||
...props
|
||||
}: PropsWithChildren<
|
||||
AutomationTestingProps &
|
||||
|
|
|
@ -17,6 +17,7 @@ function Template({ loadingText, isLoading }: Args) {
|
|||
return (
|
||||
<LoadingButton
|
||||
loadingText={loadingText}
|
||||
data-cy="loading-button"
|
||||
isLoading={isLoading}
|
||||
icon={Download}
|
||||
>
|
||||
|
@ -34,7 +35,12 @@ export const Example = Template.bind({});
|
|||
|
||||
export function IsLoading() {
|
||||
return (
|
||||
<LoadingButton loadingText="loading" isLoading icon={Download}>
|
||||
<LoadingButton
|
||||
loadingText="loading"
|
||||
isLoading
|
||||
icon={Download}
|
||||
data-cy="loading-button"
|
||||
>
|
||||
Download
|
||||
</LoadingButton>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@ test('when isLoading is true should show spinner and loading text', async () =>
|
|||
const children = 'not visible';
|
||||
|
||||
const { queryByText, findByText, container } = render(
|
||||
<LoadingButton loadingText={loadingText} isLoading>
|
||||
<LoadingButton loadingText={loadingText} isLoading data-cy="loading-button">
|
||||
{children}
|
||||
</LoadingButton>
|
||||
);
|
||||
|
@ -27,7 +27,11 @@ test('should show children when false', async () => {
|
|||
const children = 'visible';
|
||||
|
||||
const { queryByText, container } = render(
|
||||
<LoadingButton loadingText={loadingText} isLoading={false}>
|
||||
<LoadingButton
|
||||
loadingText={loadingText}
|
||||
isLoading={false}
|
||||
data-cy="loading-button"
|
||||
>
|
||||
{children}
|
||||
</LoadingButton>
|
||||
);
|
||||
|
|
|
@ -47,6 +47,7 @@ export function ColumnVisibilityMenu<D extends object>({
|
|||
<div key={column.id}>
|
||||
<Checkbox
|
||||
checked={column.getIsVisible()}
|
||||
data-cy="column-visibility-checkbox"
|
||||
label={
|
||||
typeof column.columnDef.header === 'string'
|
||||
? column.columnDef.header
|
||||
|
|
|
@ -108,7 +108,8 @@ export function Datatable<D extends DefaultType>({
|
|||
);
|
||||
|
||||
const allColumns = useMemo(
|
||||
() => _.compact([!disableSelect && createSelectColumn<D>(), ...columns]),
|
||||
() =>
|
||||
_.compact([!disableSelect && createSelectColumn<D>(dataCy), ...columns]),
|
||||
[disableSelect, columns]
|
||||
);
|
||||
|
||||
|
@ -179,6 +180,7 @@ export function Datatable<D extends DefaultType>({
|
|||
description={description}
|
||||
renderTableActions={() => renderTableActions(selectedItems)}
|
||||
renderTableSettings={() => renderTableSettings(tableInstance)}
|
||||
data-cy={`${dataCy}-header`}
|
||||
/>
|
||||
|
||||
<DatatableContent<D>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { ReactNode } from 'react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { IconProps } from '@@/Icon';
|
||||
|
||||
import { SearchBar } from './SearchBar';
|
||||
|
@ -14,7 +16,7 @@ type Props = {
|
|||
renderTableActions?(): ReactNode;
|
||||
description?: ReactNode;
|
||||
titleId?: string;
|
||||
};
|
||||
} & AutomationTestingProps;
|
||||
|
||||
export function DatatableHeader({
|
||||
onSearchChange,
|
||||
|
@ -25,12 +27,19 @@ export function DatatableHeader({
|
|||
titleIcon,
|
||||
description,
|
||||
titleId,
|
||||
'data-cy': dataCy,
|
||||
}: Props) {
|
||||
if (!title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const searchBar = <SearchBar value={searchValue} onChange={onSearchChange} />;
|
||||
const searchBar = (
|
||||
<SearchBar
|
||||
value={searchValue}
|
||||
onChange={onSearchChange}
|
||||
data-cy={`${dataCy}-search-input`}
|
||||
/>
|
||||
);
|
||||
const tableActions = !!renderTableActions && (
|
||||
<Table.Actions>{renderTableActions()}</Table.Actions>
|
||||
);
|
||||
|
@ -44,6 +53,7 @@ export function DatatableHeader({
|
|||
label={title}
|
||||
icon={titleIcon}
|
||||
description={description}
|
||||
data-cy={dataCy}
|
||||
>
|
||||
{searchBar}
|
||||
{tableActions}
|
||||
|
|
|
@ -48,6 +48,7 @@ export function MultipleSelectionFilter({
|
|||
type="checkbox"
|
||||
checked={value.includes(option)}
|
||||
onChange={() => handleChange(option)}
|
||||
data-cy={`filter_${filterKey}_${index}`}
|
||||
/>
|
||||
<label htmlFor={`filter_${filterKey}_${index}`}>
|
||||
{option}
|
||||
|
|
|
@ -8,13 +8,15 @@ import {
|
|||
useReactTable,
|
||||
} from '@tanstack/react-table';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { defaultGetRowId } from './defaultGetRowId';
|
||||
import { Table } from './Table';
|
||||
import { NestedTable } from './NestedTable';
|
||||
import { DatatableContent } from './DatatableContent';
|
||||
import { BasicTableSettings, DefaultType } from './types';
|
||||
|
||||
interface Props<D extends DefaultType> {
|
||||
interface Props<D extends DefaultType> extends AutomationTestingProps {
|
||||
dataset: D[];
|
||||
columns: TableOptions<D>['columns'];
|
||||
|
||||
|
@ -41,6 +43,7 @@ export function NestedDatatable<D extends DefaultType>({
|
|||
isLoading,
|
||||
initialSortBy,
|
||||
search,
|
||||
'data-cy': dataCy,
|
||||
'aria-label': ariaLabel,
|
||||
}: Props<D>) {
|
||||
const tableInstance = useReactTable<D>({
|
||||
|
@ -74,6 +77,7 @@ export function NestedDatatable<D extends DefaultType>({
|
|||
emptyContentLabel={emptyContentLabel}
|
||||
renderRow={(row) => <Table.Row<D> cells={row.getVisibleCells()} />}
|
||||
aria-label={ariaLabel}
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
</Table.Container>
|
||||
</NestedTable>
|
||||
|
|
|
@ -25,6 +25,7 @@ export function QuickActionsSettings({ actions }: Props) {
|
|||
{actions.map(({ id, label }) => (
|
||||
<Checkbox
|
||||
key={id}
|
||||
data-cy="quick-actions-checkbox"
|
||||
label={label}
|
||||
id={`quick-actions-${id}`}
|
||||
checked={!settings.hiddenQuickActions.includes(id)}
|
||||
|
|
|
@ -37,15 +37,21 @@ export function SearchBar({
|
|||
<Search className="searchIcon lucide shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
data-cy={dataCy}
|
||||
className="searchInput"
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
data-cy={dataCy}
|
||||
aria-label="Search input"
|
||||
/>
|
||||
{children}
|
||||
<Button onClick={onClear} icon={X} color="none" disabled={!searchValue} />
|
||||
<Button
|
||||
onClick={onClear}
|
||||
icon={X}
|
||||
color="none"
|
||||
disabled={!searchValue}
|
||||
data-cy={`${dataCy}-clear-button`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export function TableSettingsMenuAutoRefresh({ onChange, value }: Props) {
|
|||
<>
|
||||
<Checkbox
|
||||
id="settings-auto-refresh"
|
||||
data-cy="settings-auto-refresh"
|
||||
label="Auto refresh"
|
||||
checked={isEnabled}
|
||||
onChange={(e) => onChange(e.target.checked ? 10 : 0)}
|
||||
|
@ -30,6 +31,7 @@ export function TableSettingsMenuAutoRefresh({ onChange, value }: Props) {
|
|||
<div>
|
||||
<label htmlFor="settings_refresh_rate">Refresh rate</label>
|
||||
<select
|
||||
data-cy="settings-refresh-rate"
|
||||
id="settings_refresh_rate"
|
||||
className="small-select"
|
||||
value={value}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { defaultGetRowId } from './defaultGetRowId';
|
|||
export function buildNameColumn<T extends DefaultType>(
|
||||
nameKey: keyof T,
|
||||
path: string,
|
||||
dataCy: string,
|
||||
idParam = 'id',
|
||||
idGetter: (row: T) => string = defaultGetRowId<T>
|
||||
): ColumnDef<T> {
|
||||
|
@ -35,6 +36,7 @@ export function buildNameColumn<T extends DefaultType>(
|
|||
to={path}
|
||||
params={{ [idParam]: idGetter(row.original) }}
|
||||
title={name}
|
||||
data-cy={`${dataCy}_${name}`}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
|
|
|
@ -18,6 +18,7 @@ export function buildExpandColumn<T extends DefaultType>(): ColumnDef<T> {
|
|||
color="none"
|
||||
icon={table.getIsAllRowsExpanded() ? ChevronDown : ChevronUp}
|
||||
title="Expand all"
|
||||
data-cy="expand-all-rows-button"
|
||||
aria-label="Expand all rows"
|
||||
/>
|
||||
)
|
||||
|
@ -35,6 +36,7 @@ export function buildExpandColumn<T extends DefaultType>(): ColumnDef<T> {
|
|||
color="none"
|
||||
icon={row.getIsExpanded() ? ChevronDown : ChevronUp}
|
||||
title={row.getIsExpanded() ? 'Collapse' : 'Expand'}
|
||||
data-cy={`expand-row-button_${row.index}`}
|
||||
aria-label={row.getIsExpanded() ? 'Collapse row' : 'Expand row'}
|
||||
aria-expanded={row.getIsExpanded()}
|
||||
/>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ColumnDef, Row } from '@tanstack/react-table';
|
|||
|
||||
import { Checkbox } from '@@/form-components/Checkbox';
|
||||
|
||||
export function createSelectColumn<T>(): ColumnDef<T> {
|
||||
export function createSelectColumn<T>(dataCy: string): ColumnDef<T> {
|
||||
let lastSelectedId = '';
|
||||
|
||||
return {
|
||||
|
@ -10,6 +10,7 @@ export function createSelectColumn<T>(): ColumnDef<T> {
|
|||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
id="select-all"
|
||||
data-cy={`select-all-checkbox-${dataCy}`}
|
||||
checked={table.getIsAllRowsSelected()}
|
||||
indeterminate={table.getIsSomeRowsSelected()}
|
||||
onChange={table.getToggleAllRowsSelectedHandler()}
|
||||
|
@ -24,6 +25,7 @@ export function createSelectColumn<T>(): ColumnDef<T> {
|
|||
cell: ({ row, table }) => (
|
||||
<Checkbox
|
||||
id={`select-row-${row.id}`}
|
||||
data-cy={`select-row-checkbox_${row.id}`}
|
||||
checked={row.getIsSelected()}
|
||||
indeterminate={row.getIsSomeSelected()}
|
||||
onChange={row.getToggleSelectedHandler()}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { useDebounce } from '@/react/hooks/useDebounce';
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { Option } from '@@/form-components/PortainerSelect';
|
||||
|
||||
|
@ -23,6 +24,7 @@ export function AutocompleteSelect({
|
|||
searchResults,
|
||||
readOnly,
|
||||
inputId,
|
||||
'data-cy': dataCy,
|
||||
}: {
|
||||
value: string;
|
||||
/**
|
||||
|
@ -35,7 +37,7 @@ export function AutocompleteSelect({
|
|||
searchResults?: Option<string>[];
|
||||
readOnly?: boolean;
|
||||
inputId: string;
|
||||
}) {
|
||||
} & AutomationTestingProps) {
|
||||
const [searchTerm, setSearchTerm] = useDebounce(value, onChange);
|
||||
const [selected, setSelected] = useState(false);
|
||||
|
||||
|
@ -54,6 +56,7 @@ export function AutocompleteSelect({
|
|||
readOnly={readOnly}
|
||||
id={inputId}
|
||||
autoComplete="off"
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
{!selected && searchResults && searchResults.length > 0 && (
|
||||
<ComboboxPopover>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import clsx from 'clsx';
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { ButtonGroup, Size } from '@@/buttons/ButtonGroup';
|
||||
import { Button } from '@@/buttons';
|
||||
|
||||
|
@ -42,6 +44,7 @@ export function ButtonSelector<T extends string | number | boolean>({
|
|||
{options.map((option) => (
|
||||
<OptionItem
|
||||
key={option.value.toString()}
|
||||
data-cy={`button-selector-option-${option.value}`}
|
||||
selected={value === option.value}
|
||||
onChange={() => onChange(option.value)}
|
||||
disabled={disabled || option.disabled}
|
||||
|
@ -67,7 +70,8 @@ function OptionItem({
|
|||
onChange,
|
||||
disabled,
|
||||
readOnly,
|
||||
}: PropsWithChildren<OptionItemProps>) {
|
||||
'data-cy': dataCy,
|
||||
}: PropsWithChildren<OptionItemProps> & AutomationTestingProps) {
|
||||
return (
|
||||
<Button
|
||||
color="light"
|
||||
|
@ -79,10 +83,12 @@ function OptionItem({
|
|||
},
|
||||
'!static !z-auto'
|
||||
)}
|
||||
data-cy={dataCy}
|
||||
>
|
||||
{children}
|
||||
<input
|
||||
type="radio"
|
||||
data-cy={`${dataCy}-radio-input`}
|
||||
checked={selected}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
|
|
|
@ -18,6 +18,7 @@ interface Props extends HTMLProps<HTMLInputElement> {
|
|||
role?: string;
|
||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||
bold?: boolean;
|
||||
'data-cy': string;
|
||||
}
|
||||
|
||||
export const Checkbox = forwardRef<HTMLInputElement, Props>(
|
||||
|
@ -30,6 +31,7 @@ export const Checkbox = forwardRef<HTMLInputElement, Props>(
|
|||
checked,
|
||||
onChange,
|
||||
bold = true,
|
||||
'data-cy': dataCy,
|
||||
...props
|
||||
}: Props,
|
||||
ref
|
||||
|
@ -58,6 +60,7 @@ export const Checkbox = forwardRef<HTMLInputElement, Props>(
|
|||
ref={resolvedRef}
|
||||
onChange={onChange}
|
||||
checked={checked}
|
||||
data-cy={dataCy}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { List } from 'lucide-react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { CodeEditor } from '@@/CodeEditor';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
import { Button } from '@@/buttons';
|
||||
|
@ -11,11 +13,12 @@ export function AdvancedMode({
|
|||
value,
|
||||
onChange,
|
||||
onSimpleModeClick,
|
||||
'data-cy': dataCy,
|
||||
}: {
|
||||
value: Values;
|
||||
onChange: (value: Values) => void;
|
||||
onSimpleModeClick: () => void;
|
||||
}) {
|
||||
} & AutomationTestingProps) {
|
||||
const editorValue = convertToArrayOfStrings(value).join('\n');
|
||||
|
||||
return (
|
||||
|
@ -26,6 +29,7 @@ export function AdvancedMode({
|
|||
icon={List}
|
||||
className="!ml-0 p-0 hover:no-underline"
|
||||
onClick={onSimpleModeClick}
|
||||
data-cy="env-simple-mode-button"
|
||||
>
|
||||
Simple mode
|
||||
</Button>
|
||||
|
@ -40,6 +44,7 @@ export function AdvancedMode({
|
|||
value={editorValue}
|
||||
onChange={handleEditorChange}
|
||||
placeholder="e.g. key=value"
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -18,6 +18,7 @@ export function EnvironmentVariableItem({
|
|||
<div className="w-1/2">
|
||||
<InputLabeled
|
||||
className="w-full"
|
||||
data-cy={`env-name_${index}`}
|
||||
label="name"
|
||||
required
|
||||
value={item.name}
|
||||
|
@ -31,7 +32,7 @@ export function EnvironmentVariableItem({
|
|||
/>
|
||||
{error && (
|
||||
<div>
|
||||
<FormError className="mt-1 !mb-0">
|
||||
<FormError className="!mb-0 mt-1">
|
||||
{Object.values(error)[0]}
|
||||
</FormError>
|
||||
</div>
|
||||
|
@ -39,6 +40,7 @@ export function EnvironmentVariableItem({
|
|||
</div>
|
||||
<InputLabeled
|
||||
className="w-1/2"
|
||||
data-cy={`env-value_${index}`}
|
||||
label="value"
|
||||
value={item.value}
|
||||
onChange={(e) => handleChange({ value: e.target.value })}
|
||||
|
|
|
@ -34,6 +34,7 @@ export function EnvironmentVariablesFieldset({
|
|||
) : (
|
||||
<AdvancedMode
|
||||
onSimpleModeClick={() => setSimpleMode(true)}
|
||||
data-cy="env-var-advanced-mode"
|
||||
onChange={onChange}
|
||||
value={values}
|
||||
/>
|
||||
|
|
|
@ -34,6 +34,7 @@ export function SimpleMode({
|
|||
icon={Edit}
|
||||
className="!ml-0 p-0 hover:no-underline"
|
||||
onClick={onAdvancedModeClick}
|
||||
data-cy="environment-variables-advanced-mode-button"
|
||||
>
|
||||
Advanced mode
|
||||
</Button>
|
||||
|
@ -50,6 +51,7 @@ export function SimpleMode({
|
|||
item={EnvironmentVariableItem}
|
||||
errors={errors}
|
||||
canUndoDelete={canUndoDelete}
|
||||
data-cy="simple-environment-variable-fieldset"
|
||||
/>
|
||||
|
||||
<div className="flex gap-2">
|
||||
|
@ -60,6 +62,7 @@ export function SimpleMode({
|
|||
className="!ml-0"
|
||||
color="default"
|
||||
icon={Plus}
|
||||
data-cy="add-environment-variable-button"
|
||||
>
|
||||
Add an environment variable
|
||||
</Button>
|
||||
|
@ -84,6 +87,7 @@ function FileEnv({ onChooseFile }: { onChooseFile: (file: Values) => void }) {
|
|||
accept=".env"
|
||||
value={file}
|
||||
color="default"
|
||||
data-cy="load-environment-variables-from-file-button"
|
||||
/>
|
||||
|
||||
{fileTooBig && (
|
||||
|
|
|
@ -28,6 +28,7 @@ function Example({ title }: Args) {
|
|||
value={value}
|
||||
title={title}
|
||||
inputId="file-field"
|
||||
data-cy="file-upload-field"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ test('render should make the file button clickable and fire onChange event after
|
|||
const { findByText, findByLabelText } = render(
|
||||
<FileUploadField
|
||||
title="test button"
|
||||
data-cy="file-input"
|
||||
onChange={onClick}
|
||||
inputId="file-field"
|
||||
/>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { ChangeEvent, ComponentProps, createRef } from 'react';
|
||||
import { Upload, XCircle } from 'lucide-react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import styles from './FileUploadField.module.css';
|
||||
|
||||
export interface Props {
|
||||
export interface Props extends AutomationTestingProps {
|
||||
onChange(value: File): void;
|
||||
value?: File | null;
|
||||
accept?: string;
|
||||
|
@ -26,6 +28,7 @@ export function FileUploadField({
|
|||
inputId,
|
||||
color = 'primary',
|
||||
name,
|
||||
'data-cy': dataCy,
|
||||
}: Props) {
|
||||
const fileRef = createRef<HTMLInputElement>();
|
||||
|
||||
|
@ -47,6 +50,7 @@ export function FileUploadField({
|
|||
color={color}
|
||||
onClick={handleButtonClick}
|
||||
className={styles.fileButton}
|
||||
data-cy={dataCy}
|
||||
icon={Upload}
|
||||
>
|
||||
{title}
|
||||
|
|
|
@ -31,6 +31,7 @@ function Example({ title }: Args) {
|
|||
description={
|
||||
<span>You can upload a Compose file from your computer.</span>
|
||||
}
|
||||
data-cy="file-upload-form"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ test('render should include description', async () => {
|
|||
title="test button"
|
||||
onChange={onClick}
|
||||
description={<span>test description</span>}
|
||||
data-cy="test"
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { FormSectionTitle } from '@@/form-components/FormSectionTitle';
|
||||
import { FileUploadField } from '@@/form-components/FileUpload/FileUploadField';
|
||||
|
||||
|
@ -17,7 +19,8 @@ export function FileUploadForm({
|
|||
title = 'Select a file',
|
||||
required = false,
|
||||
description,
|
||||
}: PropsWithChildren<Props>) {
|
||||
'data-cy': dataCy,
|
||||
}: PropsWithChildren<Props> & AutomationTestingProps) {
|
||||
return (
|
||||
<div className="file-upload-form">
|
||||
<FormSectionTitle>Upload</FormSectionTitle>
|
||||
|
@ -28,6 +31,7 @@ export function FileUploadForm({
|
|||
<div className="col-sm-12">
|
||||
<FileUploadField
|
||||
inputId="file-upload-field"
|
||||
data-cy={dataCy}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
title={title}
|
||||
|
|
|
@ -42,6 +42,7 @@ function TextField({
|
|||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
data-cy="input"
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
|
@ -79,6 +80,7 @@ function SelectField({
|
|||
>
|
||||
<Select
|
||||
className="form-control"
|
||||
data-cy="select"
|
||||
value={value}
|
||||
onChange={(e) => setValue(parseInt(e.target.value, 10))}
|
||||
options={options}
|
||||
|
|
|
@ -22,6 +22,7 @@ export function TextField({ disabled }: Args) {
|
|||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
disabled={disabled}
|
||||
data-cy="docker-logging-options-input"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import clsx from 'clsx';
|
||||
import { forwardRef, InputHTMLAttributes, Ref } from 'react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
export const InputWithRef = forwardRef<
|
||||
HTMLInputElement,
|
||||
InputHTMLAttributes<HTMLInputElement>
|
||||
InputHTMLAttributes<HTMLInputElement> & AutomationTestingProps
|
||||
>(
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
(props, ref) => <Input {...props} mRef={ref} />
|
||||
|
@ -14,10 +16,11 @@ export function Input({
|
|||
mRef: ref,
|
||||
value,
|
||||
type,
|
||||
'data-cy': dataCy,
|
||||
...props
|
||||
}: InputHTMLAttributes<HTMLInputElement> & {
|
||||
mRef?: Ref<HTMLInputElement>;
|
||||
}) {
|
||||
} & AutomationTestingProps) {
|
||||
return (
|
||||
<input
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
|
@ -26,6 +29,7 @@ export function Input({
|
|||
value={type === 'number' && Number.isNaN(value) ? '' : value} // avoid the `"NaN" cannot be parsed, or is out of range.` error for an empty number input
|
||||
ref={ref}
|
||||
className={clsx('form-control', className)}
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ function TextInput() {
|
|||
return (
|
||||
<InputLabeled
|
||||
label="label"
|
||||
data-cy="input"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
|
@ -28,6 +29,7 @@ function NumberInput() {
|
|||
return (
|
||||
<InputLabeled
|
||||
label="label"
|
||||
data-cy="input"
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.valueAsNumber)}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { ComponentProps, InputHTMLAttributes } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { InputGroup } from '../InputGroup';
|
||||
|
||||
export function InputLabeled({
|
||||
|
@ -17,7 +19,8 @@ export function InputLabeled({
|
|||
className?: string;
|
||||
size?: ComponentProps<typeof InputGroup>['size'];
|
||||
needsDeletion?: boolean;
|
||||
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'children'>) {
|
||||
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'children'> &
|
||||
AutomationTestingProps) {
|
||||
return (
|
||||
<InputGroup
|
||||
className={clsx(className, needsDeletion && 'striked')}
|
||||
|
|
|
@ -23,6 +23,7 @@ export function Example({ disabled }: Args) {
|
|||
return (
|
||||
<Select
|
||||
value={value}
|
||||
data-cy="select"
|
||||
onChange={(e) => setValue(parseInt(e.target.value, 10))}
|
||||
disabled={disabled}
|
||||
options={options}
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
import clsx from 'clsx';
|
||||
import { SelectHTMLAttributes } from 'react';
|
||||
|
||||
export interface Option<T extends string | number> {
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
export interface Option<T extends string | number>
|
||||
extends Partial<AutomationTestingProps> {
|
||||
value: T;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface Props<T extends string | number> {
|
||||
interface Props<T extends string | number> extends AutomationTestingProps {
|
||||
options: Option<T>[];
|
||||
}
|
||||
|
||||
export function Select<T extends number | string>({
|
||||
options,
|
||||
className,
|
||||
'data-cy': dataCy,
|
||||
...props
|
||||
}: Props<T> & SelectHTMLAttributes<HTMLSelectElement>) {
|
||||
return (
|
||||
|
@ -20,9 +25,15 @@ export function Select<T extends number | string>({
|
|||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
className={clsx('form-control', className)}
|
||||
data-cy={dataCy}
|
||||
>
|
||||
{options.map((item) => (
|
||||
<option value={item.value} key={item.value}>
|
||||
<option
|
||||
value={item.value}
|
||||
key={item.value}
|
||||
disabled={item.disabled}
|
||||
data-cy={`${dataCy}-${item.value}`}
|
||||
>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
|
|
10
app/react/components/form-components/Input/Select.tsx.rej
Normal file
10
app/react/components/form-components/Input/Select.tsx.rej
Normal file
|
@ -0,0 +1,10 @@
|
|||
diff a/app/react/components/form-components/Input/Select.tsx b/app/react/components/form-components/Input/Select.tsx (rejected hunks)
|
||||
@@ -10,7 +10,7 @@ export interface Option<T extends string | number>
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
-interface Props<T extends string | number> {
|
||||
+interface Props<T extends string | number> extends AutomationTestingProps {
|
||||
options: Option<T>[];
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
import clsx from 'clsx';
|
||||
import { TextareaHTMLAttributes } from 'react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
export function TextArea({
|
||||
className,
|
||||
...props
|
||||
}: TextareaHTMLAttributes<HTMLTextAreaElement>) {
|
||||
}: TextareaHTMLAttributes<HTMLTextAreaElement> & AutomationTestingProps) {
|
||||
return (
|
||||
<textarea
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
|
|
|
@ -20,6 +20,7 @@ function BasicExample() {
|
|||
<InputGroup.Addon>@</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
value={value1}
|
||||
data-cy="input"
|
||||
onChange={(e) => setValue1(e.target.value)}
|
||||
placeholder="Username"
|
||||
aria-describedby="basic-addon1"
|
||||
|
@ -29,6 +30,7 @@ function BasicExample() {
|
|||
<InputGroup>
|
||||
<InputGroup.Input
|
||||
value={value1}
|
||||
data-cy="input"
|
||||
onChange={(e) => setValue1(e.target.value)}
|
||||
placeholder="Recipient's username"
|
||||
aria-describedby="basic-addon2"
|
||||
|
@ -40,6 +42,7 @@ function BasicExample() {
|
|||
<InputGroup.Addon>$</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
type="number"
|
||||
data-cy="input"
|
||||
value={valueNumber}
|
||||
onChange={(e) => setValueNumber(parseInt(e.target.value, 10))}
|
||||
aria-label="Amount (to the nearest dollar)"
|
||||
|
@ -52,6 +55,7 @@ function BasicExample() {
|
|||
<InputGroup.Addon>https://example.com/users/</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
value={value1}
|
||||
data-cy="input"
|
||||
onChange={(e) => setValue1(e.target.value)}
|
||||
id="basic-url"
|
||||
aria-describedby="basic-addon3"
|
||||
|
@ -75,6 +79,7 @@ function Addons() {
|
|||
</InputGroup.ButtonWrapper>
|
||||
<InputGroup.Input
|
||||
value={value1}
|
||||
data-cy="input"
|
||||
onChange={(e) => setValue1(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
@ -83,10 +88,11 @@ function Addons() {
|
|||
<InputGroup>
|
||||
<InputGroup.Input
|
||||
value={value2}
|
||||
data-cy="input"
|
||||
onChange={(e) => setValue2(e.target.value)}
|
||||
/>
|
||||
<InputGroup.Addon>
|
||||
<input type="checkbox" />
|
||||
<input type="checkbox" data-cy="checkbox" />
|
||||
</InputGroup.Addon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
@ -102,6 +108,7 @@ function Sizing() {
|
|||
<InputGroup.Addon>Small</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
value={value}
|
||||
data-cy="input"
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
@ -110,6 +117,7 @@ function Sizing() {
|
|||
<InputGroup.Addon>Default</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
value={value}
|
||||
data-cy="input"
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
@ -118,6 +126,7 @@ function Sizing() {
|
|||
<InputGroup.Addon>Large</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
value={value}
|
||||
data-cy="input"
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
|
|
@ -22,6 +22,7 @@ function Defaults() {
|
|||
label="default example"
|
||||
value={values}
|
||||
onChange={(value) => setValues(value)}
|
||||
data-cy="input-list-default-example"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -37,6 +38,7 @@ function ListWithUndoDeletion() {
|
|||
value={values}
|
||||
onChange={(value) => setValues(value)}
|
||||
canUndoDelete
|
||||
data-cy="input-list-with-undo-deletion"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -71,6 +73,7 @@ function ListWithInputAndSelect({
|
|||
movable={movable}
|
||||
itemBuilder={() => ({ value: 0, select: '', id: values.length })}
|
||||
tooltip={tooltip}
|
||||
data-cy="input-list-with-select-and-input"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -96,9 +99,11 @@ function SelectAndInputItem({
|
|||
onChange={(e) =>
|
||||
onChange({ ...item, value: parseInt(e.target.value, 10) })
|
||||
}
|
||||
data-cy="input"
|
||||
/>
|
||||
<Select
|
||||
onChange={(e) => onChange({ ...item, select: e.target.value })}
|
||||
data-cy="select"
|
||||
options={[
|
||||
{ label: 'option1', value: 'option1' },
|
||||
{ label: 'option2', value: 'option2' },
|
||||
|
|
|
@ -3,6 +3,8 @@ import { FormikErrors } from 'formik';
|
|||
import { ArrowDown, ArrowUp, Plus, RotateCw, Trash2 } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
import { Tooltip } from '@@/Tip/Tooltip';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
@ -53,10 +55,11 @@ type RenderItemFunction<T> = (
|
|||
item: T,
|
||||
onChange: (value: T) => void,
|
||||
index: number,
|
||||
dataCy: string,
|
||||
error?: ItemError<T>
|
||||
) => React.ReactNode;
|
||||
|
||||
interface Props<T> {
|
||||
interface Props<T> extends AutomationTestingProps {
|
||||
label?: string;
|
||||
value: T[];
|
||||
onChange(value: T[], e: OnChangeEvent<T>): void;
|
||||
|
@ -71,9 +74,7 @@ interface Props<T> {
|
|||
errors?: ArrayError<T[]>;
|
||||
textTip?: string;
|
||||
isAddButtonHidden?: boolean;
|
||||
addButtonDataCy?: string;
|
||||
isDeleteButtonHidden?: boolean;
|
||||
deleteButtonDataCy?: string;
|
||||
disabled?: boolean;
|
||||
addButtonError?: string;
|
||||
readOnly?: boolean;
|
||||
|
@ -95,9 +96,8 @@ export function InputList<T = DefaultType>({
|
|||
errors,
|
||||
textTip,
|
||||
isAddButtonHidden = false,
|
||||
addButtonDataCy,
|
||||
isDeleteButtonHidden = false,
|
||||
deleteButtonDataCy,
|
||||
'data-cy': dataCy,
|
||||
disabled,
|
||||
addButtonError,
|
||||
readOnly,
|
||||
|
@ -161,6 +161,7 @@ export function InputList<T = DefaultType>({
|
|||
item,
|
||||
(value: T) => handleChangeItem(key, value),
|
||||
index,
|
||||
dataCy,
|
||||
error
|
||||
)
|
||||
)}
|
||||
|
@ -173,6 +174,7 @@ export function InputList<T = DefaultType>({
|
|||
onClick={() => handleMoveUp(index)}
|
||||
className="vertical-center btn-only-icon"
|
||||
icon={ArrowUp}
|
||||
data-cy={`${dataCy}-move-up_${index}`}
|
||||
/>
|
||||
<Button
|
||||
size="medium"
|
||||
|
@ -181,6 +183,7 @@ export function InputList<T = DefaultType>({
|
|||
onClick={() => handleMoveDown(index)}
|
||||
className="vertical-center btn-only-icon"
|
||||
icon={ArrowDown}
|
||||
data-cy={`${dataCy}-move-down_${index}`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -190,7 +193,7 @@ export function InputList<T = DefaultType>({
|
|||
size="medium"
|
||||
onClick={() => handleRemoveItem(key, item)}
|
||||
className="vertical-center btn-only-icon"
|
||||
data-cy={`${deleteButtonDataCy}_${index}`}
|
||||
data-cy={`${dataCy}RemoveButton_${index}`}
|
||||
icon={Trash2}
|
||||
/>
|
||||
)}
|
||||
|
@ -203,7 +206,7 @@ export function InputList<T = DefaultType>({
|
|||
initialItemsCount={initialItemsCount.current}
|
||||
handleRemoveItem={handleRemoveItem}
|
||||
handleToggleNeedsDeletion={toggleNeedsDeletion}
|
||||
dataCy={`${deleteButtonDataCy}_${index}`}
|
||||
dataCy={`${dataCy}RemoveButton_${index}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -224,7 +227,7 @@ export function InputList<T = DefaultType>({
|
|||
className="!ml-0"
|
||||
size="small"
|
||||
icon={Plus}
|
||||
data-cy={addButtonDataCy}
|
||||
data-cy={`${dataCy}AddButton`}
|
||||
>
|
||||
{addLabel}
|
||||
</Button>
|
||||
|
@ -332,7 +335,9 @@ function DefaultItem({
|
|||
error,
|
||||
disabled,
|
||||
readOnly,
|
||||
}: ItemProps<DefaultType>) {
|
||||
index,
|
||||
'data-cy': dataCy,
|
||||
}: ItemProps<DefaultType> & AutomationTestingProps) {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
|
@ -341,6 +346,7 @@ function DefaultItem({
|
|||
className={clsx('!w-full', item.needsDeletion && 'striked')}
|
||||
disabled={disabled || item.needsDeletion}
|
||||
readOnly={readOnly}
|
||||
data-cy={`${dataCy}RemoveButton_${index}`}
|
||||
/>
|
||||
{error && <FormError>{error}</FormError>}
|
||||
</>
|
||||
|
@ -351,10 +357,17 @@ function renderDefaultItem(
|
|||
item: DefaultType,
|
||||
onChange: (value: DefaultType) => void,
|
||||
index: number,
|
||||
dataCy: string,
|
||||
error?: ItemError<DefaultType>
|
||||
) {
|
||||
return (
|
||||
<DefaultItem item={item} onChange={onChange} error={error} index={index} />
|
||||
<DefaultItem
|
||||
item={item}
|
||||
onChange={onChange}
|
||||
error={error}
|
||||
index={index}
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export interface Props {
|
|||
step: number;
|
||||
value: number;
|
||||
onChange: (value: number | number[]) => void;
|
||||
dataCy?: string;
|
||||
dataCy: string;
|
||||
// true if you want to always show the tooltip
|
||||
visibleTooltip?: boolean;
|
||||
disabled?: boolean;
|
||||
|
|
|
@ -13,7 +13,15 @@ export function Example() {
|
|||
setIsChecked(!isChecked);
|
||||
}
|
||||
|
||||
return <Switch name="name" checked={isChecked} onChange={onChange} id="id" />;
|
||||
return (
|
||||
<Switch
|
||||
name="name"
|
||||
data-cy="switch"
|
||||
checked={isChecked}
|
||||
onChange={onChange}
|
||||
id="id"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface Args {
|
||||
|
@ -21,7 +29,15 @@ interface Args {
|
|||
}
|
||||
|
||||
function Template({ checked }: Args) {
|
||||
return <Switch name="name" checked={checked} onChange={() => {}} id="id" />;
|
||||
return (
|
||||
<Switch
|
||||
name="name"
|
||||
data-cy="switch"
|
||||
checked={checked}
|
||||
onChange={() => {}}
|
||||
id="id"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const Checked: Story<Args> = Template.bind({});
|
||||
|
|
|
@ -8,7 +8,13 @@ function renderDefault({
|
|||
checked = false,
|
||||
}: Partial<PropsWithChildren<Props>> = {}) {
|
||||
return render(
|
||||
<Switch id="id" name={name} checked={checked} onChange={() => {}} />
|
||||
<Switch
|
||||
id="id"
|
||||
name={name}
|
||||
checked={checked}
|
||||
onChange={() => {}}
|
||||
data-cy="switch"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ export function Example() {
|
|||
return (
|
||||
<SwitchField
|
||||
name="name"
|
||||
data-cy="switch-field-example"
|
||||
checked={isChecked}
|
||||
onChange={onChange}
|
||||
label="Example"
|
||||
|
@ -33,6 +34,7 @@ function Template({ checked, label, labelClass }: Args) {
|
|||
return (
|
||||
<SwitchField
|
||||
name="name"
|
||||
data-cy="switch-field-example"
|
||||
checked={checked}
|
||||
onChange={() => {}}
|
||||
label={label}
|
||||
|
|
|
@ -12,6 +12,7 @@ function renderDefault({
|
|||
return render(
|
||||
<SwitchField
|
||||
label={label}
|
||||
data-cy="switch-field"
|
||||
name={name}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
|
|
|
@ -69,6 +69,7 @@ export function Dialog<T>({
|
|||
color={button.color}
|
||||
key={index}
|
||||
size="medium"
|
||||
data-cy={button.dataCy}
|
||||
>
|
||||
{button.label} {button.timeout && count ? `(${count})` : null}
|
||||
</Button>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { ReactNode, useState } from 'react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
|
||||
import { ModalType, type ButtonOptions } from './types';
|
||||
|
@ -16,6 +18,7 @@ function SwitchPrompt({
|
|||
modalType,
|
||||
message,
|
||||
defaultValue = false,
|
||||
'data-cy': dataCy,
|
||||
}: {
|
||||
onSubmit: OnSubmit<{ value: boolean }>;
|
||||
title: string;
|
||||
|
@ -24,7 +27,7 @@ function SwitchPrompt({
|
|||
modalType?: ModalType;
|
||||
message?: ReactNode;
|
||||
defaultValue?: boolean;
|
||||
}) {
|
||||
} & AutomationTestingProps) {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
|
||||
return (
|
||||
|
@ -36,6 +39,7 @@ function SwitchPrompt({
|
|||
{message && <div className="mb-3">{message}</div>}
|
||||
<SwitchField
|
||||
name="value"
|
||||
data-cy={dataCy}
|
||||
label={switchLabel}
|
||||
checked={value}
|
||||
onChange={setValue}
|
||||
|
@ -56,12 +60,15 @@ export async function openSwitchPrompt(
|
|||
modalType,
|
||||
message,
|
||||
defaultValue,
|
||||
'data-cy': dataCy,
|
||||
}: {
|
||||
confirmButton?: ButtonOptions<true>;
|
||||
modalType?: ModalType;
|
||||
message?: ReactNode;
|
||||
defaultValue?: boolean;
|
||||
} = {}
|
||||
} & AutomationTestingProps = {
|
||||
'data-cy': 'switch-prompt',
|
||||
}
|
||||
) {
|
||||
return openModal(SwitchPrompt, {
|
||||
confirmButton,
|
||||
|
@ -70,5 +77,6 @@ export async function openSwitchPrompt(
|
|||
modalType,
|
||||
message,
|
||||
defaultValue,
|
||||
'data-cy': dataCy,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ export interface ButtonOptions<TValue = undefined> {
|
|||
color?: ComponentProps<typeof Button>['color'];
|
||||
value?: TValue;
|
||||
timeout?: number;
|
||||
dataCy: string;
|
||||
}
|
||||
|
||||
export interface ButtonsOptions<T> {
|
||||
|
|
|
@ -7,15 +7,20 @@ import { ButtonOptions } from './types';
|
|||
export function buildConfirmButton(
|
||||
label = 'Confirm',
|
||||
color: ComponentProps<typeof Button>['color'] = 'primary',
|
||||
timeout = 0
|
||||
timeout = 0,
|
||||
dataCy = 'modal-confirm-button'
|
||||
): ButtonOptions<true> {
|
||||
return { label, color, value: true, timeout };
|
||||
return { label, color, value: true, timeout, dataCy };
|
||||
}
|
||||
|
||||
export function buildCancelButton(label = 'Cancel'): ButtonOptions<false> {
|
||||
export function buildCancelButton(
|
||||
label = 'Cancel',
|
||||
dataCy = 'modal-cancel-button'
|
||||
): ButtonOptions<false> {
|
||||
return {
|
||||
label,
|
||||
color: 'default',
|
||||
value: false,
|
||||
dataCy,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue