mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardLinks: Fixes crash when link has no title (#31008)
* DashboardLinks: Fixes crash when link misses title * Chore: updates after PR comments
This commit is contained in:
parent
f43d834a59
commit
297ff9a168
@ -0,0 +1,104 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { DataLinksListItem, DataLinksListItemProps } from './DataLinksListItem';
|
||||||
|
|
||||||
|
const baseLink = {
|
||||||
|
url: '',
|
||||||
|
title: '',
|
||||||
|
onBuildUrl: jest.fn(),
|
||||||
|
onClick: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
function setupTestContext(options: Partial<DataLinksListItemProps>) {
|
||||||
|
const defaults: DataLinksListItemProps = {
|
||||||
|
index: 0,
|
||||||
|
link: baseLink,
|
||||||
|
data: [],
|
||||||
|
onChange: jest.fn(),
|
||||||
|
onEdit: jest.fn(),
|
||||||
|
onRemove: jest.fn(),
|
||||||
|
suggestions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = { ...defaults, ...options };
|
||||||
|
const { rerender } = render(<DataLinksListItem {...props} />);
|
||||||
|
|
||||||
|
return { rerender, props };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DataLinksListItem', () => {
|
||||||
|
describe('when link has title', () => {
|
||||||
|
it('then the link title should be visible', () => {
|
||||||
|
const link = {
|
||||||
|
...baseLink,
|
||||||
|
title: 'Some Data Link Title',
|
||||||
|
};
|
||||||
|
setupTestContext({ link });
|
||||||
|
|
||||||
|
expect(screen.getByText(/some data link title/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when link has url', () => {
|
||||||
|
it('then the link url should be visible', () => {
|
||||||
|
const link = {
|
||||||
|
...baseLink,
|
||||||
|
url: 'http://localhost:3000',
|
||||||
|
};
|
||||||
|
setupTestContext({ link });
|
||||||
|
|
||||||
|
expect(screen.getByText(/http:\/\/localhost\:3000/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByTitle(/http:\/\/localhost\:3000/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when link is missing title', () => {
|
||||||
|
it('then the link title should be replaced by [Data link title not provided]', () => {
|
||||||
|
const link = {
|
||||||
|
...baseLink,
|
||||||
|
title: (undefined as unknown) as string,
|
||||||
|
};
|
||||||
|
setupTestContext({ link });
|
||||||
|
|
||||||
|
expect(screen.getByText(/data link title not provided/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when link is missing url', () => {
|
||||||
|
it('then the link url should be replaced by [Data link url not provided]', () => {
|
||||||
|
const link = {
|
||||||
|
...baseLink,
|
||||||
|
url: (undefined as unknown) as string,
|
||||||
|
};
|
||||||
|
setupTestContext({ link });
|
||||||
|
|
||||||
|
expect(screen.getByText(/data link url not provided/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByTitle('')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when link title is empty', () => {
|
||||||
|
it('then the link title should be replaced by [Data link title not provided]', () => {
|
||||||
|
const link = {
|
||||||
|
...baseLink,
|
||||||
|
title: ' ',
|
||||||
|
};
|
||||||
|
setupTestContext({ link });
|
||||||
|
|
||||||
|
expect(screen.getByText(/data link title not provided/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when link url is empty', () => {
|
||||||
|
it('then the link url should be replaced by [Data link url not provided]', () => {
|
||||||
|
const link = {
|
||||||
|
...baseLink,
|
||||||
|
url: ' ',
|
||||||
|
};
|
||||||
|
setupTestContext({ link });
|
||||||
|
|
||||||
|
expect(screen.getByText(/data link url not provided/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByTitle('')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -5,7 +5,7 @@ import { stylesFactory, useTheme } from '../../../themes';
|
|||||||
import { HorizontalGroup, VerticalGroup } from '../../Layout/Layout';
|
import { HorizontalGroup, VerticalGroup } from '../../Layout/Layout';
|
||||||
import { IconButton } from '../../IconButton/IconButton';
|
import { IconButton } from '../../IconButton/IconButton';
|
||||||
|
|
||||||
interface DataLinksListItemProps {
|
export interface DataLinksListItemProps {
|
||||||
index: number;
|
index: number;
|
||||||
link: DataLink;
|
link: DataLink;
|
||||||
data: DataFrame[];
|
data: DataFrame[];
|
||||||
@ -19,24 +19,25 @@ interface DataLinksListItemProps {
|
|||||||
export const DataLinksListItem: FC<DataLinksListItemProps> = ({ link, onEdit, onRemove }) => {
|
export const DataLinksListItem: FC<DataLinksListItemProps> = ({ link, onEdit, onRemove }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getDataLinkListItemStyles(theme);
|
const styles = getDataLinkListItemStyles(theme);
|
||||||
|
const { title = '', url = '' } = link;
|
||||||
|
|
||||||
const hasTitle = link.title.trim() !== '';
|
const hasTitle = title.trim() !== '';
|
||||||
const hasUrl = link.url.trim() !== '';
|
const hasUrl = url.trim() !== '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<VerticalGroup spacing="xs">
|
<VerticalGroup spacing="xs">
|
||||||
<HorizontalGroup justify="space-between" align="flex-start" width="100%">
|
<HorizontalGroup justify="space-between" align="flex-start" width="100%">
|
||||||
<div className={cx(styles.title, !hasTitle && styles.notConfigured)}>
|
<div className={cx(styles.title, !hasTitle && styles.notConfigured)}>
|
||||||
{hasTitle ? link.title : 'Data link title not provided'}
|
{hasTitle ? title : 'Data link title not provided'}
|
||||||
</div>
|
</div>
|
||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
<IconButton name="pen" onClick={onEdit} />
|
<IconButton name="pen" onClick={onEdit} />
|
||||||
<IconButton name="times" onClick={onRemove} />
|
<IconButton name="times" onClick={onRemove} />
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
<div className={cx(styles.url, !hasUrl && styles.notConfigured)} title={link.url}>
|
<div className={cx(styles.url, !hasUrl && styles.notConfigured)} title={url}>
|
||||||
{hasUrl ? link.url : 'Data link url not provided'}
|
{hasUrl ? url : 'Data link url not provided'}
|
||||||
</div>
|
</div>
|
||||||
</VerticalGroup>
|
</VerticalGroup>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user