mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
[Chore] Add unit tests to PanelChrome component (#61695)
* PanelChrome: test loadingState and status * add some tests * fix error-related tests * clean up * fix tests * pass aria-label from PanelChrome to ToolbarButton * add prop comments and clean up
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
import { screen, render } from '@testing-library/react';
|
import { screen, render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { LoadingState } from '@grafana/data';
|
||||||
|
|
||||||
import { PanelChrome, PanelChromeProps } from './PanelChrome';
|
import { PanelChrome, PanelChromeProps } from './PanelChrome';
|
||||||
|
|
||||||
const setup = (propOverrides?: Partial<PanelChromeProps>) => {
|
const setup = (propOverrides?: Partial<PanelChromeProps>) => {
|
||||||
@@ -35,18 +37,50 @@ it('renders an empty panel with padding', () => {
|
|||||||
expect(screen.getByText("Panel's Content").parentElement).not.toHaveStyle({ padding: '0px' });
|
expect(screen.getByText("Panel's Content").parentElement).not.toHaveStyle({ padding: '0px' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders panel with a header if prop title', () => {
|
// Check for backwards compatibility
|
||||||
|
it('renders panel header if prop title', () => {
|
||||||
setup({ title: 'Test Panel Header' });
|
setup({ title: 'Test Panel Header' });
|
||||||
|
|
||||||
expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders panel with a header with title in place if prop title', () => {
|
// Check for backwards compatibility
|
||||||
|
it('renders panel with title in place if prop title', () => {
|
||||||
setup({ title: 'Test Panel Header' });
|
setup({ title: 'Test Panel Header' });
|
||||||
|
|
||||||
expect(screen.getByText('Test Panel Header')).toBeInTheDocument();
|
expect(screen.getByText('Test Panel Header')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check for backwards compatibility
|
||||||
|
it('renders panel with a header if prop leftItems', () => {
|
||||||
|
setup({
|
||||||
|
leftItems: [<div key="left-item-test"> This should be a self-contained node </div>],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo implement when hoverHeader is implemented
|
||||||
|
it.skip('renders panel without header if no title, no leftItems, and hoverHeader is undefined', () => {
|
||||||
|
setup();
|
||||||
|
|
||||||
|
expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo implement when hoverHeader is implemented
|
||||||
|
it.skip('renders panel with a fixed header if prop hoverHeader is false', () => {
|
||||||
|
setup({ hoverHeader: false });
|
||||||
|
|
||||||
|
expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo implement when hoverHeader is implemented
|
||||||
|
it.skip('renders panel with a hovering header if prop hoverHeader is true', () => {
|
||||||
|
setup({ title: 'Test Panel Header', hoverHeader: true });
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('header-container')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders panel with a header if prop titleItems', () => {
|
it('renders panel with a header if prop titleItems', () => {
|
||||||
setup({
|
setup({
|
||||||
titleItems: [<div key="title-item-test"> This should be a self-contained node </div>],
|
titleItems: [<div key="title-item-test"> This should be a self-contained node </div>],
|
||||||
@@ -63,11 +97,6 @@ it('renders panel with a header with icons in place if prop titleItems', () => {
|
|||||||
expect(screen.getByTestId('title-items-container')).toBeInTheDocument();
|
expect(screen.getByTestId('title-items-container')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('renders panel with a fixed header if prop hoverHeader is false', () => {
|
|
||||||
// setup({ title: 'Test Panel Header', hoverHeader: false });
|
|
||||||
// expect(screen.getByTestId('header-container')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders panel with a header if prop menu', () => {
|
it('renders panel with a header if prop menu', () => {
|
||||||
setup({ menu: <div> Menu </div> });
|
setup({ menu: <div> Menu </div> });
|
||||||
|
|
||||||
@@ -81,8 +110,38 @@ it('renders panel with a show-on-hover menu icon if prop menu', () => {
|
|||||||
expect(screen.getByTestId('panel-menu-button')).not.toBeVisible();
|
expect(screen.getByTestId('panel-menu-button')).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('renders states in the panel header if any given', () => {});
|
it('renders error status in the panel header if any given', () => {
|
||||||
|
setup({ statusMessage: 'Error test' });
|
||||||
|
|
||||||
it.skip('renders leftItems in the panel header if any given when no states prop is given', () => {});
|
expect(screen.getByLabelText('Panel status')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it.skip('renders states in the panel header if both leftItems and states are given', () => {});
|
it('does not render error status in the panel header if loadingState is error, but no statusMessage', () => {
|
||||||
|
setup({ loadingState: LoadingState.Error, statusMessage: '' });
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('panel-status')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders loading indicator in the panel header if loadingState is loading', () => {
|
||||||
|
setup({ loadingState: LoadingState.Loading });
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('Panel loading bar')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders loading indicator in the panel header if loadingState is loading regardless of not having a header', () => {
|
||||||
|
setup({ loadingState: LoadingState.Loading, hoverHeader: true });
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('Panel loading bar')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders loading indicator in the panel header if loadingState is loading regardless of having a header', () => {
|
||||||
|
setup({ loadingState: LoadingState.Loading, hoverHeader: false });
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('Panel loading bar')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders streaming indicator in the panel header if loadingState is streaming', () => {
|
||||||
|
setup({ loadingState: LoadingState.Streaming });
|
||||||
|
|
||||||
|
expect(screen.getByTestId('panel-streaming')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|||||||
@@ -26,10 +26,13 @@ export interface PanelChromeProps {
|
|||||||
description?: string | (() => string);
|
description?: string | (() => string);
|
||||||
titleItems?: ReactNode[];
|
titleItems?: ReactNode[];
|
||||||
menu?: ReactElement | (() => ReactElement);
|
menu?: ReactElement | (() => ReactElement);
|
||||||
/** dragClass, hoverHeader not yet implemented */
|
|
||||||
dragClass?: string;
|
dragClass?: string;
|
||||||
dragClassCancel?: string;
|
dragClassCancel?: string;
|
||||||
hoverHeader?: boolean;
|
hoverHeader?: boolean;
|
||||||
|
/**
|
||||||
|
* Use only to indicate loading or streaming data in the panel.
|
||||||
|
* Any other values of loadingState are ignored.
|
||||||
|
*/
|
||||||
loadingState?: LoadingState;
|
loadingState?: LoadingState;
|
||||||
/**
|
/**
|
||||||
* Used to display status message (used for panel errors currently)
|
* Used to display status message (used for panel errors currently)
|
||||||
@@ -39,11 +42,13 @@ export interface PanelChromeProps {
|
|||||||
* Handle opening error details view (like inspect / error tab)
|
* Handle opening error details view (like inspect / error tab)
|
||||||
*/
|
*/
|
||||||
statusMessageOnClick?: (e: React.SyntheticEvent) => void;
|
statusMessageOnClick?: (e: React.SyntheticEvent) => void;
|
||||||
/** @deprecated in favor of props
|
/**
|
||||||
* status for errors and loadingState for loading and streaming
|
* @deprecated in favor of props
|
||||||
|
* statusMessage for error messages
|
||||||
|
* and loadingState for loading and streaming data
|
||||||
* which will serve the same purpose
|
* which will serve the same purpose
|
||||||
* of showing/interacting with the panel's data state
|
* of showing/interacting with the panel's state
|
||||||
* */
|
*/
|
||||||
leftItems?: ReactNode[];
|
leftItems?: ReactNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,14 +69,13 @@ export function PanelChrome({
|
|||||||
description = '',
|
description = '',
|
||||||
titleItems = [],
|
titleItems = [],
|
||||||
menu,
|
menu,
|
||||||
// dragClass,
|
dragClass,
|
||||||
|
dragClassCancel,
|
||||||
hoverHeader = false,
|
hoverHeader = false,
|
||||||
loadingState,
|
loadingState,
|
||||||
statusMessage,
|
statusMessage,
|
||||||
statusMessageOnClick,
|
statusMessageOnClick,
|
||||||
leftItems,
|
leftItems,
|
||||||
dragClass,
|
|
||||||
dragClassCancel,
|
|
||||||
}: PanelChromeProps) {
|
}: PanelChromeProps) {
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
@@ -108,7 +112,9 @@ export function PanelChrome({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.container} style={containerStyles} aria-label={ariaLabel}>
|
<div className={styles.container} style={containerStyles} aria-label={ariaLabel}>
|
||||||
<div className={styles.loadingBarContainer}>
|
<div className={styles.loadingBarContainer}>
|
||||||
{loadingState === LoadingState.Loading ? <LoadingBar width={'28%'} height={'2px'} /> : null}
|
{loadingState === LoadingState.Loading ? (
|
||||||
|
<LoadingBar width={'28%'} height={'2px'} ariaLabel="Panel loading bar" />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container">
|
<div className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container">
|
||||||
@@ -127,7 +133,7 @@ export function PanelChrome({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{loadingState === LoadingState.Streaming && (
|
{loadingState === LoadingState.Streaming && (
|
||||||
<div className={styles.item} style={itemStyles}>
|
<div className={styles.item} style={itemStyles} data-testid="panel-streaming">
|
||||||
<Tooltip content="Streaming">
|
<Tooltip content="Streaming">
|
||||||
<Icon name="circle-mono" size="sm" className={styles.streaming} />
|
<Icon name="circle-mono" size="sm" className={styles.streaming} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -156,6 +162,7 @@ export function PanelChrome({
|
|||||||
className={cx(styles.errorContainer, dragClassCancel)}
|
className={cx(styles.errorContainer, dragClassCancel)}
|
||||||
message={statusMessage}
|
message={statusMessage}
|
||||||
onClick={statusMessageOnClick}
|
onClick={statusMessageOnClick}
|
||||||
|
ariaLabel="Panel status"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -229,6 +236,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
loadingBarContainer: css({
|
loadingBarContainer: css({
|
||||||
|
label: 'panel-loading-bar-container',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -246,6 +254,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
padding: theme.spacing(0, 0, 0, 1),
|
padding: theme.spacing(0, 0, 0, 1),
|
||||||
}),
|
}),
|
||||||
streaming: css({
|
streaming: css({
|
||||||
|
label: 'panel-streaming',
|
||||||
marginRight: 0,
|
marginRight: 0,
|
||||||
color: theme.colors.success.text,
|
color: theme.colors.success.text,
|
||||||
|
|
||||||
@@ -254,6 +263,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
title: css({
|
title: css({
|
||||||
|
label: 'panel-title',
|
||||||
marginBottom: 0, // override default h6 margin-bottom
|
marginBottom: 0, // override default h6 margin-bottom
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -270,6 +280,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}),
|
}),
|
||||||
menuItem: css({
|
menuItem: css({
|
||||||
|
label: 'panel-menu',
|
||||||
visibility: 'hidden',
|
visibility: 'hidden',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ export interface Props {
|
|||||||
className?: string;
|
className?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
onClick?: (e: React.SyntheticEvent) => void;
|
onClick?: (e: React.SyntheticEvent) => void;
|
||||||
|
ariaLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PanelStatus({ className, message, onClick }: Props) {
|
export function PanelStatus({ className, message, onClick, ariaLabel = 'status' }: Props) {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -22,6 +23,7 @@ export function PanelStatus({ className, message, onClick }: Props) {
|
|||||||
variant={'destructive'}
|
variant={'destructive'}
|
||||||
icon="exclamation-triangle"
|
icon="exclamation-triangle"
|
||||||
tooltip={message || ''}
|
tooltip={message || ''}
|
||||||
|
aria-label={ariaLabel}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user