[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:
Polina Boneva
2023-01-25 18:29:53 +02:00
committed by GitHub
parent 4dafdcbdc4
commit 08806924d8
3 changed files with 93 additions and 21 deletions

View File

@@ -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();
});

View File

@@ -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',
}), }),

View File

@@ -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}
/> />
); );
} }