mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GrafanaUI: Define tooltip or aria-label as required for IconButton (#69699)
* refactor: modify interfaces to make tooltip or aria-label required * refactor: change functionality around aria-label and tooltip * refactor: change and add information in storybook documentation * refactor: remove default from tooltip * refactor: IconButton to make tooltip or aria-label required * refactor: Fix tests * refactor: Fix tests * refactor: Fix tests * refactor: Fix tests * feat: add migration guide for breaking change * feat: add latest requirements to storybook docs * refactor: separate iconbutton story with and without tooltip * refactor: remove exported baseArgs * refactor: clean up and restructure original story * refactor: adjust styling * refactor: enable control for tooltip * refactor: clean up * refactor: enable control for aria-label * refactor: fix theme getting the wrong theme * refactor: fix tests * refactor: adjust story * refactor: remove confusing story * refactor: adjust controls for stories
This commit is contained in:
parent
8dc9fcf88b
commit
d64b6264ff
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
description: Guide for migrating plugins from Grafana v10.0.x to v10.1.x
|
||||||
|
keywords:
|
||||||
|
- grafana
|
||||||
|
- plugins
|
||||||
|
- migration
|
||||||
|
- plugin
|
||||||
|
- documentation
|
||||||
|
title: Migrate plugins from Grafana 10.0.x to 10.1.x
|
||||||
|
menutitle: v10.0.x to v10.1.x
|
||||||
|
weight: 1900
|
||||||
|
---
|
||||||
|
|
||||||
|
# Migrate plugins from Grafana version 10.0.x to 10.1.x
|
||||||
|
|
||||||
|
## Accessibility update for IconButton component in grafana-ui
|
||||||
|
|
||||||
|
We updated the component's TypeScript interface due to an accessibility issue. This change was delivered to the core `grafana` repo with [PR 69699](https://github.com/grafana/grafana/pull/69699).
|
||||||
|
|
||||||
|
In case you are using the IconButton component in your plugin you will get TypeScript errors related to the change.
|
||||||
|
|
||||||
|
**Recommended actions:**
|
||||||
|
|
||||||
|
- Review use cases of IconButton in your plugin.
|
||||||
|
- Add a meaningful tooltip which the component will also use as an aria-label.
|
||||||
|
- Another option is to set an aria-label. In this case a tooltip will not be shown.
|
||||||
|
|
||||||
|
**Please note:**
|
||||||
|
The IconButton used to have a property called `ariaLabel` which got deprecated with this change. You can now use the regular property `aria-label` instead.
|
@ -1,17 +1,23 @@
|
|||||||
import { Meta, ArgTypes } from '@storybook/blocks';
|
import { Meta, ArgTypes } from '@storybook/blocks';
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
|
import { Icon } from '../Icon/Icon';
|
||||||
import { Alert } from '../Alert/Alert';
|
import { Alert } from '../Alert/Alert';
|
||||||
|
|
||||||
<Meta title="MDX|IconButton" component={IconButton} />
|
<Meta title="MDX|IconButton" component={IconButton} />
|
||||||
|
|
||||||
# IconButton
|
# IconButton
|
||||||
|
|
||||||
This component looks just like an icon but behaves like a button. It fulfils an action when you click it and has hover and focus states. You can choose which icon size you would like to use.
|
This component looks just like an icon but behaves like a button. It fulfils an action when you click it and has a hover as well a focus states. You can choose which icon size you would like to use.
|
||||||
|
|
||||||
`IconButton` is best used when you only want an icon instead of a button with text, for example when you want to place a solitary clickable icon next to text. An example where an `IconButton` is used in Grafana is the hamburger icon at the top left which opens the new navigation.
|
`IconButton` is best used when you only want an icon instead of a button with text, for example when you want to place a solitary clickable icon next to text. An example where an `IconButton` is used in Grafana is the hamburger icon at the top left which opens the new navigation.
|
||||||
When using `IconButton` right next to a text element consider wrapping both in a flex container and use `align-items: center;` to make them align properly.
|
When using `IconButton` right next to a text element consider wrapping both in a flex container and use `align-items: center;` to make them align properly.
|
||||||
|
|
||||||
Always keep in mind to add text for a tooltip and an aria label.
|
There are two options to use the IconButton:
|
||||||
|
|
||||||
|
- with `Tooltip`: This is the preferred option since we don't want to rely on assumptions when it comes to the meaning an `Icon` has. Add a text for the `Tooltip`. It will be used for the `aria-label` as well.
|
||||||
|
- without `Tooltip`: This is an option for use cases where the `Icon` is unambiguous e.g <Icon name="angle-down" /> for expanding a folder. Add a text for the `aria-label` and there will **not** be a `Tooltip`.
|
||||||
|
|
||||||
|
The IconButton used to have a property called `ariaLabel` which got deprecated. You can now use the regular property `aria-label` instead.
|
||||||
|
|
||||||
<Alert severity="warning" title={'Please note:'}>
|
<Alert severity="warning" title={'Please note:'}>
|
||||||
After reviewing this component we would like you to know that there are only 5 sizes available (sizes xs to xl). Sizes
|
After reviewing this component we would like you to know that there are only 5 sizes available (sizes xs to xl). Sizes
|
||||||
|
@ -7,13 +7,16 @@ import { IconSize, IconName } from '../../types';
|
|||||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
import { HorizontalGroup, VerticalGroup } from '../Layout/Layout';
|
import { HorizontalGroup, VerticalGroup } from '../Layout/Layout';
|
||||||
|
|
||||||
import { IconButton, IconButtonVariant, Props as IconButtonProps } from './IconButton';
|
import { BasePropsWithTooltip, IconButton, IconButtonVariant, Props as IconButtonProps } from './IconButton';
|
||||||
import mdx from './IconButton.mdx';
|
import mdx from './IconButton.mdx';
|
||||||
|
|
||||||
interface ScenarioProps {
|
interface ScenarioProps {
|
||||||
background: 'canvas' | 'primary' | 'secondary';
|
background: 'canvas' | 'primary' | 'secondary';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultExcludes = ['ariaLabel', 'aria-label'];
|
||||||
|
const additionalExcludes = ['size', 'name', 'variant', 'iconType'];
|
||||||
|
|
||||||
const meta: Meta<typeof IconButton> = {
|
const meta: Meta<typeof IconButton> = {
|
||||||
title: 'Buttons/IconButton',
|
title: 'Buttons/IconButton',
|
||||||
component: IconButton,
|
component: IconButton,
|
||||||
@ -22,6 +25,7 @@ const meta: Meta<typeof IconButton> = {
|
|||||||
docs: {
|
docs: {
|
||||||
page: mdx,
|
page: mdx,
|
||||||
},
|
},
|
||||||
|
controls: { exclude: defaultExcludes },
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
name: 'apps',
|
name: 'apps',
|
||||||
@ -30,7 +34,8 @@ const meta: Meta<typeof IconButton> = {
|
|||||||
tooltip: 'sample tooltip message',
|
tooltip: 'sample tooltip message',
|
||||||
tooltipPlacement: 'top',
|
tooltipPlacement: 'top',
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
ariaLabel: 'sample aria-label content',
|
ariaLabel: 'this property is deprecated',
|
||||||
|
['aria-label']: 'sample aria-label content',
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@ -43,7 +48,7 @@ export const Basic: StoryFn<typeof IconButton> = (args: IconButtonProps) => {
|
|||||||
return <IconButton {...args} />;
|
return <IconButton {...args} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExamplesSizes = () => {
|
export const ExamplesSizes = (args: BasePropsWithTooltip) => {
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const sizes: IconSize[] = ['xs', 'sm', 'md', 'lg', 'xl'];
|
const sizes: IconSize[] = ['xs', 'sm', 'md', 'lg', 'xl'];
|
||||||
const icons: IconName[] = ['search', 'trash-alt', 'arrow-left', 'times'];
|
const icons: IconName[] = ['search', 'trash-alt', 'arrow-left', 'times'];
|
||||||
@ -56,17 +61,22 @@ export const ExamplesSizes = () => {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HorizontalGroup spacing="md">
|
<HorizontalGroup justify="center">
|
||||||
{variants.map((variant) => {
|
{variants.map((variant) => {
|
||||||
return (
|
return (
|
||||||
<div key={variant}>
|
<div
|
||||||
|
key={variant}
|
||||||
|
className={css`
|
||||||
|
margin: auto ${theme.spacing(1)};
|
||||||
|
`}
|
||||||
|
>
|
||||||
<p>{variant}</p>
|
<p>{variant}</p>
|
||||||
{icons.map((icon) => {
|
{icons.map((icon) => {
|
||||||
return (
|
return (
|
||||||
<div className={rowStyle} key={icon}>
|
<div className={rowStyle} key={icon}>
|
||||||
{sizes.map((size) => (
|
{sizes.map((size) => (
|
||||||
<span key={icon + size}>
|
<span key={icon + size}>
|
||||||
<IconButton name={icon} size={size} variant={variant} tooltip="Tooltip example" />
|
<IconButton name={icon} size={size} variant={variant} tooltip={args.tooltip} />
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -81,7 +91,7 @@ export const ExamplesSizes = () => {
|
|||||||
<div className={rowStyle} key={icon}>
|
<div className={rowStyle} key={icon}>
|
||||||
{sizes.map((size) => (
|
{sizes.map((size) => (
|
||||||
<span key={icon + size}>
|
<span key={icon + size}>
|
||||||
<IconButton name={icon} size={size} tooltip="Tooltip example" disabled />
|
<IconButton name={icon} size={size} tooltip={args.tooltip} disabled />
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -91,7 +101,42 @@ export const ExamplesSizes = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExamplesBackground = () => {
|
ExamplesSizes.parameters = {
|
||||||
|
controls: {
|
||||||
|
exclude: [...defaultExcludes, ...additionalExcludes],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExamplesBackground = (args: BasePropsWithTooltip) => {
|
||||||
|
const RenderBackgroundScenario = ({ background }: ScenarioProps) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const variants: IconButtonVariant[] = ['primary', 'secondary', 'destructive'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
padding: 30px;
|
||||||
|
background: ${theme.colors.background[background]};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<VerticalGroup spacing="md">
|
||||||
|
<div>{background}</div>
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
gap: ${theme.spacing(2)};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{variants.map((variant) => {
|
||||||
|
return <IconButton name="times" size="xl" variant={variant} key={variant} tooltip={args.tooltip} />;
|
||||||
|
})}
|
||||||
|
<IconButton name="times" size="xl" tooltip={args.tooltip} disabled />
|
||||||
|
</div>
|
||||||
|
</VerticalGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<RenderBackgroundScenario background="canvas" />
|
<RenderBackgroundScenario background="canvas" />
|
||||||
@ -101,33 +146,10 @@ export const ExamplesBackground = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RenderBackgroundScenario = ({ background }: ScenarioProps) => {
|
ExamplesBackground.parameters = {
|
||||||
const theme = useTheme2();
|
controls: {
|
||||||
const variants: IconButtonVariant[] = ['primary', 'secondary', 'destructive'];
|
exclude: [...defaultExcludes, ...additionalExcludes],
|
||||||
|
},
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={css`
|
|
||||||
padding: 30px;
|
|
||||||
background: ${theme.colors.background[background]};
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<VerticalGroup spacing="md">
|
|
||||||
<div>{background}</div>
|
|
||||||
<div
|
|
||||||
className={css`
|
|
||||||
display: flex;
|
|
||||||
gap: ${theme.spacing(2)};
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{variants.map((variant) => {
|
|
||||||
return <IconButton name="times" size="xl" variant={variant} key={variant} tooltip="Tooltip example" />;
|
|
||||||
})}
|
|
||||||
<IconButton name="times" size="xl" tooltip="Tooltip example" disabled />
|
|
||||||
</div>
|
|
||||||
</VerticalGroup>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
@ -15,76 +15,92 @@ export type IconButtonVariant = 'primary' | 'secondary' | 'destructive';
|
|||||||
|
|
||||||
type LimitedIconSize = ComponentSize | 'xl';
|
type LimitedIconSize = ComponentSize | 'xl';
|
||||||
|
|
||||||
export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
interface BaseProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'aria-label'> {
|
||||||
/** Name of the icon **/
|
/** Name of the icon **/
|
||||||
name: IconName;
|
name: IconName;
|
||||||
/** Icon size - sizes xxl and xxxl are deprecated and when used being decreased to xl*/
|
/** Icon size - sizes xxl and xxxl are deprecated and when used being decreased to xl*/
|
||||||
size?: IconSize;
|
size?: IconSize;
|
||||||
/** Type of the icon - mono or default */
|
/** Type of the icon - mono or default */
|
||||||
iconType?: IconType;
|
iconType?: IconType;
|
||||||
/** Tooltip content to display on hover */
|
|
||||||
tooltip?: PopoverContent;
|
|
||||||
/** Position of the tooltip */
|
|
||||||
tooltipPlacement?: TooltipPlacement;
|
|
||||||
/** Variant to change the color of the Icon */
|
/** Variant to change the color of the Icon */
|
||||||
variant?: IconButtonVariant;
|
variant?: IconButtonVariant;
|
||||||
/** Text available only for screen readers. Will use tooltip text as fallback. */
|
|
||||||
ariaLabel?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IconButton = React.forwardRef<HTMLButtonElement, Props>(
|
export interface BasePropsWithTooltip extends BaseProps {
|
||||||
(
|
/** Tooltip content to display on hover and as the aria-label */
|
||||||
{
|
tooltip: PopoverContent;
|
||||||
name,
|
/** Position of the tooltip */
|
||||||
size = 'md',
|
tooltipPlacement?: TooltipPlacement;
|
||||||
iconType,
|
}
|
||||||
tooltip,
|
|
||||||
tooltipPlacement,
|
|
||||||
ariaLabel,
|
|
||||||
className,
|
|
||||||
variant = 'secondary',
|
|
||||||
...restProps
|
|
||||||
},
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const theme = useTheme2();
|
|
||||||
let limitedIconSize: LimitedIconSize;
|
|
||||||
|
|
||||||
// very large icons (xl to xxxl) are unified to size xl
|
interface BasePropsWithAriaLabel extends BaseProps {
|
||||||
if (size === 'xxl' || size === 'xxxl') {
|
/** @deprecated use aria-label instead*/
|
||||||
deprecationWarning('IconButton', 'size="xxl" and size="xxxl"', 'size="xl"');
|
ariaLabel?: string;
|
||||||
limitedIconSize = 'xl';
|
/** Text available only for screen readers. No tooltip will be set in this case. */
|
||||||
} else {
|
['aria-label']: string;
|
||||||
limitedIconSize = size;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const styles = getStyles(theme, limitedIconSize, variant);
|
export type Props = BasePropsWithTooltip | BasePropsWithAriaLabel;
|
||||||
const tooltipString = typeof tooltip === 'string' ? tooltip : '';
|
|
||||||
|
|
||||||
// When using tooltip, ref is forwarded to Tooltip component instead for https://github.com/grafana/grafana/issues/65632
|
export const IconButton = React.forwardRef<HTMLButtonElement, Props>((props, ref) => {
|
||||||
const button = (
|
const { size = 'md', variant = 'secondary' } = props;
|
||||||
|
|
||||||
|
const theme = useTheme2();
|
||||||
|
let limitedIconSize: LimitedIconSize;
|
||||||
|
|
||||||
|
// very large icons (xl to xxxl) are unified to size xl
|
||||||
|
if (size === 'xxl' || size === 'xxxl') {
|
||||||
|
deprecationWarning('IconButton', 'size="xxl" and size="xxxl"', 'size="xl"');
|
||||||
|
limitedIconSize = 'xl';
|
||||||
|
} else {
|
||||||
|
limitedIconSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = getStyles(theme, limitedIconSize, variant);
|
||||||
|
|
||||||
|
let ariaLabel: string | undefined;
|
||||||
|
let buttonRef: typeof ref | undefined;
|
||||||
|
|
||||||
|
if ('tooltip' in props) {
|
||||||
|
const { tooltip } = props;
|
||||||
|
ariaLabel = typeof tooltip === 'string' ? tooltip : undefined;
|
||||||
|
} else if ('ariaLabel' in props || 'aria-label' in props) {
|
||||||
|
const { ariaLabel: deprecatedAriaLabel, ['aria-label']: ariaLabelProp } = props;
|
||||||
|
ariaLabel = ariaLabelProp || deprecatedAriaLabel;
|
||||||
|
buttonRef = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When using tooltip, ref is forwarded to Tooltip component instead for https://github.com/grafana/grafana/issues/65632
|
||||||
|
if ('tooltip' in props) {
|
||||||
|
const { name, iconType, className, tooltip, tooltipPlacement, ...restProps } = props;
|
||||||
|
return (
|
||||||
|
<Tooltip ref={ref} content={tooltip} placement={tooltipPlacement}>
|
||||||
|
<button
|
||||||
|
{...restProps}
|
||||||
|
ref={buttonRef}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
className={cx(styles.button, className)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Icon name={name} size={limitedIconSize} className={styles.icon} type={iconType} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const { name, iconType, className, ...restProps } = props;
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
ref={tooltip ? undefined : ref}
|
|
||||||
aria-label={ariaLabel || tooltipString}
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
ref={buttonRef}
|
||||||
|
aria-label={ariaLabel}
|
||||||
className={cx(styles.button, className)}
|
className={cx(styles.button, className)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Icon name={name} size={limitedIconSize} className={styles.icon} type={iconType} />
|
<Icon name={name} size={limitedIconSize} className={styles.icon} type={iconType} />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tooltip) {
|
|
||||||
return (
|
|
||||||
<Tooltip ref={ref} content={tooltip} placement={tooltipPlacement}>
|
|
||||||
{button}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return button;
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
IconButton.displayName = 'IconButton';
|
IconButton.displayName = 'IconButton';
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ export function Modal(props: PropsWithChildren<Props>) {
|
|||||||
typeof title !== 'string' && title
|
typeof title !== 'string' && title
|
||||||
}
|
}
|
||||||
<div className={styles.modalHeaderClose}>
|
<div className={styles.modalHeaderClose}>
|
||||||
<IconButton aria-label="Close dialog" name="times" size="xl" onClick={onDismiss} tooltip="Close" />
|
<IconButton name="times" size="xl" onClick={onDismiss} tooltip="Close" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(styles.modalContent, contentClassName)}>{children}</div>
|
<div className={cx(styles.modalContent, contentClassName)}>{children}</div>
|
||||||
|
@ -233,7 +233,7 @@ describe('SelectBase', () => {
|
|||||||
|
|
||||||
expect(screen.getByLabelText('My select')).toBeInTheDocument();
|
expect(screen.getByLabelText('My select')).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(screen.getByLabelText('Remove Option 1'));
|
await userEvent.click(screen.getAllByLabelText('Remove')[0]);
|
||||||
expect(onChangeHandler).toHaveBeenCalledWith([], {
|
expect(onChangeHandler).toHaveBeenCalledWith([], {
|
||||||
action: 'remove-value',
|
action: 'remove-value',
|
||||||
name: undefined,
|
name: undefined,
|
||||||
|
@ -28,8 +28,7 @@ export const TagItem = ({ name, disabled, onRemove }: Props) => {
|
|||||||
name="times"
|
name="times"
|
||||||
size="lg"
|
size="lg"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
ariaLabel={`Remove "${name}" tag`}
|
tooltip={`Remove "${name}" tag`}
|
||||||
tooltip="Remove tag"
|
|
||||||
onClick={() => onRemove(name)}
|
onClick={() => onRemove(name)}
|
||||||
className={styles.buttonStyles}
|
className={styles.buttonStyles}
|
||||||
/>
|
/>
|
||||||
|
@ -25,15 +25,15 @@ describe('LayerDragDropList', () => {
|
|||||||
it('showActions', () => {
|
it('showActions', () => {
|
||||||
renderScenario({ showActions: () => true });
|
renderScenario({ showActions: () => true });
|
||||||
|
|
||||||
expect(screen.getAllByLabelText('Duplicate button').length).toEqual(2);
|
expect(screen.getAllByLabelText('Duplicate').length).toEqual(2);
|
||||||
expect(screen.getAllByLabelText('Remove button').length).toEqual(2);
|
expect(screen.getAllByLabelText('Remove').length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('showActions - no duplicate', () => {
|
it('showActions - no duplicate', () => {
|
||||||
renderScenario({ showActions: () => true, onDuplicate: undefined });
|
renderScenario({ showActions: () => true, onDuplicate: undefined });
|
||||||
|
|
||||||
expect(screen.getAllByLabelText('Remove button').length).toEqual(2);
|
expect(screen.getAllByLabelText('Remove').length).toEqual(2);
|
||||||
expect(screen.queryAllByLabelText('Duplicate button').length).toEqual(0);
|
expect(screen.queryAllByLabelText('Duplicate').length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders draggable icon', () => {
|
it('renders draggable icon', () => {
|
||||||
|
@ -81,7 +81,6 @@ export const LayerDragDropList = <T extends LayerElement>({
|
|||||||
<IconButton
|
<IconButton
|
||||||
name="copy"
|
name="copy"
|
||||||
tooltip="Duplicate"
|
tooltip="Duplicate"
|
||||||
ariaLabel="Duplicate button"
|
|
||||||
className={style.actionIcon}
|
className={style.actionIcon}
|
||||||
onClick={() => onDuplicate(element)}
|
onClick={() => onDuplicate(element)}
|
||||||
/>
|
/>
|
||||||
@ -90,7 +89,6 @@ export const LayerDragDropList = <T extends LayerElement>({
|
|||||||
<IconButton
|
<IconButton
|
||||||
name="trash-alt"
|
name="trash-alt"
|
||||||
tooltip="Remove"
|
tooltip="Remove"
|
||||||
ariaLabel="Remove button"
|
|
||||||
className={cx(style.actionIcon, style.dragIcon)}
|
className={cx(style.actionIcon, style.dragIcon)}
|
||||||
onClick={() => onDelete(element)}
|
onClick={() => onDelete(element)}
|
||||||
/>
|
/>
|
||||||
|
@ -24,11 +24,11 @@ describe('QueryOperationRowHeader', () => {
|
|||||||
describe('collapsable property', () => {
|
describe('collapsable property', () => {
|
||||||
test('should show the button to collapse the query row by default', () => {
|
test('should show the button to collapse the query row by default', () => {
|
||||||
setup();
|
setup();
|
||||||
expect(screen.getByLabelText('toggle collapse and expand query row')).toBeInTheDocument();
|
expect(screen.getByLabelText('Collapse query row')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
test('should hide the button to collapse the query row when collapsable is set as false', () => {
|
test('should hide the button to collapse the query row when collapsable is set as false', () => {
|
||||||
setup({ collapsable: false });
|
setup({ collapsable: false });
|
||||||
expect(screen.queryByLabelText('toggle collapse and expand query row')).not.toBeInTheDocument();
|
expect(screen.queryByLabelText('Collapse query row')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -40,7 +40,6 @@ export const QueryOperationRowHeader = ({
|
|||||||
{collapsable && (
|
{collapsable && (
|
||||||
<IconButton
|
<IconButton
|
||||||
name={isContentVisible ? 'angle-down' : 'angle-right'}
|
name={isContentVisible ? 'angle-down' : 'angle-right'}
|
||||||
aria-label="toggle collapse and expand query row"
|
|
||||||
tooltip={isContentVisible ? 'Collapse query row' : 'Expand query row'}
|
tooltip={isContentVisible ? 'Collapse query row' : 'Expand query row'}
|
||||||
className={styles.collapseIcon}
|
className={styles.collapseIcon}
|
||||||
onClick={onRowToggle}
|
onClick={onRowToggle}
|
||||||
|
@ -72,7 +72,7 @@ describe('browse-dashboards DeleteModal', () => {
|
|||||||
it('calls onDismiss when clicking the X', async () => {
|
it('calls onDismiss when clicking the X', async () => {
|
||||||
render(<DeleteModal {...defaultProps} />);
|
render(<DeleteModal {...defaultProps} />);
|
||||||
|
|
||||||
await userEvent.click(await screen.findByRole('button', { name: 'Close dialog' }));
|
await userEvent.click(await screen.findByRole('button', { name: 'Close' }));
|
||||||
expect(mockOnDismiss).toHaveBeenCalled();
|
expect(mockOnDismiss).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -113,7 +113,7 @@ describe('browse-dashboards MoveModal', () => {
|
|||||||
it('calls onDismiss when clicking the X', async () => {
|
it('calls onDismiss when clicking the X', async () => {
|
||||||
render(<MoveModal {...props} />);
|
render(<MoveModal {...props} />);
|
||||||
|
|
||||||
await userEvent.click(await screen.findByRole('button', { name: 'Close dialog' }));
|
await userEvent.click(await screen.findByRole('button', { name: 'Close' }));
|
||||||
expect(mockOnDismiss).toHaveBeenCalled();
|
expect(mockOnDismiss).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -74,7 +74,7 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
|
|||||||
onFolderClick(item.uid, !isOpen);
|
onFolderClick(item.uid, !isOpen);
|
||||||
}}
|
}}
|
||||||
name={isOpen ? 'angle-down' : 'angle-right'}
|
name={isOpen ? 'angle-down' : 'angle-right'}
|
||||||
ariaLabel={isOpen ? 'Collapse folder' : 'Expand folder'}
|
tooltip={isOpen ? 'Collapse folder' : 'Expand folder'}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -318,7 +318,7 @@ describe('LibraryPanelsSearch', () => {
|
|||||||
expect(card()).toBeInTheDocument();
|
expect(card()).toBeInTheDocument();
|
||||||
expect(within(card()).getByText(/library panel name/i)).toBeInTheDocument();
|
expect(within(card()).getByText(/library panel name/i)).toBeInTheDocument();
|
||||||
expect(within(card()).getByText(/library panel description/i)).toBeInTheDocument();
|
expect(within(card()).getByText(/library panel description/i)).toBeInTheDocument();
|
||||||
expect(within(card()).getByLabelText(/delete button on panel type card/i)).toBeInTheDocument();
|
expect(within(card()).getByLabelText(/Delete/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -354,9 +354,9 @@ describe('LibraryPanelsSearch', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await userEvent.click(screen.getByLabelText(/delete button on panel type card/i));
|
await userEvent.click(screen.getByLabelText('Delete'));
|
||||||
await waitFor(() => expect(screen.getByText('Do you want to delete this panel?')).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText('Do you want to delete this panel?')).toBeInTheDocument());
|
||||||
await userEvent.click(screen.getByRole('button', { name: 'Delete' }));
|
await userEvent.click(screen.getAllByRole('button', { name: 'Delete' })[1]);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(getLibraryPanelsSpy).toHaveBeenCalledWith({
|
expect(getLibraryPanelsSpy).toHaveBeenCalledWith({
|
||||||
|
@ -96,7 +96,7 @@ describe('QueryEditorRows', () => {
|
|||||||
const queryEditorRows = await screen.findAllByTestId('query-editor-row');
|
const queryEditorRows = await screen.findAllByTestId('query-editor-row');
|
||||||
|
|
||||||
for (const childQuery of queryEditorRows) {
|
for (const childQuery of queryEditorRows) {
|
||||||
const toggleExpandButton = queryByLabelText(childQuery, 'toggle collapse and expand query row') as HTMLElement;
|
const toggleExpandButton = queryByLabelText(childQuery, 'Collapse query row') as HTMLElement;
|
||||||
|
|
||||||
expect(toggleExpandButton).toBeInTheDocument();
|
expect(toggleExpandButton).toBeInTheDocument();
|
||||||
expect(toggleExpandButton.getAttribute('aria-expanded')).toBe('true');
|
expect(toggleExpandButton.getAttribute('aria-expanded')).toBe('true');
|
||||||
|
@ -80,7 +80,7 @@ describe('QueryGroup', () => {
|
|||||||
await userEvent.click(addExpressionButton);
|
await userEvent.click(addExpressionButton);
|
||||||
|
|
||||||
const lastQueryEditorRow = (await screen.findAllByTestId('query-editor-row')).at(-1);
|
const lastQueryEditorRow = (await screen.findAllByTestId('query-editor-row')).at(-1);
|
||||||
const lastEditorToggleRow = (await screen.findAllByLabelText('toggle collapse and expand query row')).at(-1);
|
const lastEditorToggleRow = (await screen.findAllByLabelText('Collapse query row')).at(-1);
|
||||||
|
|
||||||
expect(lastEditorToggleRow?.getAttribute('aria-expanded')).toBe('true');
|
expect(lastEditorToggleRow?.getAttribute('aria-expanded')).toBe('true');
|
||||||
expect(lastQueryEditorRow?.firstElementChild?.children.length).toBe(2);
|
expect(lastQueryEditorRow?.firstElementChild?.children.length).toBe(2);
|
||||||
@ -97,7 +97,7 @@ describe('QueryGroup', () => {
|
|||||||
await userEvent.click(addQueryButton);
|
await userEvent.click(addQueryButton);
|
||||||
|
|
||||||
const lastQueryEditorRow = (await screen.findAllByTestId('query-editor-row')).at(-1);
|
const lastQueryEditorRow = (await screen.findAllByTestId('query-editor-row')).at(-1);
|
||||||
const lastEditorToggleRow = (await screen.findAllByLabelText('toggle collapse and expand query row')).at(-1);
|
const lastEditorToggleRow = (await screen.findAllByLabelText('Collapse query row')).at(-1);
|
||||||
|
|
||||||
expect(lastEditorToggleRow?.getAttribute('aria-expanded')).toBe('true');
|
expect(lastEditorToggleRow?.getAttribute('aria-expanded')).toBe('true');
|
||||||
expect(lastQueryEditorRow?.firstElementChild?.children.length).toBe(2);
|
expect(lastQueryEditorRow?.firstElementChild?.children.length).toBe(2);
|
||||||
|
@ -132,8 +132,7 @@ const ServiceAccountListItem = memo(
|
|||||||
name="trash-alt"
|
name="trash-alt"
|
||||||
size="md"
|
size="md"
|
||||||
onClick={() => onRemoveButtonClick(serviceAccount)}
|
onClick={() => onRemoveButtonClick(serviceAccount)}
|
||||||
aria-label={`Delete service account ${serviceAccount.name}`}
|
tooltip={`Delete service account ${serviceAccount.name}`}
|
||||||
tooltip="Delete account"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
|
@ -201,8 +201,8 @@ describe(`Azure Monitor QueryEditor`, () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
await screen.findByText('testlabel');
|
await screen.findByText('testlabel');
|
||||||
const labelClear = await screen.findByLabelText('Remove testlabel');
|
const labelClear = await screen.findAllByLabelText('Remove');
|
||||||
await user.click(labelClear);
|
await user.click(labelClear[0]);
|
||||||
mockQuery = setDimensionFilterValue(mockQuery, 0, 'filters', []);
|
mockQuery = setDimensionFilterValue(mockQuery, 0, 'filters', []);
|
||||||
expect(onQueryChange).toHaveBeenCalledWith({
|
expect(onQueryChange).toHaveBeenCalledWith({
|
||||||
...mockQuery,
|
...mockQuery,
|
||||||
|
@ -270,7 +270,7 @@ const Filter = (
|
|||||||
onCloseMenu={() => onFieldChange('filters', item, selected, onChange)}
|
onCloseMenu={() => onFieldChange('filters', item, selected, onChange)}
|
||||||
hideSelectedOptions={false}
|
hideSelectedOptions={false}
|
||||||
/>
|
/>
|
||||||
<AccessoryButton aria-label="Remove" icon="times" variant="secondary" onClick={onDelete} type="button" />
|
<AccessoryButton aria-label="Remove filter" icon="times" variant="secondary" onClick={onDelete} type="button" />
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -281,7 +281,7 @@ describe(`Traces Filters`, () => {
|
|||||||
rerender
|
rerender
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeButtons = screen.getAllByLabelText('Remove');
|
const removeButtons = screen.getAllByLabelText('Remove filter');
|
||||||
|
|
||||||
mockQuery = {
|
mockQuery = {
|
||||||
...mockQuery,
|
...mockQuery,
|
||||||
@ -388,9 +388,9 @@ describe(`Traces Filters`, () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const removeLabel = screen.getByLabelText(`Remove test-app-id-2`);
|
const removeLabel = screen.getAllByLabelText(`Remove`);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await userEvent.click(removeLabel);
|
await userEvent.click(removeLabel[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
|
@ -112,8 +112,8 @@ describe('SearchField', () => {
|
|||||||
expect(updateFilter).toHaveBeenCalledWith({ ...filter, value: ['driver', 'customer'] });
|
expect(updateFilter).toHaveBeenCalledWith({ ...filter, value: ['driver', 'customer'] });
|
||||||
|
|
||||||
// Remove the first value
|
// Remove the first value
|
||||||
const firstValRemove = await screen.findByLabelText('Remove driver');
|
const firstValRemove = await screen.findAllByLabelText('Remove');
|
||||||
await user.click(firstValRemove);
|
await user.click(firstValRemove[0]);
|
||||||
expect(updateFilter).toHaveBeenCalledWith({ ...filter, value: ['customer'] });
|
expect(updateFilter).toHaveBeenCalledWith({ ...filter, value: ['customer'] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user